# ATS Job Board Scraper: Greenhouse, Lever, Ashby, Workable (`hydrocore/ats-job-board-scraper`) Actor

Scrape every open job from Greenhouse, Lever, Ashby & Workable job boards in one run. Auto-detects the ATS from a company name or careers page. Normalized fields, salaries, remote flag, filters and a pay-only-for-new-jobs monitor mode.

- **URL**: https://apify.com/hydrocore/ats-job-board-scraper.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.60 / 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 Board Scraper: Greenhouse, Lever, Ashby & Workable Jobs API

**Get every open job from any company that hires through Greenhouse, Lever, Ashby or Workable, in one run, in one clean format, for about $1 per 1,000 jobs.**

Paste company names, careers-page URLs or job-board links. The Actor detects which applicant tracking system (ATS) each company uses, reads the employer's **official public job-board feed**, and returns the same normalized fields for every job: title, department, location, remote flag, salary range, posted date, apply link and full description. Export to JSON, CSV or Excel, or pull it through the API.

> ⚡ In our test run on Apify, the default input (Airbnb, Spotify, Ramp and Hugging Face) returned **472 jobs in about 5 seconds** at 256 MB memory.

### ✨ What does ATS Job Board Scraper do?

- 🏢 **Four ATS platforms, one schema**: Greenhouse, Lever, Ashby and Workable all map to the same fields, so you don't have to merge four different formats.
- 🔎 **Auto-detects the ATS**: give it `ramp`, `lever:spotify` or `https://www.figma.com/careers/`, and it finds the right job board.
- 💰 **Salary data**: structured pay ranges when the ATS provides them, plus pay-transparency ranges parsed from the description (for example `$212,000—$265,000 USD`).
- 🎯 **Filters**: title keywords, excluded words, locations, remote only, departments, posted within the last N days, max jobs per company and in total. Filtered-out jobs aren't saved, so they aren't charged.
- 🔔 **Monitoring mode (only new jobs)**: schedule it, and each run returns only postings you haven't received before. You pay only for new jobs.
- 🤖 **AI-ready**: plain-text descriptions (or HTML, or both) that go straight into LLM, RAG and enrichment pipelines.

### 🧩 Supported job boards

| ATS | Board URL format | Prefixed input |
| --- | --- | --- |
| Greenhouse | `https://job-boards.greenhouse.io/airbnb` or `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` |
| **Auto-detect** | a bare slug (`ramp`) or a company careers page (`https://linear.app/careers`) | — |

### 👥 Who is it for?

- **Recruiters and staffing agencies**: see which target companies are hiring in your niche and when new roles open.
- **Job boards and newsletters**: fill a niche board (remote, AI, climate, fintech…) straight from employer sources.
- **Sales and lead generation**: hiring is a buying signal. *"Company X just opened 5 data-engineering roles"* → pitch your data tool.
- **Market research and investors**: track hiring volume, departments, locations and pay ranges across companies.
- **AI agents and developers**: one call returns structured job data, usable as a tool through the Apify API or MCP server.

### ⚙️ How it works

1. **Resolve**: each input is matched to a job board. URLs and `ats:slug` inputs are used directly. Bare slugs are checked against all four ATSs (when a slug is found on more than one, the board with the most jobs wins and the others are listed in the run summary). Careers pages are scanned for an embedded Greenhouse, Lever, Ashby or Workable board.
2. **Fetch**: the Actor reads each board's **public JSON job feed**, the same feed the employer's own careers page uses. There's no headless browser, no proxies and no login, which is why runs take seconds and cost little.
3. **Normalize and filter**: every job is mapped to one schema, salaries are extracted, and your filters are applied.
4. **Save**: matching jobs go to the dataset. A per-company `RUN_SUMMARY` (jobs on the board, matched, saved, plus any input that couldn't be resolved) is saved to the key-value store.

The Actor respects robots.txt (including `Crawl-delay`) and spaces requests to each host.

### 🏆 Why use this instead of a single-ATS scraper?

- **One tool, four ATSs**: a mixed list of companies works in a single run with a single output format.
- **You don't need to know the ATS**: auto-detection handles company names and careers pages.
- **Normalized schema**: the same field names for remote flag, location, salary and dates on every ATS.
- **Built-in monitoring**: the *Only new jobs* mode replaces your own deduplication logic.
- **Fast and light**: public JSON feeds, no browser. In our test runs: 472 jobs in about 5 s; 35 jobs from 7 boards (with descriptions) in about 5 s.
- **Simple, tiered pricing**: about $1.00 per 1,000 jobs, cheaper on higher Apify plans (see below).

### 📥 Input example

```json
{
    "companies": [
        "https://job-boards.greenhouse.io/airbnb",
        "lever:spotify",
        "ramp",
        "https://apply.workable.com/huggingface",
        "https://www.figma.com/careers/"
    ],
    "keywords": ["engineer", "data"],
    "excludeKeywords": ["intern"],
    "locations": ["Remote", "New York", "London"],
    "postedWithinDays": 30,
    "includeDescription": true,
    "descriptionFormat": "text",
    "onlyNewJobs": false
}
```

Only `companies` is required. Everything else is optional.

### 📤 Output example

Each job is one dataset item (a real Ashby job, description shortened):

```json
{
    "ats": "ashby",
    "company": "Linear",
    "companySlug": "Linear",
    "jobId": "f272fc4a-91a7-4dcf-9cb5-3600ebaf14e1",
    "title": "Analytics Engineer",
    "department": "Operations",
    "team": "Operations",
    "employmentType": "FullTime",
    "workplaceType": "Remote",
    "isRemote": true,
    "location": "North America",
    "locations": ["North America"],
    "country": "United States",
    "salaryMin": 192000,
    "salaryMax": 208000,
    "salaryCurrency": "USD",
    "salaryInterval": "1 YEAR",
    "salaryText": "$192K - $208K",
    "salarySource": "ats",
    "url": "https://jobs.ashbyhq.com/Linear/f272fc4a-91a7-4dcf-9cb5-3600ebaf14e1",
    "applyUrl": "https://jobs.ashbyhq.com/Linear/f272fc4a-91a7-4dcf-9cb5-3600ebaf14e1/application",
    "publishedAt": "2026-08-11T00:36:47.459Z",
    "updatedAt": null,
    "descriptionText": "At Linear, we're building the product development system for teams and agents…",
    "descriptionHtml": null,
    "sourceInput": "https://linear.app/careers",
    "scrapedAt": "2026-11-02T20:27:25.790Z"
}
```

| Field | Description |
| --- | --- |
| `ats` | `greenhouse`, `lever`, `ashby` or `workable` |
| `company`, `companySlug` | Company name and board slug |
| `jobId` | The job's ID on its ATS |
| `title`, `department`, `team`, `employmentType` | Job basics |
| `workplaceType`, `isRemote`, `location`, `locations`, `country` | Where the job is |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryInterval`, `salaryText`, `salarySource` | Pay range. `salarySource` is `ats` (structured) or `description` (parsed from the text, best effort) |
| `url`, `applyUrl` | Posting and application links |
| `publishedAt`, `updatedAt` | ISO 8601 dates |
| `descriptionText`, `descriptionHtml` | Full description in the format you chose |
| `sourceInput`, `scrapedAt` | Which input produced the job, and when it was scraped |

Fields an ATS doesn't provide are `null`.

### 💵 Pricing

Pay per event. You pay only for jobs saved to your dataset, and platform usage is included in the price.

| Event | Free plan | Bronze | Silver | Gold / Platinum / Diamond |
| --- | --- | --- | --- | --- |
| Job saved | $0.0010 | $0.0009 | $0.0008 | $0.0006 |
| Actor start (per run) | $0.00005 | $0.00005 | $0.00005 | $0.00005 |

That's **$1.00 per 1,000 jobs** on the Free plan, down to $0.60 per 1,000 on Gold and above. Inputs that can't be resolved, errors, jobs removed by your filters and (in monitoring mode) jobs you've already received are **never charged**. The Actor also stops at your *Max cost per run* limit.

### ❓ FAQ

**Is it legal to scrape job postings?**
The Actor reads only public job postings that employers publish on purpose so candidates, job boards and search engines can find them. It uses the ATS vendors' public job-board feeds, doesn't log in or bypass any protection, respects robots.txt, and collects no candidate or employee data. Job descriptions are the employer's content, so make sure your use (for example republishing full descriptions) fits your jurisdiction and the employer's terms. If unsure, link to `url` and show a short excerpt. This isn't legal advice.

**Which companies can I scrape?**
Any company whose public job board is hosted on Greenhouse, Lever, Ashby or Workable. Many tech companies and startups use one of these four. Workday, SmartRecruiters, iCIMS and other ATSs aren't supported yet.

**How do I find a company's slug or board URL?**
Open the company's careers page and click any job. If the link contains `greenhouse.io`, `lever.co`, `ashbyhq.com` or `workable.com`, paste that board URL. The slug is the part right after the domain (e.g. `jobs.lever.co/spotify` → `spotify`). You can also just try the company name; the Actor checks all four ATSs.

**How does monitoring mode work?**
Turn on **Only new jobs**. The Actor remembers which job IDs it delivered (in a named key-value store, set by *Monitoring memory name*) and returns only jobs that are new since the last run. The first run returns all matching jobs. Use a different memory name for each watchlist.

**How much will a run cost?**
Number of jobs saved × the price for your plan, plus $0.00005 per run. For example, 472 jobs ≈ $0.47 on the Free plan. A monitoring run that finds no new jobs costs only the start event. Use *Max jobs per company*, *Max jobs in total* or filters to control volume.

**Can I schedule it?**
Yes. Save your input as a **Task**, then add a **Schedule** in Apify Console (e.g. daily at 8 AM). Combined with *Only new jobs*, that gives you a daily new-jobs feed.

**Can I use it with the API, Zapier, Make, n8n or an AI agent?**
Yes. Run it through the Apify API or the JavaScript/Python clients, connect it with Apify's integrations (Zapier, Make, n8n, Google Sheets, Slack, webhooks), or call it as a tool from AI agents through the Apify MCP server.

**What happens if a company can't be found?**
The run continues with the other companies. The unresolved input is logged with a warning and listed in `RUN_SUMMARY` with status `not_found`. Nothing is saved for it, so it's not charged. Paste the direct board URL to fix it.

**Are there rate limits or blocking issues?**
The Actor uses public feeds, spaces requests to each host, follows `Crawl-delay`, and retries failed requests up to 3 times. It doesn't need proxies. Very large lists simply take a bit longer.

**How fresh is the data?**
Live. Every run reads the employer's current job board.

**I found a bug or need another ATS. Where do I ask?**
Open an issue on the **Issues** tab of this Actor. Requests for new ATSs are welcome.

# Actor input Schema

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

One company per line. Accepts: a job-board URL (https://job-boards.greenhouse.io/airbnb, https://jobs.lever.co/spotify, https://jobs.ashbyhq.com/ramp, https://apply.workable.com/huggingface), an ats:slug (greenhouse:airbnb, lever:spotify, ashby:ramp, workable:huggingface), a bare slug (ramp), which is auto-detected across all 4 ATSs, or a company careers-page URL (https://www.figma.com/careers/), where we look for an embedded board.

## `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. a tech stack like 'Python' or 'Snowflake'). Needs 'Include description'.

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

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

Include the full job description. 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 jobs across all companies. 0 = no limit (your 'Max cost per run' still applies).

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

Remember which jobs were already delivered and return only jobs that are new since the last run. Perfect for scheduled runs: you pay only for new postings. The first run returns all matching jobs.

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

Name of the key-value store that remembers delivered job IDs in monitoring mode. Use a different name per saved task/watchlist.

## Actor input object example

```json
{
  "companies": [
    "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": 0,
  "maxJobs": 0,
  "onlyNewJobs": false,
  "monitoringStoreName": "ats-job-monitor"
}
```

# 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://job-boards.greenhouse.io/airbnb",
        "lever:spotify",
        "ramp",
        "https://apply.workable.com/huggingface"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("hydrocore/ats-job-board-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": [
        "https://job-boards.greenhouse.io/airbnb",
        "lever:spotify",
        "ramp",
        "https://apply.workable.com/huggingface",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("hydrocore/ats-job-board-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": [
    "https://job-boards.greenhouse.io/airbnb",
    "lever:spotify",
    "ramp",
    "https://apply.workable.com/huggingface"
  ]
}' |
apify call hydrocore/ats-job-board-scraper --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"
        }
    }
}
```

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/6lrC4cmnrucWObKCt/builds/jYZS7qObKE5c4bjML/openapi.json
