# Workable Jobs Scraper (`devilscrapes/workable-jobs-scraper`) Actor

Scrape every open job posting from any Workable-hosted employer board via Workable's own public widget JSON API, no login or browser required. Filter by keyword, department, or location entirely client-side, and add full HTML descriptions on request.

- **URL**: https://apify.com/devilscrapes/workable-jobs-scraper.md
- **Developed by:** [DevilScrapes](https://apify.com/devilscrapes) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

<div align="center">
  <img src=".actor/icon.svg" width="160" alt="Devil Scrapes mark" />

## Workable Jobs Scraper

**💰 $1.50 / 1 000 results**  ·  pay only for results  ·  no credit card to try

*We do the dirty work so your dataset stays clean.* 😈

Scrape every open job posting from any Workable-hosted employer board via Workable's own public widget JSON API — no login, no browser, no API key. Give it a list of subdomains and get titles, locations, departments, remote flags, and optional full descriptions as clean typed rows.

</div>

***

### 🎯 What this scrapes

Thousands of companies run their careers page on Workable, and every one of those boards is served by the same public widget JSON API (`apply.workable.com/api/v1/widget/accounts/{subdomain}`). This Actor talks to it directly: hand it one or more subdomains — the `{subdomain}` in `apply.workable.com/{subdomain}` or `{subdomain}.workable.com` — and it pulls every open posting for each board in one call, normalizing the result into one row schema. One scraper, every Workable employer.

### 🔥 Features

- 🔑 **Keyless public API** — no OAuth, no token, no per-company setup; the same widget endpoint the careers page itself calls.
- 🏢 **Multi-company in one run** — pass many subdomains; rows come back identically shaped, tagged with `company` / `company_name`.
- 🎯 **Client-side filters** — narrow by free-text `searchQuery`, `department`, or `location` after the fetch, at no extra request cost.
- 📝 **Optional full descriptions** — flip `includeDescription` on for the complete HTML posting body, or off for a faster, smaller payload.
- 🔁 **Retries with backoff + fingerprint impersonation** — `curl-cffi` presents a real browser handshake and retries transient `429/5xx` so a large multi-employer batch finishes instead of dying midway.
- 🧊 **Clean, typed rows** — Pydantic-validated, ISO-8601 timestamps, stable IDs. Export JSON / CSV / Excel straight from the Apify Console.

### 💡 Use cases

- **Recruiting & sourcing** — pull every open req from a target employer's Workable board in one pass.
- **Job-board aggregation** — add Workable coverage next to Workday / Greenhouse / Lever / Ashby / SmartRecruiters in one pipeline.
- **SDR / BD hiring-intent signal** — track which companies are actively hiring, where, and in which departments.
- **HR-tech data pipelines** — wire structured job rows into your CRM, dashboard, or n8n / Make workflow on a schedule.

### ⚙️ How to use it

1. Click **Try for free** at the top of the Store listing.
2. Add one or more **Companies** — a Workable subdomain (e.g. `remotebase`) or a full board URL in either recognized form.
3. Optionally set **Search query**, **Department**, **Location**, **Max results per company**, and toggle **Include description**.
4. Click **Start**. Rows stream into the dataset as each company's board is fetched.
5. Export from **Storage → Dataset** as JSON, CSV, or Excel — or pull via the Apify API.

### 📥 Input

| Field | Type | Required | Default | Notes |
|---|---|:--:|---|---|
| `companies` | `array` | ✅ | — | Workable subdomains or full board URLs (e.g. `remotebase`, `https://apply.workable.com/remotebase/`, `https://remotebase.workable.com/`). |
| `searchQuery` | `string` | no | `null` | Client-side substring filter on job title (+ description). |
| `department` | `string` | no | `null` | Client-side substring filter on department. |
| `location` | `string` | no | `null` | Client-side substring filter on city/state/country. |
| `maxResultsPerCompany` | `integer` | no | `100` | Cap on rows emitted per company, applied after filtering. |
| `includeDescription` | `boolean` | no | `false` | Populate the full HTML job description for every emitted row. |
| `proxyConfiguration` | `object` | no | `{"useApifyProxy": true}` | Apify Proxy configuration. |

#### Example input

```json
{
  "companies": ["remotebase"],
  "maxResultsPerCompany": 25,
  "includeDescription": true,
  "proxyConfiguration": { "useApifyProxy": true }
}
```

### 📤 Output

One dataset item per job posting.

| Field | Type | Notes |
|---|---|---|
| `job_id` | `string` | Stable Workable job shortcode. |
| `title` | `string` | Job title. |
| `company` | `string` | Normalized subdomain you supplied. |
| `company_name` | `string \| null` | Employer display name. |
| `department` | `string \| null` | Job department. |
| `location_city` | `string \| null` | Posting city. |
| `location_country` | `string \| null` | Posting country. |
| `is_remote` | `boolean` | Whether the posting is remote-eligible. |
| `employment_type` | `string \| null` | Employment type, when Workable reports one. |
| `url` | `string` | Canonical posting URL. |
| `posted_date` | `string` | Date the posting went live (`YYYY-MM-DD`). |
| `description_html` | `string \| null` | Full HTML description when `includeDescription` is on. |
| `scraped_at` | `string` | ISO-8601 timestamp of row construction. |

#### Example output

```json
{
  "job_id": "6E7795E82F",
  "title": "AI Engineer",
  "company": "remotebase",
  "company_name": "Remotebase",
  "department": "Core",
  "location_city": null,
  "location_country": "Pakistan",
  "is_remote": true,
  "employment_type": null,
  "url": "https://apply.workable.com/j/6E7795E82F",
  "posted_date": "2026-04-01",
  "description_html": null,
  "scraped_at": "2026-07-23T15:00:00Z"
}
```

### 💰 Pricing

Pay-per-event (PPE) — you're only charged for what actually runs and lands.

| Event | Price | Trigger |
|---|---|---|
| Actor start | $0.005 | Once per run |
| Result | $0.0015 | Per job posting written to the dataset |

**$1.50 / 1 000 results.** No data → no charge (only the small actor-start warm-up fee).

### 🚧 Limitations

- One row per posting — a job listed at multiple `locations[]` entries emits only the first location.
- Only currently-live postings; no historical/removed posting archive.
- `searchQuery` / `department` / `location` filters run client-side after the fetch — Workable's own server-side facets don't work reliably for this endpoint.

### ❓ FAQ

**Do I need a Workable API key?** No — this Actor uses Workable's own public, unauthenticated widget endpoint.

**What if a subdomain doesn't exist?** It's logged as a warning and treated as zero jobs; the run still completes and other companies keep processing.

**Can I filter server-side to save money?** No — Workable's facet params don't reliably filter results, so all filtering happens client-side after the one fetch per company. You're still only charged per emitted row.

### 🙌 Your feedback

Found a field that's missing, a board that behaves differently, or have a feature request? Reach out via [DevilScrapes on Apify](https://apify.com/DevilScrapes) — we ship fixes fast.

***

### Changelog

See [CHANGELOG.md](CHANGELOG.md).

# Actor input Schema

## `companies` (type: `array`):

Workable subdomain, e.g. "remotebase" (the employer identifier in apply.workable.com/{subdomain}/ or {subdomain}.workable.com/), or a full board URL in either form — the subdomain is regex-extracted automatically. A plain string with no "workable.com" substring is used literally, unchanged.

## `searchQuery` (type: `string`):

Client-side substring match on job title (and description when Include description is on). Never sent to Workable as a query param — Workable's server-side search facet is confirmed non-functional. Leave empty to skip.

## `department` (type: `string`):

Client-side substring match on job department. Never sent to Workable as a query param. Leave empty to skip.

## `location` (type: `string`):

Client-side substring match on job city/state/country. Never sent to Workable as a query param. Leave empty to skip.

## `maxResultsPerCompany` (type: `integer`):

Cap job postings emitted per companies entry, applied after client-side filtering.

## `includeDescription` (type: `boolean`):

When enabled, populate the full HTML job description for every emitted row. The description is already fetched in the one call made per company either way — this only gates whether it's emitted.

## `proxyConfiguration` (type: `object`):

Apify Proxy configuration. No anti-bot behaviour has been observed on Workable's public widget API, so the standard (non-residential) group is enough.

## Actor input object example

```json
{
  "companies": [
    "remotebase"
  ],
  "maxResultsPerCompany": 100,
  "includeDescription": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

All dataset items as JSON.

## `datasetItemsCsv` (type: `string`):

Same data exported to CSV.

## `datasetView` (type: `string`):

Open the run dataset in the Console.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "companies": [
        "remotebase"
    ],
    "maxResultsPerCompany": 100,
    "includeDescription": false,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("devilscrapes/workable-jobs-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "companies": ["remotebase"],
    "maxResultsPerCompany": 100,
    "includeDescription": False,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("devilscrapes/workable-jobs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "companies": [
    "remotebase"
  ],
  "maxResultsPerCompany": 100,
  "includeDescription": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call devilscrapes/workable-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=devilscrapes/workable-jobs-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/ymx8DnhU0vojbDEA3/builds/ICrxlhyeI4riorCp2/openapi.json
