# ATS Job Scraper Pro: Workday, Personio, Greenhouse, Lever (`hydrocore/ats-job-board-scraper-pro`) Actor

Scrape open jobs from Workday, Personio, Greenhouse, Lever, Ashby and Workable boards in one run. Track new and closed jobs per company between runs. Normalized fields, salary ranges, remote flag, filters. Uses public job feeds and obeys robots.txt.

- **URL**: https://apify.com/hydrocore/ats-job-board-scraper-pro.md
- **Developed by:** [Hydro Core](https://apify.com/hydrocore) (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 $0.90 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## ATS Job Scraper Pro: Workday, Personio, Greenhouse, Lever, Ashby & Workable + change tracking

**Get every open job from the companies you care about, in one clean spreadsheet, straight from their own careers pages. Run it again tomorrow and get only what changed: the new jobs and the jobs that were taken down.**

It works with companies that use **Workday, Personio, Greenhouse, Lever, Ashby or Workable** for their careers pages, which covers many tech, scale-up and enterprise employers. You don't need to code, log in anywhere or set up proxies. Paste the careers-page links, click **Start**, and download the results as Excel, CSV or JSON.

### 🎁 What you get

- 📋 **One row per job, same columns for every company**: title, department, location(s), remote yes/no, salary range (when published), seniority, requisition number, posted date, apply link and the full job description.
- 🔁 **New and closed jobs, per company**: switch on change tracking and each run shows which jobs appeared and which disappeared since the last run, with the date each one was first seen and when it closed.
- 💰 **Salary ranges**: taken from the ATS where it has them, or read from the pay range written in the job description (common in US postings).
- 🎯 **Filters**: keep only jobs with certain words in the title, in certain locations, remote only, certain departments or posted in the last N days. Jobs you filter out are not charged.
- 📊 **A run summary**: for every company, how many jobs are on the board, how many matched, how many are new and how many closed.
- 🤝 **Polite and low-risk**: it reads the same public job feed the careers page itself loads, follows each site's robots.txt and spaces out requests. No login and no headless browser.

**Good for:** recruiters and agencies watching target companies, sales teams using hiring as a buying signal, job boards and newsletters, investors and analysts tracking hiring, and AI agents that need clean job data.

### 🚀 Quick start (2 minutes)

1. **Find the careers pages.** Open a company's careers site and click any job. Copy the address of the job board, for example:

   - Workday: `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`
   - Personio: `https://ottonova.jobs.personio.de`
   - Greenhouse: `https://job-boards.greenhouse.io/airbnb`
   - Lever, Ashby, Workable: `https://jobs.lever.co/spotify`, `https://jobs.ashbyhq.com/ramp`, `https://apply.workable.com/huggingface`

   For everything except Workday, the company's short name (like `ramp`) usually works too.
2. **Paste them** into **Companies**, one per line.
3. *(Optional)* Add **Title keywords** (e.g. `engineer`), **Locations** (e.g. `Berlin`, `Remote`) or tick **Remote jobs only**.
4. Click **Start**. When the run finishes, open the **Output** tab and click **Export** to download Excel, CSV or JSON.

**To get only new and closed jobs each day:**

5. Set **Output mode** to *New + closed jobs* and give the watchlist a name in **Tracking store name** (e.g. `my-watchlist`).
6. Click **Save as a new task**, then add a **Schedule** (e.g. every morning). Connect Slack, Google Sheets, email or a webhook under **Integrations** if you want each change sent to you.

The first run remembers every company's current jobs (and lists them, unless you set **First run of a company** to *Save baseline only*). From then on you only see changes.

### 🧩 Supported job boards

| ATS | Example board link | Short form you can paste instead |
| --- | --- | --- |
| Workday | `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite` | `workday:nvidia.wd5/NVIDIAExternalCareerSite` |
| Personio | `https://ottonova.jobs.personio.de` | `personio:ottonova` |
| Greenhouse | `https://job-boards.greenhouse.io/airbnb` | `greenhouse:airbnb` |
| Lever | `https://jobs.lever.co/spotify` | `lever:spotify` |
| Ashby | `https://jobs.ashbyhq.com/ramp` | `ashby:ramp` |
| Workable | `https://apply.workable.com/huggingface` | `workable:huggingface` |
| Not sure? | a company's own careers page, or just its name | the Actor tries Greenhouse, Lever, Ashby, Workable and Personio |

**Not supported, on purpose:** SmartRecruiters (its API robots.txt disallows general crawlers), Recruitee (its API terms forbid bulk copying or scraping) and BambooHR (its developer terms forbid scraping without written consent).

### 📤 Example output

One real Workday job (description shortened):

```json
{
    "ats": "workday",
    "company": "Workday",
    "companySlug": "workday.wd5/Workday",
    "jobId": "Principal-Full-Stack-Engineer---Agent-Platform_JR-0109720-1",
    "title": "Principal Full Stack Engineer - Agent Platform",
    "employmentType": "Full Time",
    "workplaceType": "Flex",
    "location": "USA, GA, Atlanta",
    "locations": ["USA, GA, Atlanta", "USA, CO, Boulder", "USA, CA, Pleasanton"],
    "country": "United States of America",
    "requisitionId": "JR-0109720",
    "salaryMin": 182400,
    "salaryMax": 273600,
    "salaryCurrency": "USD",
    "salaryText": "$182,400 - $273,600 USD",
    "salarySource": "description",
    "url": "https://workday.wd5.myworkdayjobs.com/Workday/job/USA-GA-Atlanta/Principal-Full-Stack-Engineer---Agent-Platform_JR-0109720-1",
    "publishedAt": "2026-11-02T00:00:00.000Z",
    "descriptionText": "Your work days are brighter here. …",
    "changeType": "new",
    "firstSeenAt": "2026-11-03T00:52:19.328Z",
    "closedAt": null
}
```

In Excel or CSV, each of these becomes a column. A **closed** job row keeps the title, link, location and department it had, with `changeType: "closed"` and the `closedAt` date.

<details>
<summary>All output fields</summary>

| Field | What it is |
| --- | --- |
| `ats` | `workday`, `personio`, `greenhouse`, `lever`, `ashby` or `workable` |
| `company`, `companySlug`, `jobId` | Company, which board, and the job's ID on its ATS |
| `title`, `department`, `team`, `employmentType`, `seniority` | Job basics (`seniority` comes from Personio) |
| `workplaceType`, `isRemote`, `location`, `locations`, `country` | Where the job is |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryInterval`, `salaryText`, `salarySource` | Pay. `salarySource` is `ats` (from the ATS) or `description` (read from the text, best effort) |
| `requisitionId` | Workday requisition number |
| `url`, `applyUrl`, `publishedAt`, `updatedAt` | Links and dates |
| `descriptionText`, `descriptionHtml` | Full description as plain text and/or HTML |
| `changeType`, `firstSeenAt`, `closedAt` | Change tracking only: `new` or `closed`, and when |
| `sourceInput`, `scrapedAt` | Which line of your input produced the row, and when |

Fields a company's ATS doesn't provide are empty (`null`). The `RUN_SUMMARY` record in the run's Storage tab lists every company with its status and counts.

</details>

### 💵 Pricing

You pay only for results. Apify platform costs are included in these prices.

| What you're charged for | Free plan | Bronze | Silver | Gold and up |
| --- | --- | --- | --- | --- |
| **Job** (each open job saved, or each *new* job in tracking modes) | $0.0015 | $0.0013 | $0.0011 | $0.0009 |
| **Closed job** (each job reported as taken down, tracking mode) | $0.0003 | $0.0003 | $0.0002 | $0.0002 |
| **Board page** (each company checked; Workday: each page of 20 jobs the Actor actually reads) | $0.0002 | $0.0002 | $0.0002 | $0.0002 |
| **Start** (once per run) | $0.001 | $0.001 | $0.001 | $0.001 |

**What that means in dollars (Free plan prices):**

| You run | Cost |
| --- | --- |
| 1,000 jobs from 10 Greenhouse, Lever or Ashby companies | **about $1.50** |
| The 5 newest jobs from a big Workday board (e.g. NVIDIA), *All open jobs* mode | **about $0.009** (5 jobs + 1 page + start) |
| A full export of a 2,000-job Workday board (100 pages read) | **about $3.02** |
| A daily check of 50 companies when nothing changed | **$0.011 per day** (about $0.33 a month) |
| A daily check of 20 companies that finds 10 new and 5 closed jobs | **about $0.02 per day** (about $0.65 a month) |

You're never charged for companies that can't be found, for errors, for jobs your filters remove, or for jobs you already received in tracking mode. Set a maximum cost per run in the run options and the Actor stops before going over it.

### 🏆 How it compares (checked on the Apify Store on Nov 2, 2026)

| | This Actor | factpipe/ats-jobs-feed | memo23/career-site-ats-jobs-api | viridian_layout_ea2/company-career-site-jobs | openclawai/career-site-ats-jobs-scraper |
| --- | --- | --- | --- | --- | --- |
| Data source | live board feeds | pre-built index (nightly), live mode for board URLs | live | live | live |
| Workday · Personio | ✅ · ✅ | ✅ · ✅ | ✅ · ✅ | ✅ · — | ✅ · ✅ |
| Closed jobs as dataset rows | ✅ | — (only new jobs) | — (new / updated / unchanged) | — (only new jobs) | — |
| Price per job (Free plan) | $0.0015 | $0.003 | $0.004 | $0.0015 | $0.0005 |
| Start fee | $0.001 + $0.0002 per board page | none | $0.03 | $0.00005 | none |

The others cover more ATS platforms (memo23 lists 116 and openclawai lists 60+), and some have a lower price per job. Other Store actors also report closed jobs in other ways, for example as a separate notice or record. Choose this Actor if your companies use the six ATSs above and you want new and closed jobs as rows in the same dataset, with one set of columns.

### ❓ FAQ

**Do I need to know how to code?**
No. Paste links, click Start and export to Excel or CSV. Developers can also use the Apify API.

**Which companies does it work for?**
Any company whose careers page runs on Workday, Personio, Greenhouse, Lever, Ashby or Workable. Click a job on their careers site: if the address contains `myworkdayjobs.com`, `jobs.personio`, `greenhouse.io`, `lever.co`, `ashbyhq.com` or `workable.com`, it's supported.

**How do I find a Workday link?**
Open the company's careers site and click any job. Copy the address up to and including the part after `myworkdayjobs.com/`, e.g. `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`. A language part such as `/en-US/` is fine. Workday companies need the full link; the short name alone isn't enough.

**How does "new and closed jobs" work?**
The Actor remembers each company's jobs in a storage area in your own Apify account (named by **Tracking store name**). Each run compares the board with that memory. *New* means the job wasn't there last time. *Closed* means it has disappeared from the board. A job that just stopped matching your filters is not counted as closed. Use a different store name for each watchlist or filter set.

**How much will it cost me?**
See the dollar examples under Pricing. A daily check of 50 companies with no changes costs about one cent. Apify's Free plan includes $5 of monthly credit, which you can spend on this Actor.

**Can I get results automatically every day?**
Yes. Save your input as a Task, add a Schedule, and optionally connect Slack, Google Sheets, email or a webhook under Integrations.

**Why does a big Workday company stop at 2,000 jobs?**
Workday's own search shows at most 2,000 results per board. Add keyword or location filters to narrow it down. For boards over that limit, closed-job detection is switched off, so you never get false "closed" rows, and the run summary tells you.

**Personio descriptions are in German. Can I get English?**
Yes. Set **Personio feed language**. If a job has no text in that language, you get the German text rather than an empty field.

**Why are Workday runs slower, and how many Workday pages do I pay for?**
Workday lists 20 jobs at a time and each description is a separate request, which the Actor spaces out politely. In our tests, 363 Workday jobs with descriptions took about 2.3 minutes. Turn off **Include job description** for faster runs. You pay only for the list pages the Actor actually reads (20 jobs each). In *All open jobs* mode with **Max jobs per company** or **Max jobs in total** set, it stops reading as soon as it has enough matching jobs, so asking for 5 jobs usually reads just 1 page. In the change-tracking modes it has to read the whole board (up to Workday's 2,000-job limit) to spot new and closed jobs, so a 2,000-job board reads 100 pages ($0.02).

**What if a company isn't found?**
The run continues. That company shows as `not_found` in the run summary and costs nothing. Paste its direct job-board link instead.

**Is this legal?**
The Actor reads only job postings that employers publish for candidates and search engines. It uses public feeds without logging in, bypasses no protection, follows robots.txt and collects no personal candidate data. We left out ATS vendors whose terms or robots.txt forbid this kind of access. Job descriptions are the employer's content, so check that your use (for example republishing full text) is allowed. This isn't legal advice.

**Can I use it with Zapier, Make, n8n or an AI agent?**
Yes, through Apify's integrations, the Apify API, or the Apify MCP server. Need another ATS? Open an issue on the Issues tab. We add sources only when their terms allow it.

# Actor input Schema

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

One company per line. Accepts: a job-board URL — Workday (https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite), Personio (https://ottonova.jobs.personio.de), Greenhouse (https://job-boards.greenhouse.io/airbnb), Lever (https://jobs.lever.co/spotify), Ashby (https://jobs.ashbyhq.com/ramp), Workable (https://apply.workable.com/huggingface); an ats:slug (workday:nvidia.wd5/NVIDIAExternalCareerSite, personio:ottonova, greenhouse:airbnb, lever:spotify, ashby:ramp, workable:huggingface); a bare slug (ramp), auto-detected across Greenhouse, Lever, Ashby, Workable and Personio; or a company careers-page URL, where we look for an embedded board. Workday boards need the full board URL or workday:tenant.wdN/Site.

## `keywords` (type: `array`):

Keep jobs whose title contains ANY of these words (case-insensitive). Leave empty to keep all jobs.

## `excludeKeywords` (type: `array`):

Drop jobs whose title contains any of these words, e.g. intern, senior, manager.

## `searchInDescription` (type: `boolean`):

If on, 'Title keywords' also match text in the job description (e.g. 'Python', 'Snowflake'). Needs 'Include description'. On Workday this fetches the detail page of every job on the board, so runs are slower.

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

Keep jobs whose location contains ANY of these strings, e.g. 'New York', 'London', 'Germany', 'Remote'.

## `remoteOnly` (type: `boolean`):

Keep only jobs marked remote by the ATS or whose location says Remote/Anywhere.

## `departments` (type: `array`):

Keep jobs whose department or team contains ANY of these strings, e.g. Engineering, Sales, Marketing.

## `postedWithinDays` (type: `integer`):

Only jobs published in the last N days (0 = any date). Greenhouse uses 'first published', Lever 'created', Ashby 'published', Workable 'published on', Personio 'created', Workday the posting start date (or 'Posted N days ago' when no details are fetched; '30+ days ago' counts as unknown and is dropped).

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

Include the full job description. On Workday each description is one extra request per saved job. Turn off for faster, lighter runs when you only need titles, locations and links.

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

Plain text (best for AI/LLM pipelines and spreadsheets), original HTML, or both.

## `maxJobsPerCompany` (type: `integer`):

Newest jobs first. 0 = no limit.

## `maxJobs` (type: `integer`):

Stop after saving this many items (jobs, plus closed-job records in change tracking) across all companies. 0 = no limit (your 'Max cost per run' still applies).

## `personioLanguage` (type: `string`):

Language requested from Personio XML feeds. Jobs that have no text in this language fall back to the German feed.

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

All jobs: every matching open job, every run. Only new jobs: jobs that were not on the board at the previous run. New + closed jobs: also one record per job that disappeared from the board since the previous run (changeType = closed).

## `firstRunOutput` (type: `string`):

What to output the first time a company is tracked: all matching jobs (as new), or nothing (only save a baseline so later runs report changes). Only used in 'Only new' and 'New + closed' modes.

## `includeClosedJobs` (type: `boolean`):

In 'New + closed jobs' mode, output a record for each job that was removed from the board since the previous run. Closed detection is off for Workday boards with more than 2,000 jobs (Workday's search limit).

## `stateStoreName` (type: `string`):

Name of the key-value store that remembers each board's jobs between runs. Use a different name per saved task or watchlist. Filters are applied before saving, so changing filters changes what counts as new.

## Actor input object example

```json
{
  "companies": [
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://ottonova.jobs.personio.de",
    "https://job-boards.greenhouse.io/airbnb",
    "lever:spotify",
    "ramp",
    "https://apply.workable.com/huggingface"
  ],
  "keywords": [],
  "excludeKeywords": [],
  "searchInDescription": false,
  "locations": [],
  "remoteOnly": false,
  "departments": [],
  "postedWithinDays": 0,
  "includeDescription": true,
  "descriptionFormat": "text",
  "maxJobsPerCompany": 20,
  "maxJobs": 0,
  "personioLanguage": "en",
  "mode": "all",
  "firstRunOutput": "all",
  "includeClosedJobs": true,
  "stateStoreName": "ats-job-tracker"
}
```

# Actor output Schema

## `jobs` (type: `string`):

No description

## `summary` (type: `string`):

No description

# 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": [
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
        "https://ottonova.jobs.personio.de",
        "https://job-boards.greenhouse.io/airbnb",
        "lever:spotify",
        "ramp",
        "https://apply.workable.com/huggingface"
    ],
    "maxJobsPerCompany": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("hydrocore/ats-job-board-scraper-pro").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": [
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
        "https://ottonova.jobs.personio.de",
        "https://job-boards.greenhouse.io/airbnb",
        "lever:spotify",
        "ramp",
        "https://apply.workable.com/huggingface",
    ],
    "maxJobsPerCompany": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("hydrocore/ats-job-board-scraper-pro").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 '{
  "companies": [
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://ottonova.jobs.personio.de",
    "https://job-boards.greenhouse.io/airbnb",
    "lever:spotify",
    "ramp",
    "https://apply.workable.com/huggingface"
  ],
  "maxJobsPerCompany": 20
}' |
apify call hydrocore/ats-job-board-scraper-pro --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hydrocore/ats-job-board-scraper-pro"
        }
    }
}
```

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/cH3jN0XbtrVRMch9d/builds/ujkWJ6lTw0A2AhtYD/openapi.json
