# All-in-One ATS Jobs Scraper — Greenhouse, Lever, Ashby & 8 more (`wowlab/all-in-one-ats-jobs-scraper`) Actor

Open jobs from 11 ATS in one schema: Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, BambooHR, Personio, Teamtailor, Breezy HR, JazzHR. Paste company names or websites - the ATS is auto-detected. Salary ranges, filters, dedupe and new/updated/removed job alerts. No login.

- **URL**: https://apify.com/wowlab/all-in-one-ats-jobs-scraper.md
- **Developed by:** [Erwinbooze Ricardo](https://apify.com/wowlab) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Security:** Limited permissions
- **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/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

## All-in-One ATS Jobs Scraper — Greenhouse, Lever, Ashby, Workable & more

**All-in-One ATS Jobs Scraper** is a multi-ATS jobs scraper and job board API for **11 applicant tracking systems**: Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, BambooHR, Personio, Teamtailor, Breezy HR and JazzHR. Paste **company names, websites or careers URLs** - the ATS is **detected automatically** - and get every open job in **one unified schema**: title, location, remote / workplace type, department, employment type, **salary ranges**, dates, apply link and description. It reads each ATS's public job-board endpoints: **no login, no proxy, no browser**. Add **change detection** (new / updated / removed jobs) and schedule it for hiring alerts.

### What can you use it for?

- **Job boards and aggregators**: one feed and one schema across 11 ATS platforms.
- **Lead generation and sales intelligence**: find out which companies are hiring, for which teams and where - just from a list of company websites.
- **Salary benchmarking**: published pay ranges, normalized to min / max / currency / period.
- **Hiring alerts**: daily new, updated and removed jobs, pushed to Slack, email or any webhook.
- **AI agents (MCP)**: one tool that answers "what is <company> hiring for?" for most startups and tech companies, whatever ATS they use.

### Supported ATS

| ATS | Input examples | Description | Salary | Publish date |
|---|---|---|---|---|
| Greenhouse | `greenhouse:airbnb`, `boards.greenhouse.io/...`, `job-boards.greenhouse.io/...` | ✅ | structured pay transparency + text | ✅ |
| Lever | `lever:spotify`, `jobs.lever.co/...`, `jobs.eu.lever.co/...` | ✅ | structured range + text | ✅ |
| Ashby | `ashby:openai`, `jobs.ashbyhq.com/...` | ✅ | structured compensation + text | ✅ |
| Workable | `workable:huggingface`, `apply.workable.com/...` | ✅ | text | ✅ |
| SmartRecruiters | `smartrecruiters:ServiceNow`, `jobs.smartrecruiters.com/...` | ✅ (one request per job) | text | ✅ |
| Recruitee | `recruitee:bunq`, `<company>.recruitee.com` | ✅ | structured (when published) + text | ✅ |
| BambooHR | `bamboohr:<company>`, `<company>.bamboohr.com/careers` | ✅ (one request per job) | compensation field + text | ✅ |
| Personio | `personio:<company>`, `<company>.jobs.personio.de` | ✅ | text | ✅ |
| Teamtailor | `teamtailor:<company>`, `<company>.teamtailor.com` | ✅ | text | ✅ |
| Breezy HR | `breezy:<company>`, `<company>.breezy.hr` | ❌ (not in the public feed) | salary field (when set) | ✅ |
| JazzHR | `jazzhr:<company>`, `<company>.applytojob.com` | ✅ | text | ❌ (not published) |

Workday is not included in this Actor.

### Features

- **Auto-detect the ATS** from a company name (`Hugging Face`) or website (`stripe.com`): the Actor first looks for ATS links on the company's own website (home, `/careers`, `/jobs`), then tries name guesses on every supported ATS and picks the board with the most jobs. `RUN_SUMMARY` records how each company was matched (`detectedVia`) and other boards it found (`alsoFoundOn`). Use `ats:token` (e.g. `greenhouse:airbnb`) for exact control.
- **Built-in company lists** (`companyLists`): about 110 curated companies across AI, fintech, developer tools, consumer tech, B2B SaaS, robotics/defense, European tech and large employers - each verified to have open jobs on 2026-10-30.
- **Cross-company deduplication**: the same board entered twice is scraped once, duplicate job IDs are dropped, and `dedupeSimilar` (on by default) drops identical reposts across boards.
- **Unified filters**: keywords (title or description), locations, departments, remote only, posted within N days, `atsFilter` to limit platforms.
- **Change detection**: `changeType` new / updated / removed with `changedFields` and `firstSeenAt`, stored between runs in a named key-value store.
- **Robust**: a wrong or missing company does not fail the run; each company's status is in `RUN_SUMMARY`. Respects your maximum cost per run.

### Quick start

```json
{
  "companies": [
    "stripe.com",
    "Hugging Face",
    "https://jobs.lever.co/palantir",
    "ashby:openai"
  ],
  "keywords": [
    "engineer"
  ],
  "maxJobsPerCompany": 20
}
```

A curated list, limited to remote roles:

```json
{
  "companyLists": [
    "ai-companies"
  ],
  "remoteOnly": true,
  "maxJobs": 200
}
```

Daily hiring monitor (outputs only changes after the first run):

```json
{
  "companies": [
    "notion.so",
    "workable:huggingface",
    "recruitee:bunq"
  ],
  "changeDetection": true,
  "skipFirstRunOutput": true,
  "changeStoreName": "my-hiring-monitor"
}
```

### Input

| Field | Description |
|---|---|
| `companies` | Company names, websites, careers/job URLs on any supported ATS, or `ats:token`. Optional if `companyLists` is set. |
| `companyLists` | Built-in curated company lists. |
| `atsFilter` | Only use these ATS platforms (empty = all 11). |
| `autoDetect` | Detect the ATS for names/websites (default on). |
| `keywords` / `searchInDescription` | Keep jobs whose title (or description) contains any keyword. |
| `locations` / `departments` | Keep jobs whose location / department contains any of these strings. |
| `remoteOnly` / `postedWithinDays` | Remote only; published in the last N days (JazzHR jobs have no date and are kept). |
| `includeDescription` / `includeDescriptionHtml` / `includeSalary` | What to include. |
| `changeDetection`, `changeStoreName`, `includeUnchanged`, `includeRemoved`, `skipFirstRunOutput` | New / updated / removed tracking. |
| `onlyNewJobs` / `monitoringStoreName` | Simple mode: only job IDs not seen before. |
| `dedupeSimilar` | Drop identical reposts (default on). |
| `maxJobsPerCompany` / `maxJobs` | Result limits (0 = no limit). |

### Output

Every job has the same fields, whatever the ATS (fields an ATS does not publish are `null`):

```json
{
  "ats": "greenhouse",
  "jobId": "8306554",
  "title": "AirCover Specialist, Claims Experience",
  "companyName": "Airbnb",
  "companyToken": "airbnb",
  "url": "https://careers.airbnb.com/positions/8306554?gh_jid=8306554",
  "applyUrl": "https://careers.airbnb.com/positions/8306554?gh_jid=8306554",
  "location": "Canada",
  "allLocations": [
    "Canada"
  ],
  "city": null,
  "region": null,
  "country": null,
  "isRemote": true,
  "workplaceType": "Remote",
  "department": "Community Support",
  "team": null,
  "employmentType": null,
  "experienceLevel": null,
  "publishedAt": "2026-10-28T14:52:05.000Z",
  "updatedAt": "2026-10-28T14:52:05.000Z",
  "salaryMin": 58000,
  "salaryMax": 68000,
  "salaryCurrency": "CAD",
  "salaryPeriod": "YEAR",
  "salarySource": "pay_transparency",
  "salaryText": null,
  "salaryRanges": [
    {
      "min": 58000,
      "max": 68000,
      "currency": "CAD",
      "title": "Canada Annual Pay Range"
    }
  ],
  "descriptionText": "Airbnb was born in 2007 when two hosts welcomed three guests to their San Francisco home...",
  "scrapedAt": "2026-10-30T10:49:05.109Z"
}
```

`salarySource` tells you where the salary came from (the ATS's structured field, or `description` when parsed from text, with the snippet in `salaryText`). With change detection, items also get `changeType`, `changedFields` and `firstSeenAt`. Download as JSON, CSV, Excel, XML or HTML, or use the API.

`RUN_SUMMARY` (default key-value store) lists, per company: `input`, `ats`, `token`, `status` (`ok` / `not_found` / `error`), `detectedVia`, `alsoFoundOn`, `jobsOnBoard`, `jobsMatched`, `jobsOutput`, `duplicatesSkipped` and change counts.

### Job alerts and change detection (new / updated / removed)

Turn on **`changeDetection`** and schedule the Actor (for example daily in **Schedules** in Apify Console). Every run is compared with the previous run that used the same companies and filters:

- `changeType: "new"` - the job appeared since the last run.
- `changeType: "updated"` - the title, location, department, employment type, salary or description changed; `changedFields` lists which.
- `changeType: "removed"` - the job was open last time and is gone now (filled or closed). The item has `jobId`, `title`, `url`, `location`, `firstSeenAt` and `removedAt`.
- Unchanged jobs are skipped unless you turn on `includeUnchanged`. Every output job also gets `firstSeenAt`.

The snapshot lives in a named key-value store (`changeStoreName`, default `ats-jobs-changes`), so it survives between runs. Use a different store name per monitor. Turn on `skipFirstRunOutput` to save the baseline silently on the first run. Change counts per company appear in `RUN_SUMMARY`. If a result limit (`maxJobsPerCompany` / `maxJobs`) cuts the output, the jobs that were not output stay pending and come out as `new` on the next run, so nothing is missed.

```json
{ "companies": ["stripe.com"], "changeDetection": true, "skipFirstRunOutput": true, "changeStoreName": "my-hiring-monitor" }
```

Prefer something simpler? `onlyNewJobs` outputs only job IDs not seen before (no updated/removed tracking).

#### Send changes to Slack, email or a webhook

- **Slack / email:** in the Actor's **Integrations** tab, add the Slack or email integration and trigger it on *Run succeeded*. Because change-detection runs output only changes, the message is your alert.
- **Webhook (your own endpoint, Zapier "Catch Hook", n8n "Webhook" node, Make "Custom webhook"):** add an HTTP webhook for the event `ACTOR.RUN.SUCCEEDED`. The default payload contains `resource.defaultDatasetId`; fetch the changes from `https://api.apify.com/v2/datasets/<defaultDatasetId>/items?clean=true` (add `&token=...` or an `Authorization: Bearer` header) and filter by `changeType`.

### Use with AI agents (MCP)

This Actor can be called by AI agents through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp). It runs with limited permissions and pay-per-event pricing, so agents only pay for the results they get. To give Claude, Cursor, VS Code or any MCP client a dedicated tool for it, add this server URL:

```
https://mcp.apify.com?tools=wowlab/all-in-one-ats-jobs-scraper
```

The agent can then call it with plain inputs such as `{"companies": ["stripe.com"], "keywords": ["engineer"], "maxJobs": 20}`. Every input field has a description written for agents, and results come back as clean JSON with plain-text descriptions, ready for RAG, classification or summaries. Set `maxJobs` (and a max total charge in the call options) to keep agent spend predictable.

### Integrations: Make, n8n, Zapier, Google Sheets

- **Make:** use the Apify app's *Run an Actor* module with `wowlab/all-in-one-ats-jobs-scraper`, then *Get Dataset Items*.
- **n8n:** use the Apify node (*Run Actor* + *Get dataset items*), or an HTTP Request node calling `POST https://api.apify.com/v2/acts/wowlab~all-in-one-ats-jobs-scraper/run-sync-get-dataset-items`.
- **Zapier:** use the Apify app (*Run Actor* action, or *Finished Actor Run* trigger + *Fetch Dataset Items*).
- **Google Sheets, Airtable, Slack, email:** connect them in the Actor's **Integrations** tab, or export the dataset as CSV/Excel/JSON.
- **API:** `POST https://api.apify.com/v2/acts/wowlab~all-in-one-ats-jobs-scraper/run-sync-get-dataset-items` with the input as JSON body returns the jobs directly.

### Pricing

Pay per result (see the Pricing tab for the current price). No subscription, and with change detection only changed jobs (and removed-job records) are output and charged. Set `maxJobs` or a maximum cost per run to cap spend.

### Tips

- Auto-detection makes a few extra requests per name; for large recurring lists, copy the detected `ats` and `token` from `RUN_SUMMARY` and use `ats:token` entries.
- Some companies run several boards (e.g. regional ones). `alsoFoundOn` lists the other boards with jobs; add them as `ats:token` entries if you want them too.
- Name guesses can match a different company with the same name on a smaller ATS. Check `companyName` / `companyToken`, or use the company's website or careers URL instead.

### Is it legal?

This Actor reads only the **public job postings** that companies publish on purpose through each ATS's public job-board endpoints. It collects no personal data and needs no login. You are responsible for how you use the data, so check the terms of the sites you rely on.

### FAQ

**Why are some fields empty?** Each ATS publishes different data. For example Breezy HR's public feed has no description and JazzHR's has no publish date; salary is only present when the company publishes it.

**A company returns "not_found".** It may use an ATS that is not supported (for example Workday or an in-house system), or a board name different from its brand. Pass its careers page URL.

**Only need one ATS?** See our [Greenhouse Jobs Scraper](https://apify.com/wowlab/greenhouse-jobs-scraper), [Lever Jobs Scraper](https://apify.com/wowlab/lever-jobs-scraper) and [Ashby Jobs Scraper](https://apify.com/wowlab/ashby-jobs-scraper), with ATS-specific fields (e.g. Lever lists, Ashby equity flag).

# Actor input Schema

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

One entry per company. Accepts: a company name (`Hugging Face`), a website (`stripe.com`), any careers/job URL on a supported ATS (Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, BambooHR, Personio, Teamtailor, Breezy HR, JazzHR), or an explicit `ats:token` such as `greenhouse:airbnb`, `lever:spotify`, `workable:huggingface`. Names and websites are matched to the ATS automatically (careers-page links first, then name guesses on every ATS; the board with the most jobs wins). `ats:token` is fastest and most precise. Optional if you pick a company list below.

## `companyLists` (type: `array`):

Add curated companies to the run (verified to have open jobs on 2026-10-30). Values: `ai-companies` (AI labs & AI startups), `fintech` (Fintech & crypto), `developer-tools` (Developer tools, cloud & data infrastructure), `consumer-tech` (Consumer apps & marketplaces), `b2b-saas` (B2B SaaS), `robotics-autonomy-defense` (Robotics, autonomy & defense tech), `europe-tech` (European tech companies (Recruitee, Personio, Teamtailor and Lever boards)), `large-employers` (Large employers on SmartRecruiters). Lists are a convenience, not complete directories.

## `atsFilter` (type: `array`):

Only search and output these ATS platforms (applies to auto-detection and company lists). Empty = all 11. Values: `greenhouse`, `lever`, `ashby`, `workable`, `smartrecruiters`, `recruitee`, `bamboohr`, `personio`, `teamtailor`, `breezy`, `jazzhr`.

## `autoDetect` (type: `boolean`):

For entries that are a company name or a website, detect which ATS hosts the jobs. Turn off to accept only URLs and `ats:token` entries.

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

Keep only jobs whose title (or description, if 'Search keywords in description' is on) contains at least one of these words or phrases. Case-insensitive substring match. Example: \["engineer", "data scientist"]. Leave empty to keep all jobs.

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

Also match keywords against the job description, not only the title. Slower on ATSs that need one request per job for the description.

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

Keep only jobs whose location (any of its locations) contains one of these strings. Case-insensitive substring match. Example: \["London", "United States", "Remote"].

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

Keep only jobs whose department or team contains one of these strings. Example: \["Engineering", "Sales"].

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

Keep only jobs marked remote by the ATS, or whose location text says remote.

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

Keep only jobs published within this many days. 0 = no limit. Jobs whose ATS publishes no date (JazzHR) are kept.

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

Include the full job description as plain text in `descriptionText`.

## `includeDescriptionHtml` (type: `boolean`):

Also output the original description HTML in `descriptionHtml`. Makes results larger.

## `includeSalary` (type: `boolean`):

Output salary fields (`salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salarySource`) from the ATS's structured pay data when published (Greenhouse, Lever, Ashby, Recruitee, BambooHR, Breezy HR), otherwise parsed from the job text. Null when no pay is published.

## `changeDetection` (type: `boolean`):

Compare with the previous run that used the same companies and filters. Each job gets `changeType` = `new` or `updated` (with `changedFields`), and jobs that disappeared since the last run are output as `changeType` = `removed` items. Unchanged jobs are skipped unless 'Include unchanged jobs' is on. State is kept in the named key-value store below, so schedule this Actor to get a daily diff. If a result limit (`maxJobsPerCompany` / `maxJobs`) cuts the output, the jobs that were not output stay pending and are reported as `new` on the next run, so nothing is missed.

## `changeStoreName` (type: `string`):

Name of the key-value store that keeps the job snapshot for change detection. Use a different name for each independent monitor.

## `includeUnchanged` (type: `boolean`):

With change detection on: also output jobs that did not change (`changeType` = `unchanged`), so every run returns the full current list.

## `includeRemoved` (type: `boolean`):

With change detection on: output jobs that were open in the previous run but are gone now (`changeType` = `removed`, with `removedAt`).

## `skipFirstRunOutput` (type: `boolean`):

With change detection on: the first run for a company only saves the baseline and outputs nothing (no result charges); later runs output only the changes.

## `onlyNewJobs` (type: `boolean`):

Simpler alternative to change detection: output only jobs whose IDs were not seen in previous runs with this option on. Seen job IDs are stored in the named key-value store below.

## `monitoringStoreName` (type: `string`):

Name of the key-value store that keeps seen job IDs for 'Only new jobs'. Use different names for different monitors.

## `dedupeSimilar` (type: `boolean`):

Drop jobs that have the same title, location and description as a job already output in this run (e.g. one role reposted under several boards or companies). Exact duplicates of the same job ID are always removed.

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

Maximum jobs to output per company after filtering. 0 = no limit.

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

Maximum jobs to output in this run. 0 = no limit. Use this to cap cost; the run also stops at your maximum cost per run.

## Actor input object example

```json
{
  "companies": [
    "stripe.com",
    "Hugging Face",
    "https://jobs.lever.co/palantir",
    "ashby:openai"
  ],
  "companyLists": [],
  "atsFilter": [],
  "autoDetect": true,
  "keywords": [],
  "searchInDescription": false,
  "locations": [],
  "departments": [],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "includeDescription": true,
  "includeDescriptionHtml": false,
  "includeSalary": true,
  "changeDetection": false,
  "changeStoreName": "ats-jobs-changes",
  "includeUnchanged": false,
  "includeRemoved": true,
  "skipFirstRunOutput": false,
  "onlyNewJobs": false,
  "monitoringStoreName": "ats-jobs-monitor",
  "dedupeSimilar": true,
  "maxJobsPerCompany": 0,
  "maxJobs": 0
}
```

# Actor output Schema

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

All job postings in one schema: ats, company, title, location, remote/workplace type, department, employment type, salary min/max/currency/period, dates, URL, description.

## `runSummary` (type: `string`):

Per-company status: input, detected ATS and board, how it was detected, jobs on board, matched, output, duplicates skipped, change counts and errors.

# 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": [
        "stripe.com",
        "Hugging Face",
        "https://jobs.lever.co/palantir",
        "ashby:openai"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("wowlab/all-in-one-ats-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": [
        "stripe.com",
        "Hugging Face",
        "https://jobs.lever.co/palantir",
        "ashby:openai",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("wowlab/all-in-one-ats-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 '{
  "companies": [
    "stripe.com",
    "Hugging Face",
    "https://jobs.lever.co/palantir",
    "ashby:openai"
  ]
}' |
apify call wowlab/all-in-one-ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,wowlab/all-in-one-ats-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/oiWPlroWnveze55BI/builds/tGsGsf6ow9qzZD0b3/openapi.json
