# ATS Jobs Scraper - Greenhouse, Lever & Ashby Boards (`borasipahi/ats-jobs-scraper`) Actor

Pull every open job from any company's Greenhouse, Lever or Ashby board in one schema, or give it company domains and let it discover which ATS and board token they use.

- **URL**: https://apify.com/borasipahi/ats-jobs-scraper.md
- **Developed by:** [Bora Sipahi](https://apify.com/borasipahi) (community)
- **Categories:** Jobs, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## 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

Most companies do not run their own job site. They rent one from an applicant tracking system, and the three most common for technology and growth companies, **Greenhouse**, **Lever** and **Ashby**, all publish every open job through a public, keyless JSON API. ATS Jobs Scraper reads those APIs and returns every job in one consistent schema, whichever system the company uses.

It also solves the part nobody else sells: **finding the board in the first place**. Give it company domains and it visits their careers pages, probes all three APIs, and tells you which ATS and which board token each company uses. That index is cached, so you pay for a discovery once and reuse it for free on every later run.

No proxies, no browser, no login. A run over three boards with a thousand jobs finishes in seconds.

### Two ways to use it

**Boards you already know.** Fill **Boards** with the system and token from the board URL:

- `boards.greenhouse.io/stripe` is `{"ats": "greenhouse", "token": "stripe"}`
- `jobs.lever.co/veeva` is `{"ats": "lever", "token": "veeva"}`
- `jobs.ashbyhq.com/ramp` is `{"ats": "ashby", "token": "ramp"}`

**Companies you only know by website.** Fill **Company domains** with `["stripe.com", "ramp.com"]`. The Actor resolves each domain to its board, then fetches the jobs. Turn on **Discover only** to get just the board list without the jobs.

Then filter with **Search query**, **Departments**, **Locations**, **Remote only** and **Posted within (days)**, and download the dataset as JSON, CSV or Excel. For a daily feed of only new postings, turn on **Incremental mode** and add a Schedule.

### Output

Every job row has the same fields regardless of system:

| Field | Meaning |
| --- | --- |
| `ats`, `boardToken`, `companyName`, `companyDomain` | Where the job came from |
| `jobId`, `requisitionId` | Stable ids, e.g. `greenhouse:stripe:8172508` |
| `title`, `department`, `team`, `employmentType` | Basics; employment type is normalised to `full-time`, `part-time`, `contract`, `internship` |
| `locationName`, `locationCountry`, `isRemote` | Location as published plus a remote flag |
| `minSalary`, `maxSalary`, `currency` | When the board publishes a range (Lever and Ashby do for many jobs, Greenhouse for some) |
| `descriptionSnippet` | First 300 characters of the description, always included |
| `descriptionHtml`, `descriptionText` | The full text, only when **Include full description** is on |
| `applyUrl`, `canonicalUrl` | Deep links to the official listing and application form |
| `publishedAt`, `updatedAt` | ISO timestamps |
| `aiDisclaimer`, `aiOptOutRequestUrl` | Greenhouse's automated-decision disclosure fields, carried through unchanged |
| `scrapedAt` | Run timestamp |

In **Discover only** mode each row is a board: `companyDomain`, `ats`, `boardToken`, `boardUrl`, `jobCount`, `companyName`, `discoveredVia`.

### Pricing

Three events, all listed on the Pricing tab:

- `job-listing`, once per job row saved. Filtered-out, duplicate and already-delivered jobs are never charged.
- `job-detail`, once per job row when **Include full description** is on. Off by default.
- `board-discovered`, once per board found for a company domain. Cached boards and boards you supply yourself are free.

Set **Maximum jobs** to cap a run. No platform usage is passed on to you.

### Input example

```json
{
    "companyDomains": ["stripe.com", "ramp.com", "zoox.com"],
    "searchQuery": "engineer, developer",
    "remoteOnly": true,
    "postedWithinDays": 14,
    "includeFullDescription": false,
    "maxItems": 500
}
```

### Output example

```json
{
    "ats": "lever",
    "boardToken": "zoox",
    "companyName": "Zoox",
    "jobId": "lever:zoox:f4746da4-8eb8-43e2-b7ce-bf3c7cf9640d",
    "title": "Autonomy System Test Engineer",
    "department": "Software",
    "team": "Software Quality Assurance",
    "employmentType": "full-time",
    "locationName": "Foster City, CA",
    "locationCountry": "US",
    "isRemote": false,
    "minSalary": 144000,
    "maxSalary": 193000,
    "currency": "USD",
    "descriptionSnippet": "Zoox is building the first ground-up, fully autonomous vehicle fleet...",
    "applyUrl": "https://jobs.lever.co/zoox/f4746da4-8eb8-43e2-b7ce-bf3c7cf9640d/apply",
    "canonicalUrl": "https://jobs.lever.co/zoox/f4746da4-8eb8-43e2-b7ce-bf3c7cf9640d",
    "publishedAt": "2026-08-12T16:02:11.000Z",
    "scrapedAt": "2026-09-07T00:30:00.000Z"
}
```

### Data sources and what we do with them

All three vendors document their job board endpoints as public, unauthenticated read APIs. The Actor only reads listed, open postings; it never touches applications, candidates or anything behind a login. Job descriptions are the employer's text, so the full description is opt-in and every row deep-links to the official listing and apply page. Recruiter e-mail addresses and phone numbers pasted into descriptions are removed by default. Greenhouse's AI-disclosure fields are passed through so downstream users who need them for local hiring law can keep them.

Workday is deliberately not included: it has no documented public API and blocks datacenter traffic.

### Related Actors

- Pair with a Slack or e-mail integration and Incremental mode for a daily "new jobs at these companies" digest.
- Use **Discover only** to build a company-to-ATS index once and feed the tokens into any other tooling you run.

### FAQ

**Discovery found nothing for a domain.** The company either uses a different ATS (Workday, SmartRecruiters, iCIMS, a custom site) or embeds the board in a way the careers page does not expose. Supply the board token directly if you know it.

**Why are salaries empty for a company?** The board only exposes salary when the employer publishes one. Lever and Ashby expose a structured range when present, Greenhouse exposes pay ranges on some boards.

**Is the same job across two boards deduplicated?** Within a run each job id is delivered once. The same role posted on two different systems is rare; if you need cross-system deduplication, use the `title` and `companyName` fields downstream.

**Something broke?** Open an issue on the Actor's Issues tab. The three APIs are checked several times a day and the Actor is updated when a vendor changes its payload.

# Actor input Schema

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

Boards to read, one object per board: {"ats": "greenhouse" | "lever" | "ashby", "token": "<board token>"}. The token is the slug in the board URL, e.g. boards.greenhouse.io/stripe, jobs.lever.co/veeva, jobs.ashbyhq.com/ramp.

## `companyDomains` (type: `array`):

Company websites such as \["stripe.com", "ramp.com"]. The Actor visits the site's careers pages and probes the three ATS APIs to find the board automatically. Each found board is charged once as board-discovered.

## `discoverOnly` (type: `boolean`):

Only resolve company domains to boards; do not fetch jobs. Output rows are boards instead of jobs.

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

Restrict to these systems.

## `searchQuery` (type: `string`):

Case-insensitive keywords matched against title, department, team and description. Separate alternatives with a comma.

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

Keep only jobs whose department or team contains any of these words.

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

Keep only jobs whose location contains any of these words, e.g. \["Remote", "London", "US"].

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

Keep only jobs flagged remote by the board or whose location mentions remote.

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

Keep only jobs published or updated in the last N days. Leave empty for all open jobs.

## `includeFullDescription` (type: `boolean`):

Include descriptionHtml and descriptionText for each job (charged as job-detail on top of job-listing). Off gives a 300-character snippet only.

## `stripContactDetails` (type: `boolean`):

Remove e-mail addresses and phone numbers pasted into descriptions.

## `maxItems` (type: `integer`):

Stop after this many jobs. Bounds your cost.

## `incremental` (type: `boolean`):

Remember every job already delivered and skip it next time. Use with a schedule to get only new jobs.

## `incrementalStoreName` (type: `string`):

Key-value store that holds delivered job ids and the discovered board index.

## Actor input object example

```json
{
  "boards": [
    {
      "ats": "greenhouse",
      "token": "stripe"
    },
    {
      "ats": "lever",
      "token": "zoox"
    },
    {
      "ats": "ashby",
      "token": "ramp"
    }
  ],
  "companyDomains": [],
  "discoverOnly": false,
  "atsFilter": [],
  "departments": [],
  "locations": [],
  "remoteOnly": false,
  "includeFullDescription": false,
  "stripContactDetails": true,
  "maxItems": 200,
  "incremental": false,
  "incrementalStoreName": "ats-jobs-scraper-state"
}
```

# Actor output Schema

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

No description

## `jobsCsv` (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 = {
    "boards": [
        {
            "ats": "greenhouse",
            "token": "stripe"
        },
        {
            "ats": "lever",
            "token": "zoox"
        },
        {
            "ats": "ashby",
            "token": "ramp"
        }
    ],
    "maxItems": 200
};

// Run the Actor and wait for it to finish
const run = await client.actor("borasipahi/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 = {
    "boards": [
        {
            "ats": "greenhouse",
            "token": "stripe",
        },
        {
            "ats": "lever",
            "token": "zoox",
        },
        {
            "ats": "ashby",
            "token": "ramp",
        },
    ],
    "maxItems": 200,
}

# Run the Actor and wait for it to finish
run = client.actor("borasipahi/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 '{
  "boards": [
    {
      "ats": "greenhouse",
      "token": "stripe"
    },
    {
      "ats": "lever",
      "token": "zoox"
    },
    {
      "ats": "ashby",
      "token": "ramp"
    }
  ],
  "maxItems": 200
}' |
apify call borasipahi/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

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