# Workable Jobs Scraper - Search All Jobs & Company Boards (`yugenox/workable-jobs-scraper`) Actor

Scrape Workable job listings by keyword and location across every Workable employer, or pull whole company careers boards. Full descriptions, requirements, benefits, salary when stated, company info and apply links. Remote, job type, experience and date filters, plus only-new-jobs alerts. No login.

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

## Pricing

from $1.00 / 1,000 jobs

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/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Workable Jobs Scraper

Scrape job listings from **Workable**, the applicant-tracking system behind tens of thousands of company careers pages. It works two ways:

- **Search all Workable jobs.** Search by keyword and location across every company that hires on Workable (about 170,000 live jobs), just like the Workable job board.
- **Company boards.** Give it a list of companies and get every open job at each one.

Each row has the full job description, the requirements and benefits sections, the **salary when the posting states one**, workplace type (remote, hybrid or on-site), location, post date, the apply link and company details. You don't need a Workable account or a login.

### What you can do with it

- **Job boards and aggregators.** Pull every Workable job in a country or niche and keep your board up to date.
- **Job alerts.** Schedule a search with **Only new jobs** turned on. Each run returns only postings you haven't seen yet.
- **Recruiting and sales intelligence.** Find out which companies are hiring for which roles and where. Company websites come with each job, so you can go straight to outreach.
- **Market research.** Track hiring volume, remote share, seniority mix and advertised salaries by role and region.
- **Competitor monitoring.** Watch the careers boards of the companies you care about.

### Features

- **Keyword × location search.** Enter several keywords and several locations, and every combination gets searched in parallel. Duplicate jobs are removed.
- **Every filter the job board offers.** Filter by workplace (remote, hybrid, on-site), employment type, experience level and date posted (24 hours, 7 days or 30 days). You can pick more than one value per filter.
- **Company boards.** Accepts a careers-page name (`huggingface`), a careers link (`https://apply.workable.com/huggingface/`, `https://huggingface.workable.com`) or a company link from the Workable job board. Company rows also include **experience level, job function, industry, education** and a direct **apply link**.
- **Complete descriptions.** The description, requirements and benefits come as separate fields, in plain text, HTML or both.
- **Salary.** Workable has no salary field, but many postings state pay in the text. The scraper pulls the stated amount or range, such as `$100,000-$220,000` or `£45k – £55k per annum`, and skips figures that aren't pay (funding rounds, assets under management, bonuses, perks).
- **Only new jobs** mode for scheduled monitoring. Jobs returned in earlier runs are skipped.
- **Fast and light.** It uses plain HTTP with no browser, loads 20 jobs per request and saves results as it goes. A run that hits its time limit still finishes successfully and keeps everything collected up to that point.

### Input

| Field | What it does |
|---|---|
| **What to scrape** | `Search all Workable jobs` or `Company boards` |
| **Job titles / keywords** | One search per line. Leave empty to get every job in the chosen locations. |
| **Locations** | A city, region or country, e.g. `Toronto`, `London`, `Germany`, `United States`. Leave empty for worldwide. |
| **Workplace** | On-site / Hybrid / Remote (any of the selected) |
| **Employment type** | Full-time, Part-time, Contract, Temporary, Other |
| **Experience level** | Internship, Entry level, Associate, Mid-Senior level, Director, Executive |
| **Date posted** | Any time, last 24 hours, 7 days or 30 days |
| **Workable URLs** | Optional. Paste job-board search links (`https://jobs.workable.com/search?query=...&location=...`, filters included) or company pages; each becomes its own search or board. |
| **Companies** | For company-board mode: names or links, one per line |
| **Max jobs (total)** / **Max jobs per search** | Limits (0 = no limit) |
| **Include job description**, **Description format**, **Include company description** | Control how much text each row carries |
| **Only new jobs**, **Memory name** | Monitoring mode (see below) |

Common alternative field names are accepted too (`maxResults` / `limit` for **Max jobs**, `keyword` / `q` for keywords, `city` / `country` for locations, `jobType`, `experienceLevel`, `datePosted`, `remote: true`, `startUrls`), so an input written for another job scraper usually works as is.

#### Example: remote data jobs in Canada and the UK posted this week

```json
{
  "queries": ["data analyst", "data engineer"],
  "locations": ["Canada", "United Kingdom"],
  "workplace": ["remote"],
  "postedWithin": "7",
  "maxItems": 500
}
```

#### Example: every open job at a few companies

```json
{
  "mode": "companies",
  "companies": [
    "huggingface",
    "https://apply.workable.com/blueground/",
    "https://skroutz.workable.com"
  ]
}
```

#### Example: daily new-job alert

Save this as a task and schedule it to run daily. The first run returns everything that matches, and later runs return only jobs posted since the last run.

```json
{
  "queries": ["product manager"],
  "locations": ["Toronto"],
  "postedWithin": "7",
  "onlyNew": true
}
```

### Output

One row per job. Here is a real example (long text shortened):

```json
{
  "id": "b259c492-1385-4876-8f3c-51230ac16cbc",
  "title": "Software Engineer",
  "companyName": "Fiscal.ai",
  "companyWebsite": "https://fiscal.ai",
  "companyLogo": "https://workablehr.s3.amazonaws.com/uploads/account/logo/674170/logo",
  "companyUrl": "https://jobs.workable.com/company/upohitRDkZUqEnNiBRXZ9Z/jobs-at-fiscal.ai",
  "companyDescription": "About Fiscal.ai\n\nFiscal.ai is a leading research and data platform for capital markets...",
  "department": null,
  "employmentType": "Full-time",
  "workplace": "remote",
  "isRemote": true,
  "city": "Toronto",
  "region": "Ontario",
  "country": "Canada",
  "locations": ["Toronto, Ontario, Canada"],
  "salary": "$100,000-$220,000",
  "postedAt": "2026-09-21T16:20:41.327Z",
  "postedDaysAgo": 2,
  "language": "en",
  "url": "https://jobs.workable.com/view/o2n3ZT78DxDntT9TSkXPfL/remote-software-engineer-in-toronto-at-fiscal.ai",
  "applyUrl": "https://jobs.workable.com/view/o2n3ZT78DxDntT9TSkXPfL/remote-software-engineer-in-toronto-at-fiscal.ai",
  "description": "Job Title: Software Engineer: Quality (Terminal)\nSalary: $100,000-$220,000 + equity options...",
  "requirements": "• You write good code, fast, and you fix what you find...",
  "benefits": "• Competitive salary\n• Equity options\n• Unlimited vacation...",
  "experienceLevel": null,
  "source": "search",
  "searchQuery": "software engineer",
  "searchLocation": null,
  "scrapedAt": "2026-09-24T07:42:11.244Z"
}
```

Rows from company boards also fill in `experienceLevel`, `jobFunction`, `industry`, `education` and `shortcode`, and their `applyUrl` points straight to the application form, for example `https://apply.workable.com/j/F88446C814/apply`.

You can download the dataset as JSON, CSV, Excel, XML or HTML, or read it through the Apify API. The **Jobs** view shows the key columns and the **Descriptions** view shows the full text.

### Tips

- **Big pulls.** Workable returns 20 jobs per page, so 1,000 jobs takes about 50 requests. To collect a whole country quickly, split it into several keywords or cities. Every combination runs in parallel and duplicates are removed.
- **Keep scheduled runs small.** For alerts, combine **Only new jobs** with **Date posted = Last 7 days**. Each run then only reads recent postings.
- **Empty keywords** with a location returns every Workable job in that location. Empty keywords and no location returns the newest jobs worldwide, up to your **Max jobs** limit.

### FAQ

**Do I need a Workable account?**
No. The scraper reads the same public listings anyone can browse on Workable.

**Is it legal to scrape Workable?**
This scraper collects only public job listings that employers publish on Workable for anyone to read, without logging in. Job postings are business information, but a posting's text can sometimes include the name or work email of a recruiter that the employer chose to publish. If you keep or use that personal data, handle it under the privacy laws that apply to you, such as GDPR, PIPEDA or CCPA. Use the data in line with Workable's terms and the law, and check with a lawyer if you're unsure about your use case. For background, see Apify's guide [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/)

**Does it access any private data?**
No. It reads only Workable's public job board and public company careers pages, the same pages anyone can open in a browser. It never logs in and uses no accounts or cookies, so it can't see unlisted or internal postings, applicants, candidate profiles or anything else behind a Workable login. **Only new jobs** mode stores job ids (not personal data) in your own Apify account's storage.

**Why is `salary` empty on some jobs?**
Workable doesn't have a salary field. The scraper can only extract pay that the employer wrote in the posting, and roughly 40% of postings in the US, Canada and UK include it. It leaves the field empty rather than guess.

**Why are `department`, `requirements` or `benefits` empty sometimes?**
Some employers leave those sections blank or put everything in the main description. The main description is always included.

**What's the difference between search mode and company boards?**
Search covers every Workable employer and is the way to discover jobs. Company boards give you the complete list of open jobs for companies you already know, plus a few extra fields (experience level, function, industry, education, direct application link).

**Can I scrape a company that uses Workable on its own domain, like careers.example.com?**
Yes. Open any job on that site. The apply button leads to `apply.workable.com/<name>/...`, so use that `<name>` as the company.

**How does "Only new jobs" remember what it has seen?**
It keeps a list of job ids in your Apify account's storage, one list for each search setup. Change the keywords or filters and a fresh list starts. To keep one list across changes, or to share it between tasks, set **Memory name**.

**Do I need to set up proxies?**
No. The default Apify Proxy setting works. If Workable starts limiting the default IPs, the run switches to residential proxies automatically and keeps going.

**What happens if something goes wrong mid-run?**
The run still finishes successfully with everything collected so far, and the status message says what happened (for example `Stopped at the time limit — 1,240 jobs saved`). Every request is retried on a fresh IP with backoff; a blocked datacenter IP escalates to residential; a run that hits its timeout stops early and saves what it has; a run that hits your spending limit stops at the limit. A run reports `0 jobs, no charge` rather than failing when the input cannot be read, nothing matches, Workable refuses every request or its API has moved. You only ever pay for jobs that were actually saved to the dataset — never for duplicates, already-seen jobs in **Only new jobs** mode, or errors. A `RUN_REPORT` record in the run's key-value store has the request, proxy and failure statistics.

# Actor input Schema

## `mode` (type: `string`):

<b>Search</b>: keyword + location search across every company that hires on Workable. <b>Company boards</b>: every open job at the companies you list (keywords, locations and filters below still apply).

## `queries` (type: `array`):

One search per line, e.g. "data analyst" or "react developer". Each keyword is combined with each location. Leave empty to get every job in the chosen locations.

## `locations` (type: `array`):

City, region or country, e.g. "Toronto", "London", "Germany", "United States". Leave empty for worldwide.

## `startUrls` (type: `array`):

Paste links from Workable instead of (or as well as) keywords: a job-board search page (https://jobs.workable.com/search?query=...\&location=...) runs that exact search with its filters; a company page (https://apply.workable.com/<company>/ or https://jobs.workable.com/company/...) pulls that company's board.

## `workplace` (type: `array`):

Only on-site, hybrid and/or remote jobs. Pick several to get any of them. Empty = all.

## `employmentType` (type: `array`):

Only these employment types. Empty = all.

## `experience` (type: `array`):

Only these seniority levels. Empty = all.

## `postedWithin` (type: `string`):

Only jobs posted in this window.

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

One company per line: the careers-page name ("huggingface"), a careers page link (https://apply.workable.com/huggingface/ or https://huggingface.workable.com) or a company link from jobs.workable.com (https://jobs.workable.com/company/.../jobs-at-...).

## `maxItems` (type: `integer`):

Stop after this many jobs across all searches. 0 = no limit.

## `maxItemsPerSearch` (type: `integer`):

Cap for each keyword x location (or company) combination. 0 = no limit.

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

Save the full description, requirements and benefits. Turn off for smaller, faster exports (salary is still extracted).

## `descriptionFormat` (type: `string`):

Plain text, the original HTML, or both.

## `includeCompanyDetails` (type: `boolean`):

Add the employer's "about us" text to every job. Company name, website, logo and board link are always included.

## `onlyNew` (type: `boolean`):

Remember every job this search has returned before and skip it next time, so scheduled runs return (and charge for) only new postings. The memory is kept per search setup (or per "Memory name" below).

## `monitorKey` (type: `string`):

Give the only-new memory a name to share it between tasks or to keep it when you change the search. Letters, numbers and dashes.

## `maxConcurrency` (type: `integer`):

How many searches / company boards run at once. Each runs page by page on its own IP.

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

Apify Proxy (datacenter) works well and is cheapest. If Workable starts limiting datacenter IPs the run switches to residential proxies by itself.

## Actor input object example

```json
{
  "mode": "search",
  "queries": [
    "software engineer"
  ],
  "postedWithin": "any",
  "maxItems": 50,
  "maxItemsPerSearch": 0,
  "includeDescription": true,
  "descriptionFormat": "text",
  "includeCompanyDetails": true,
  "onlyNew": false,
  "maxConcurrency": 10,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `results` (type: `string`):

All scraped job postings.

## `run` (type: `string`):

Status and statistics for this run.

# 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 = {
    "queries": [
        "software engineer"
    ],
    "maxItems": 50,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("yugenox/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 = {
    "queries": ["software engineer"],
    "maxItems": 50,
    "proxyConfiguration": { "useApifyProxy": True },
}

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

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

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

```

## CLI example

```bash
echo '{
  "queries": [
    "software engineer"
  ],
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call yugenox/workable-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yugenox/workable-jobs-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/nsuaDloWRZrDa662M/builds/JauQpocp8NBLQOuCN/openapi.json
