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

Pull every open job from a company's official ATS job board (Greenhouse, Lever, Ashby, Workable) and get one normalised, deduplicated schema across all of them.

- **URL**: https://apify.com/thedeadpoet/ats-job-board-scraper.md
- **Developed by:** [Pablo D](https://apify.com/thedeadpoet) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 job listing returneds

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?

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

## ATS Job Board Scraper — Greenhouse, Lever, Ashby & Workable in one schema

Point this Actor at a list of company career pages and get back **every open role**, from four different applicant tracking systems, in **one normalised schema**. No API keys, no proxies, no login.

Most job scrapers on the Store cover a single ATS, so tracking 200 companies means running four different Actors and reconciling four different field names. This one takes a mixed list and does the reconciling for you.

### What it does

| | |
|---|---|
| **Input** | A list of company job boards — a careers URL, or `provider:token` |
| **Output** | One dataset row per open job, identical fields for every ATS |
| **Sources** | Greenhouse, Lever, Ashby, Workable — official public job-board APIs |
| **Speed** | ~1 request per company; a 50-company list finishes in about a minute |

### Use cases

- **Recruiting / talent intelligence** — track hiring at a portfolio of companies and see which teams are growing.
- **Job boards and newsletters** — build a niche board (e.g. climate tech, Berlin, remote design) from the source of truth rather than a re-scraped aggregator.
- **Sales & market research** — job postings are a leading indicator: a company hiring five Salesforce admins is buying Salesforce.
- **Competitive monitoring** — schedule a daily run and diff the dataset to get new-role alerts.

### Input

Pass the boards in whatever form you have them. All of these work:

```json
{
  "companies": [
    "https://job-boards.greenhouse.io/stripe",
    "https://jobs.lever.co/palantir",
    "https://jobs.ashbyhq.com/ramp",
    "https://apply.workable.com/blueground/",
    "greenhouse:figma",
    "lever:leverdemo",
    "ashby:ramp"
  ],
  "includeDescription": true,
  "remoteOnly": false,
  "titleKeywords": ["engineer", "data"],
  "postedWithinDays": 30,
  "maxJobsTotal": 1000,
  "requestDelaySeconds": 1
}
```

A bare token (`stripe`) also works — it uses the **Default ATS provider** setting.

#### Where do I find a company's board token?

It is the path segment in the careers URL:

| ATS | Careers URL | Token |
|---|---|---|
| Greenhouse | `https://job-boards.greenhouse.io/**stripe**` | `stripe` |
| Lever | `https://jobs.lever.co/**palantir**` | `palantir` |
| Ashby | `https://jobs.ashbyhq.com/**ramp**` | `ramp` |
| Workable | `https://apply.workable.com/**blueground**/` | `blueground` |

If a company's careers page is on their own domain, the ATS is usually still visible in the "Apply" link.

Boards that were live when this Actor was last verified, if you want something to try it on:
`greenhouse:monzo`, `lever:leverdemo`, `ashby:ramp`, `workable:blueground`, `workable:skroutz`, `workable:orfium`, `workable:epignosis`.

### Output

One row per job. Every field is present for every ATS (`null` where that ATS does not publish it):

```json
{
  "source": "ashby",
  "companySlug": "ramp",
  "companyName": null,
  "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "title": "Security Engineer, Cloud",
  "url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-.../application",
  "location": "New York, NY (HQ)",
  "locations": ["New York, NY (HQ)", "Remote (Canada)"],
  "country": "USA",
  "department": "Engineering",
  "team": "Backend",
  "employmentType": "FullTime",
  "isRemote": true,
  "workplaceType": "Hybrid",
  "postedAt": "2026-04-07T17:12:35.753000+00:00",
  "updatedAt": null,
  "salaryText": "$211.4K - $290.6K",
  "descriptionText": "About Ramp ...",
  "descriptionHtml": "<p>About Ramp ...</p>",
  "boardUrl": "https://jobs.ashbyhq.com/ramp",
  "scrapedAt": "2026-09-10T10:22:41.010000+00:00"
}
```

The run also writes a `RUN_SUMMARY` record to the key-value store with the number of boards processed, jobs pushed, and any boards that failed — handy when you are validating a long list of tokens.

### Filters

`titleKeywords`, `excludeTitleKeywords`, `locationKeywords`, `remoteOnly`, `postedWithinDays`, `maxJobsPerCompany`, `maxJobsTotal`. All filtering happens after the fetch, so filtering does not reduce the number of requests — but `maxJobsTotal` does cap your dataset size and therefore your cost.

### Honest limitations

- **Four ATS platforms, not all of them.** SmartRecruiters and Recruitee are deliberately **not** supported: their `robots.txt` says `Disallow: /` for generic clients, so this Actor does not touch them. Workday, Taleo, iCIMS and SAP SuccessFactors have no comparable open board API and are out of scope.
- **Company name.** Greenhouse and Workable return the company name; Lever and Ashby do not, so `companyName` is `null` for those (the `companySlug` and `boardUrl` always identify the company).
- **`updatedAt`** is only published by Greenhouse.
- **Salary** is only published by Ashby (and occasionally Lever). Most boards do not expose it.
- **Only public, listed jobs.** Confidential or unlisted postings never appear in these APIs, and this Actor does not try to find them.
- **Board tokens must be right.** A wrong token returns HTTP 404; turn on *Emit an item for failed boards* to get those back as rows instead of just log lines.
- **Description size.** `includeDescription` typically multiplies dataset size by 10–30×. Turn it off if you only need titles and links.

### How the data is obtained

Every source is the ATS vendor's own **public, unauthenticated job-board API**, published so that job aggregators can consume it:

- `boards-api.greenhouse.io/v1/boards/{token}/jobs`
- `api.lever.co/v0/postings/{token}?mode=json`
- `api.ashbyhq.com/posting-api/job-board/{token}`
- `apply.workable.com/api/v1/widget/accounts/{token}`

There is no login, no captcha solving, no proxy rotation and no browser automation. `robots.txt` was checked for each host; Lever asks for a one-second crawl delay and the Actor enforces it (the delay input cannot go below 1 second for Lever).

### Changelog

See `CHANGELOG.md`.

### Support

Found a bug, or need a field this Actor does not return yet? Open an issue on the Actor's **Issues** tab, or email **pablodevigoalertas@gmail.com**. Issues are read first.

# Actor input Schema

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

One entry per company. Accepted formats:
• A board URL - https://boards.greenhouse.io/stripe, https://job-boards.greenhouse.io/stripe, https://jobs.lever.co/palantir, https://jobs.ashbyhq.com/ramp, https://apply.workable.com/acme/
• An explicit pair - greenhouse:stripe, lever:palantir, ashby:ramp, workable:acme
• A bare token - stripe (then set "Default ATS provider" below)

## `defaultProvider` (type: `string`):

Used only for entries that are a bare token with no URL and no provider prefix.

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

Adds descriptionText and descriptionHtml to every job. Makes the dataset much larger; turn it off if you only need titles, locations and links.

## `titleKeywords` (type: `array`):

Case-insensitive. A job is kept if its title contains at least one of these strings. Leave empty to keep every title.

## `excludeTitleKeywords` (type: `array`):

Case-insensitive. Jobs whose title contains any of these are dropped.

## `locationKeywords` (type: `array`):

Case-insensitive match against the job location string, e.g. "Berlin", "United Kingdom", "Remote".

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

Keep only jobs the ATS flags as remote, or whose location text contains "remote".

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

0 = no date filter. Uses the posting date the ATS reports; jobs with no date are always kept.

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

0 = no limit.

## `maxJobsTotal` (type: `integer`):

0 = no limit. A hard stop so a large company list cannot run away with your budget.

## `requestDelaySeconds` (type: `number`):

Politeness delay between board requests. Lever's robots.txt asks for a 1 second crawl delay, so values below 1 are raised to 1 for Lever.

## `emitErrorItems` (type: `boolean`):

If a board returns 404 or an error, push a row with the error instead of silently skipping it. Useful when you are validating a large list of board tokens.

## Actor input object example

```json
{
  "companies": [
    "https://job-boards.greenhouse.io/stripe",
    "lever:palantir",
    "ashby:ramp",
    "workable:blueground"
  ],
  "defaultProvider": "greenhouse",
  "includeDescription": true,
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "locationKeywords": [],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "maxJobsPerCompany": 0,
  "maxJobsTotal": 0,
  "requestDelaySeconds": 1,
  "emitErrorItems": false
}
```

# Actor output Schema

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

One row per open job, normalised across Greenhouse, Lever, Ashby and Workable.

# 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/stripe",
        "lever:palantir",
        "ashby:ramp",
        "workable:blueground"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("thedeadpoet/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/stripe",
        "lever:palantir",
        "ashby:ramp",
        "workable:blueground",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("thedeadpoet/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/stripe",
    "lever:palantir",
    "ashby:ramp",
    "workable:blueground"
  ]
}' |
apify call thedeadpoet/ats-job-board-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thedeadpoet/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/JTvoEtYu05eJIBZEa/builds/OKUpJoCBLERIHcbRp/openapi.json
