# Job Boards Scraper & API - Greenhouse, Lever, Ashby, Workday (`rel8ble/ats-job-boards-scraper`) Actor

Every open job at any company from its ATS: Greenhouse, Lever, Ashby, SmartRecruiters, Workday, Workable, Recruitee, Personio, Teamtailor, BambooHR. Input: board URLs, careers pages or company sites. One result = one job: title, company, location, remote, department, salary, date, description. $1/1k

- **URL**: https://apify.com/rel8ble/ats-job-boards-scraper.md
- **Developed by:** [Giovanni Rich](https://apify.com/rel8ble) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 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

## Job Boards Scraper & API - Greenhouse, Lever, Ashby, Workday, Workable, SmartRecruiters and more

**Job Boards Scraper** is a company job postings scraper and unofficial jobs API for the applicant tracking systems most companies hire through: **Greenhouse, Lever, Ashby, SmartRecruiters, Workday, Workable, Recruitee, Personio, Teamtailor and BambooHR**. Give it a board URL, a careers page, a company website or just a company name, and get every open job as clean JSON, CSV or Excel: title, company, location, remote flag, department, employment type, salary range, posting date, full description and the apply link.

It reads each platform's **official public job feed** over plain HTTP: no browser, no login, no scraping of rendered pages, so a board with 700 jobs takes a couple of seconds and almost never fails. Use it for **hiring-signal lead generation** (which companies are hiring for what, right now), job aggregators, recruiting tools, labour-market research and competitor watching.

### How to use

1. Paste one or more entries in **Job boards, careers pages or company websites**. Any of these work:
   - an ATS board: `https://boards.greenhouse.io/stripe`, `https://jobs.lever.co/palantir`, `https://jobs.ashbyhq.com/notion`, `https://apply.workable.com/workmotion`, `https://jobs.smartrecruiters.com/Ubisoft2`, `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`, `https://bunq.recruitee.com`, `https://acme.jobs.personio.de`, `https://polestar.teamtailor.com`, `https://acme.bamboohr.com/careers`
   - a company careers page such as `https://www.notion.com/careers`: the embedded board is detected from the page
   - a company website such as `https://stripe.com`, or a bare name such as `stripe`: the name is tried on every platform and the board that has jobs wins
2. Optionally filter: **Title must contain** (e.g. `engineer`, `sales`), **Title must NOT contain**, **Location must contain** (e.g. `remote`, `london`), **Remote jobs only**, **Posted after**, and **Max jobs per board**.
3. Click **Start**. Download the results as JSON, CSV, Excel or HTML from the **Output** tab, or pull them through the Apify API, Make, Zapier, n8n or an MCP client.

### What you get

One dataset item per job posting:

- **Identity**: `id` (the platform's job id), `title`, `company`, `ats` (which platform), `url` (the job page), `applyUrl`, `requisitionId` where the platform publishes one, `boardUrl`.
- **Where**: `location` (as the company wrote it), `locations` (all locations of the posting), `city`, `region`, `country`, `workplaceType` (`remote` / `hybrid` / `onsite`) and `isRemote`.
- **What**: `department`, `team`, `employmentType` (`full-time`, `part-time`, `contract`, `internship`...), `seniority` where the platform has it.
- **Pay**: `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod` and the raw `salaryText`, from the platform's structured salary field when it exists (Lever, Ashby, Recruitee, Teamtailor) or parsed from the description otherwise (US pay-transparency ranges such as "$213,000 - $320,000").
- **When**: `publishedAt` and, where available, `updatedAt` (ISO 8601).
- **Description**: the full job description as plain text (`description`), optionally also as HTML (`descriptionHtml`).
- `scrapedAt` timestamp.

### Use cases

- **Lead generation from hiring signals**: companies hiring SDRs need sales tools, companies hiring data engineers need data vendors. Run your target-account list daily and route new matching jobs to your CRM.
- **Job boards and aggregators**: pull hundreds of company boards into one normalised feed, with stable job ids for deduplication.
- **Recruiting and talent intelligence**: track open roles, departments and locations across competitors over time.
- **Market research**: salary ranges, remote share and hiring volume by company, team or country.
- **AI agents**: an agent can ask "what is Notion hiring for in engineering?" and get structured rows back through MCP.

### Input example

| Field | Default | Description |
|---|---|---|
| `boards` | Stripe's Greenhouse board | Board URLs, careers pages, company websites or bare slugs, one per line |
| `titleKeywords` | - | Keep titles containing any of these (case-insensitive) |
| `excludeTitleKeywords` | - | Drop titles containing any of these |
| `locationKeywords` | - | Keep jobs whose location, city, country or workplace type contains any of these |
| `remoteOnly` | false | Keep only jobs marked remote |
| `postedAfter` | - | `YYYY-MM-DD`; jobs without a date are kept |
| `maxJobsPerBoard` | 0 (all) | Cap per board after filtering |
| `includeDescription` | true | Plain-text description. On SmartRecruiters, Workday, Workable and BambooHR this costs one extra request per job |
| `includeDescriptionHtml` | false | Also keep the description HTML |
| `proxyConfiguration` | Apify datacenter | No residential proxies needed |

Example: remote engineering jobs at three companies, posted this month:

```json
{
    "boards": ["https://boards.greenhouse.io/stripe", "https://jobs.ashbyhq.com/notion", "https://jobs.lever.co/palantir"],
    "titleKeywords": ["engineer", "developer"],
    "locationKeywords": ["remote"],
    "postedAfter": "2026-10-01"
}
```

Example: every open job at a list of companies you only know by website:

```json
{
    "boards": ["https://stripe.com", "https://www.notion.com/careers", "blueground", "bunq.com"],
    "maxJobsPerBoard": 200,
    "includeDescription": false
}
```

### Output example

From a real run (Notion's Ashby board, description shortened):

```json
{
    "id": "1fc309c8-da20-4ff2-84c7-8b863ece2b0a",
    "title": "Software Engineer, Developer Platform",
    "company": "Notion",
    "ats": "ashby",
    "url": "https://jobs.ashbyhq.com/notion/1fc309c8-da20-4ff2-84c7-8b863ece2b0a",
    "applyUrl": "https://jobs.ashbyhq.com/notion/1fc309c8-da20-4ff2-84c7-8b863ece2b0a/application",
    "location": "San Francisco, California",
    "locations": ["San Francisco, California", "New York, New York"],
    "city": "San Francisco",
    "region": "California",
    "country": "United States",
    "workplaceType": "hybrid",
    "isRemote": true,
    "department": "Engineering",
    "team": "Engineering",
    "employmentType": "full-time",
    "seniority": null,
    "salaryMin": 213000,
    "salaryMax": 320000,
    "salaryCurrency": "USD",
    "salaryPeriod": null,
    "salaryText": "$213,000 - $320,000",
    "publishedAt": "2026-08-24T14:44:49.699Z",
    "updatedAt": null,
    "description": "WHO WE ARE\n\nNotion is the collaborative AI workspace where teams and agents think together ...",
    "boardUrl": "https://jobs.ashbyhq.com/notion",
    "scrapedAt": "2026-10-08T17:45:45.963Z"
}
```

Fill rates on a 210-job test across all ten platforms: id, title, company, url, location, publishedAt and description 100%; country 85%; applyUrl 75%; employmentType 73%; workplaceType 61%; department 58%; city 52%; salary 21% (only postings that state a range). Fields a platform does not publish are `null`, never guessed.

### Pricing

Pay per result: **$1.00 per 1,000 jobs** (one result = one job posting saved to the dataset).

- A company board with 300 open jobs = $0.30
- 500 company boards, 50,000 jobs = $50

Failed requests, boards that could not be found and duplicates are never charged. If you set a maximum cost per run, the scraper stops cleanly when it reaches it. Apify's free plan includes $5 of monthly platform credit, enough for thousands of jobs.

### Integrations

- **Make, Zapier and n8n**: start runs and push new jobs to your CRM, Slack, Sheets or Airtable.
- **Google Sheets**: export the dataset straight into a spreadsheet, or refresh it on a schedule.
- **Apify API**: run the actor and fetch results over REST, or with the official JavaScript and Python clients.
- **Webhooks**: get notified when a run finishes and process the data right away.
- **Schedules**: run your company list daily and diff by `id` to catch new postings.
- **MCP for AI agents**: through the Apify MCP server (https://mcp.apify.com), Claude, ChatGPT, Cursor and other agents can call this jobs API directly and read the results.

### Supported platforms

| Platform | What the input looks like | Description included | Notes |
|---|---|---|---|
| Greenhouse | `boards.greenhouse.io/{company}`, `job-boards.greenhouse.io/{company}` | yes | one request per board |
| Lever | `jobs.lever.co/{company}` | yes | structured salary when published |
| Ashby | `jobs.ashbyhq.com/{company}` | yes | structured salary when published |
| SmartRecruiters | `jobs.smartrecruiters.com/{Company}` | yes (1 extra request per job) | paged, 100 per page |
| Workday | `{tenant}.wd{n}.myworkdayjobs.com/{site}` | yes (1 extra request per job) | paged, 20 per page; relative dates ("Posted 3 Days Ago") converted, "30+ days" left null |
| Workable | `apply.workable.com/{company}` | yes (1 extra request per job) | one row per job, all locations merged |
| Recruitee | `{company}.recruitee.com` | yes | structured salary when published |
| Personio | `{company}.jobs.personio.de` or `.com` | yes | XML feed |
| Teamtailor | `{company}.teamtailor.com` | yes | paged; custom-domain career sites are detected from the page |
| BambooHR | `{company}.bamboohr.com/careers` | yes (1 extra request per job) | |

Careers pages on company domains are supported when they embed or link to one of these boards (most do). When a page has no detectable board, the company name from the domain is tried on every platform.

### Limits (read before large runs)

- **Only these ten platforms.** Companies using iCIMS, Taleo, SuccessFactors, Jobvite or a fully custom careers site are reported as unresolved in the run summary, not scraped.
- **One board per input line.** A company name that exists on several platforms (for instance an old, empty Lever board and a live Greenhouse one) resolves to the board that has open jobs.
- **Workday boards can be large** (thousands of jobs, 20 per page, plus one request per description). Use `maxJobsPerBoard`, title filters, or `includeDescription: false` for a quick count.
- **Company names** come from the platform when it publishes them (Greenhouse, SmartRecruiters, Workable, Recruitee, Personio, Teamtailor); otherwise they are derived from the board slug (`notion` -> "Notion").
- **No applicant or personal data.** Only public job postings.

### FAQ

**Is it legal to scrape job boards?**
This actor reads public job postings from the platforms' own public APIs and feeds, the same data shown on the companies' careers pages, and no personal data. You're responsible for using it in line with the platforms' terms and applicable law. This is not legal advice.

**Why is it so reliable?**
No HTML parsing of rendered pages and no bot protection to fight: Greenhouse, Lever, Ashby, SmartRecruiters, Workday, Workable, Recruitee, Personio, Teamtailor and BambooHR all expose their boards as JSON or XML for exactly this purpose. Transient errors are retried with backoff on a fresh session.

**How do I find a company's board URL?**
Open the company's careers page and look at the apply links, or just paste the careers page or the company website and let the actor detect it.

**Can I get only new jobs?**
Run on a schedule and compare `id` (stable per platform) with your previous dataset, or use `postedAfter`.

**Does it need a browser, cookies or a login?**
No. Plain HTTPS requests to public endpoints only, which is why it's fast and cheap.

### How it works (for developers)

Each input line is classified by URL shape into one of the ten platforms (or marked for sniffing). Careers pages are fetched once and scanned for embedded board links or scripts; company websites and bare names are probed on every platform in parallel, and the first board that lists jobs wins. Boards are then read through their public endpoints: Greenhouse `boards-api`, Lever `api.lever.co/v0/postings`, Ashby `posting-api/job-board`, SmartRecruiters `api.smartrecruiters.com/v1/companies/{id}/postings`, Workday `wday/cxs/{tenant}/{site}/jobs`, Workable's widget API, Recruitee `api/offers`, Personio's XML feed, Teamtailor's JSON Feed and BambooHR's careers list. Every platform's fields are mapped to one schema; HTML descriptions are converted to clean text; salary ranges come from structured fields or a conservative regex that ignores year ranges.

Run it locally:

```bash
npm install
npm test                                  # parser tests on saved fixtures
APIFY_LOCAL_STORAGE_DIR=./storage node src/main.js   # input in storage/key_value_stores/default/INPUT.json
```

# Actor input Schema

## `boards` (type: `array`):

Provide at least one, one per line. Accepted: an ATS job board URL (https://boards.greenhouse.io/stripe, https://jobs.lever.co/palantir, https://jobs.ashbyhq.com/notion, https://apply.workable.com/workmotion, https://jobs.smartrecruiters.com/Ubisoft2, https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite, https://bunq.recruitee.com, https://acme.jobs.personio.de, https://polestar.teamtailor.com, https://acme.bamboohr.com/careers), a company careers page (https://www.notion.com/careers - the embedded board is detected), a company website (https://stripe.com - the board is found by trying the company name on every platform), or a bare company slug like "stripe". Every open job on each board is returned. Default: Stripe's Greenhouse board (demo).

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

Optional. Keep only jobs whose title contains at least one of these words or phrases, case-insensitive, e.g. \["engineer", "developer"]. Empty = keep all titles.

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

Optional. Drop jobs whose title contains any of these, case-insensitive, e.g. \["intern", "senior"].

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

Optional. Keep only jobs whose location, city, region, country or workplace type contains one of these, case-insensitive, e.g. \["remote", "london", "united states"]. Empty = all locations.

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

Optional boolean. true = keep only jobs the board marks as remote (workplaceType "remote" or isRemote true). Default false.

## `postedAfter` (type: `string`):

Optional. Keep only jobs published on or after this date, format YYYY-MM-DD (e.g. "2026-09-01"). Jobs whose board does not report a date are kept. Default: no date filter.

## `maxJobsPerBoard` (type: `integer`):

Optional. Maximum jobs saved per board after filtering, integer >= 0. 0 = all (default). Big companies have hundreds to thousands of open jobs; set 50 for a quick sample.

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

Optional boolean. true (default) = add the full job description as plain text in the description field. On SmartRecruiters, Workday, Workable and BambooHR boards this costs one extra request per job (still a single result); other platforms include it for free. false = listing fields only, fastest.

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

Optional boolean. true = also keep the original HTML of the description in descriptionHtml. Default false (smaller rows).

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

Optional, advanced. Parallel requests, integer 1-50. Default 10. The ATS APIs are fast and rarely rate-limit; lower it if you see HTTP 429 retries in the log.

## `maxRequestRetries` (type: `integer`):

Optional, advanced. Retries per failed request, integer 0-10. Default 4.

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

Optional, advanced. Apify Proxy settings. Default: Apify datacenter proxies, which are enough because every platform here serves jobs through an official public API or feed. Residential proxies are not needed.

## Actor input object example

```json
{
  "boards": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.ashbyhq.com/notion"
  ],
  "titleKeywords": [],
  "remoteOnly": false,
  "maxJobsPerBoard": 25,
  "includeDescription": true,
  "includeDescriptionHtml": false,
  "maxConcurrency": 10,
  "maxRequestRetries": 4,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

All job postings found: title, company, location, remote flag, department, employment type, salary, dates, description and URLs.

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

Per-board counts (jobs on board, matched filters, saved), detected platforms, unresolved inputs and stop reasons.

# 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 = {
    "boards": [
        "https://boards.greenhouse.io/stripe",
        "https://jobs.ashbyhq.com/notion"
    ],
    "titleKeywords": [],
    "maxJobsPerBoard": 25,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("rel8ble/ats-job-boards-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 = {
    "boards": [
        "https://boards.greenhouse.io/stripe",
        "https://jobs.ashbyhq.com/notion",
    ],
    "titleKeywords": [],
    "maxJobsPerBoard": 25,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("rel8ble/ats-job-boards-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 '{
  "boards": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.ashbyhq.com/notion"
  ],
  "titleKeywords": [],
  "maxJobsPerBoard": 25,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call rel8ble/ats-job-boards-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,rel8ble/ats-job-boards-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/UHSjJBRupshLaYfxn/builds/oCJUhFNcVdTIgcY8a/openapi.json
