# Greenhouse Jobs Scraper: Lever & Ashby Job Boards API (`phaux/ats-jobs-greenhouse-lever-ashby`) Actor

Get all open jobs from company boards on Greenhouse, Lever and Ashby (official public APIs). Use for a company's current openings. Input: greenhouseBoards, leverCompanies, ashbyBoards or careerUrls; filters keyword, remoteOnly, postedSince. Output per job: title, company, location, salary, URL.

- **URL**: https://apify.com/phaux/ats-jobs-greenhouse-lever-ashby.md
- **Developed by:** [Phaux](https://apify.com/phaux) (community)
- **Categories:** Jobs, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

## Greenhouse Jobs Scraper: Lever & Ashby Job Boards API

A **Greenhouse jobs scraper** that also reads **Lever** and **Ashby** job boards. Give it company board tokens or board URLs and get their open job listings as JSON, CSV or Excel, ready for job boards, hiring-signal tracking or recruiting research.

Scrape every open job from any company that hires through **Greenhouse**, **Lever** or **Ashby**. Jobs from all three applicant tracking systems (ATS) come back in **one normalized schema** with title, location, remote flag, department, employment type, salary range (when published), posting date, apply link and the full description. Filters for keyword, location, remote, department and posting date run before anything is saved, so you pay only for the jobs you keep.

### Quick start for AI agents and API users

- **What it does:** Get all open jobs from company boards on Greenhouse, Lever and Ashby (official public APIs).
- **When to use it:** For a company's current openings.
- **Example input** (the form's prefill; it finishes in under a minute):

```json
{
  "greenhouseBoards": [
    "stripe",
    "discord"
  ],
  "leverCompanies": [
    "spotify"
  ],
  "ashbyBoards": [
    "linear"
  ]
}
```

- **Example output:** one real dataset row from run `QUo9tp35NZqHGu1YN` (2026-10-23), with long fields shortened and empty fields left out. Fields not shown are listed in the output table below.

```json
{
  "platform": "ashby",
  "companyToken": "linear",
  "company": "linear",
  "jobId": "d3bc1ced-3ce4-4086-a050-555055dbb1ff",
  "title": "Senior / Staff Fullstack Engineer",
  "location": "Europe",
  "allLocations": [
    "Europe"
  ],
  "isRemote": true,
  "workplaceType": "remote",
  "department": "Product",
  "team": "Engineering",
  "employmentType": "FullTime",
  "postedAt": "2021-04-27T20:13:45.158000Z",
  "jobUrl": "https://jobs.ashbyhq.com/linear/d3bc1ced-3ce4-4086-a050-555055dbb1ff",
  "applyUrl": "https://jobs.ashbyhq.com/linear/d3bc1ced-3ce4-4086-a050-555055dbb1ff/application",
  "employmentTypeNormalized": "FULL_TIME"
}
```

- **Cost (pay per event, Free-plan prices):** job $0.002. Failed items are not charged.
- **From an AI agent:** with the Apify MCP server, call `call-actor` with actor `phaux/ats-jobs-greenhouse-lever-ashby`, or load it as its own tool with `https://mcp.apify.com?tools=phaux/ats-jobs-greenhouse-lever-ashby`. Read results with `get-dataset-items`.

### What does Greenhouse, Lever & Ashby Jobs Scraper do?

- Reads the platforms' **official public job-board APIs**, the same feeds that power each company's careers page:
  - Greenhouse: `boards-api.greenhouse.io`
  - Lever: `api.lever.co` and `api.eu.lever.co`
  - Ashby: `api.ashbyhq.com/posting-api`
- Accepts company tokens (`stripe`) or board URLs (`https://jobs.lever.co/spotify`), in any mix.
- Normalizes the three formats into one record per job and de-duplicates across the run.
- Removes recruiter and hiring-manager names, email addresses, `mailto:` links and phone numbers from descriptions.

No browser, no HTML scraping, no login and no proxy. Typical runs finish in seconds.

### Why use it?

- **Reliable:** documented JSON APIs don't break when a careers page is redesigned.
- **One bad board doesn't stop the run:** unknown tokens are listed in `BOARD_ERRORS`, cost nothing, and the other boards still run.
- **Filters before charging:** jobs removed by your filters are never saved or charged.
- **No start fee and no minimum.**
- **One schema for three ATS platforms**, with employment type mapped to `FULL_TIME`, `PART_TIME`, `CONTRACT`, `INTERNSHIP` or `TEMPORARY`.

Use cases: job aggregators and job boards, hiring-signal and sales-intelligence feeds, labor-market research, salary benchmarking, and alerts for new roles at target companies.

### How to use it

1. Find each company's board token in its careers link (see "Where's the token?" below), or paste the board URL.
2. Put Greenhouse, Lever and Ashby boards in their fields, or any board URL in `careerUrls`.
3. Optionally set filters (`keyword`, `location`, `remoteOnly`, `department`, `postedSince`) and a cost cap (`maxItems`).
4. Run the Actor and download the dataset as JSON, CSV, Excel or HTML, or read it through the Apify API.

**Where's the token?**

- Greenhouse: the part after `boards.greenhouse.io/` or `job-boards.greenhouse.io/`. Embedded boards (`boards.greenhouse.io/embed/job_board?for=<token>`) work too.
- Lever: the part after `jobs.lever.co/`.
- Ashby: the part after `jobs.ashbyhq.com/`.
- Company-hosted careers pages are not crawled. Give the Greenhouse, Lever or Ashby board URL they link to.

### Input

```json
{
  "greenhouseBoards": ["stripe", "https://job-boards.greenhouse.io/discord"],
  "leverCompanies": ["spotify"],
  "ashbyBoards": ["https://jobs.ashbyhq.com/linear"],
  "careerUrls": ["https://jobs.lever.co/zoox"],
  "keyword": "engineer, data",
  "location": "new york, london, remote",
  "remoteOnly": false,
  "department": "engineering",
  "postedSince": "7 days",
  "includeDescription": true,
  "maxItemsPerBoard": 0,
  "maxItems": 0
}
```

All text filters are case-insensitive and accept several comma-separated values (any match keeps the job). `keyword` matches the title; turn on `keywordInDescription` to search descriptions too. `postedSince` takes a date (`2026-10-01`) or a period (`24 hours`, `7 days`, `2 weeks`).

| Field | Type | Default | Description |
|---|---|---|---|
| `greenhouseBoards` | array | — | Board tokens (e.g. 'stripe') or URLs like https://boards.greenhouse.io/stripe or https://job-boards.greenhouse.io/stripe. |
| `leverCompanies` | array | — | Lever site names (e.g. 'spotify') or URLs like https://jobs.lever.co/spotify. EU-hosted sites are found automatically. |
| `ashbyBoards` | array | — | Ashby board names (e.g. 'linear') or URLs like https://jobs.ashbyhq.com/linear. |
| `careerUrls` | array | — | Mixed Greenhouse / Lever / Ashby board URLs; the platform is detected from the URL. Company-domain career pages are not scraped. |
| `keyword` | string | — | Comma-separated; a job matches if its title contains any of them (case-insensitive). |
| `keywordInDescription` | boolean | `false` | Off: keywords match the job title only. On: also match the description text. |
| `location` | string | — | Comma-separated; matches any location of the job (e.g. 'London, New York, remote'). |
| `remoteOnly` | boolean | `false` | Keep only jobs the platform marks remote (Lever/Ashby) or whose location/office says remote (Greenhouse). |
| `department` | string | — | Comma-separated, e.g. 'engineering, data'. |
| `postedSince` | string | — | A date (2026-10-01) or a period ('24 hours', '7 days', '2 weeks'). Uses the posted date, or the updated date where the platform gives no posted date. |
| `includeDescription` | boolean | `true` | Turn off for smaller, faster output without descriptionHtml/descriptionText. |
| `maxItemsPerBoard` | integer | `0` | Maximum number of jobs saved from each board, after filters. 0 = no limit. |
| `maxItems` | integer | `0` | 0 = no limit. You are only charged for jobs actually saved. |
| `maxConcurrency` | integer | `4` | How many boards to fetch at once (1-8). |
| `minRequestIntervalSecs` | string | `"0.5"` | Politeness delay between requests to the same API host. |
| `proxyConfiguration` | object | `{"useApifyProxy": false}` | Not needed: these are official public APIs. Leave off. |

### Output example

One dataset item per job:

```json
{
  "platform": "greenhouse",
  "company": "Discord",
  "companyToken": "discord",
  "jobId": "8806482002",
  "title": "Commercial Policy Lead",
  "location": "San Francisco Bay Area",
  "allLocations": ["San Francisco Bay Area", "Remote (US)"],
  "isRemote": true,
  "workplaceType": null,
  "department": "Policy",
  "team": null,
  "employmentType": null,
  "employmentTypeNormalized": null,
  "postedAt": "2026-09-15T16:52:11Z",
  "updatedAt": "2026-10-15T20:08:17Z",
  "jobUrl": "https://job-boards.greenhouse.io/discord/jobs/8806482002",
  "applyUrl": "https://job-boards.greenhouse.io/discord/jobs/8806482002",
  "salary": null,
  "descriptionHtml": "<div class=\"content-intro\">…",
  "descriptionText": "Discord has a highly engaged community…",
  "scrapedAt": "2026-10-20T10:50:00Z"
}
```

### Output fields

| Field | Type | Description |
|---|---|---|
| `platform` | string | Source ATS: greenhouse, lever or ashby. |
| `companyToken` | string or null | The company's job-board token on that platform. |
| `company` | string or null | Company name. |
| `jobId` | string/number or null | Job ID on the source platform. |
| `title` | string or null | Job title. |
| `location` | string or null | Primary location. |
| `allLocations` | array or null | All listed locations. |
| `isRemote` | boolean or null | Whether the job is remote. |
| `workplaceType` | string or null | remote, hybrid or onsite, when the platform says. |
| `department` | string or null | Department. |
| `team` | string or null | Team. |
| `employmentType` | string or null | Employment type as posted. |
| `employmentTypeNormalized` | string or null | Normalized employment type. |
| `postedAt` | string or null | Posting date (ISO 8601). |
| `updatedAt` | string or null | Last update (ISO 8601). |
| `jobUrl` | string or null | Public job page URL. |
| `applyUrl` | string or null | Application URL. |
| `salary` | object or null | Pay range: min, max, currency, interval, summary. |
| `country` | string or null | Country (Lever only). |
| `descriptionHtml` | string or null | Job description HTML. |
| `descriptionText` | string or null | Job description as plain text, contact details removed. |
| `scrapedAt` | string or null | When the job was fetched (ISO 8601). |

Notes:

- Dates are UTC ISO 8601. `postedAt` comes from Greenhouse `first_published`, Lever `createdAt` and Ashby `publishedAt`. Only Greenhouse provides `updatedAt`.
- `isRemote` uses the platform's own remote or workplace flag on Lever and Ashby. Greenhouse has no such flag, so it is detected from location and office names.
- `salary` is filled only when the company publishes a pay range.
- `company` is the display name on Greenhouse. On Lever and Ashby it is the board token, because their APIs don't return a display name.

The run also saves two key-value records: `RUN_STATS` (boards OK or failed, jobs seen, filtered, duplicates and saved) and `BOARD_ERRORS` (each bad or unsupported input, with the reason).

### How much does it cost?

Pay per event: you pay only for **each job saved to your dataset**. There's no start fee and no minimum, and Apify platform usage is included.

| Apify plan | Price per job | Per 1,000 jobs |
|---|---|---|
| Free | $0.0020 | $2.00 |
| Bronze (Starter) | $0.0018 | $1.80 |
| Silver (Scale) | $0.0016 | $1.60 |
| Gold, Platinum, Diamond (Business+) | $0.0014 | $1.40 |

Example: 500 jobs on the Free plan cost $1.00. Boards that fail or don't exist, jobs removed by your filters, and duplicates are free. Set `maxItems` to cap the cost of any run.

### Use with AI agents, MCP and the API

- **MCP:** add `https://mcp.apify.com?tools=phaux/ats-jobs-greenhouse-lever-ashby` to any MCP client (Claude, Cursor, VS Code and others) and call it as a tool.
- **API:** `POST https://api.apify.com/v2/acts/phaux~ats-jobs-greenhouse-lever-ashby/run-sync-get-dataset-items` with the input JSON returns the jobs directly.
- No field is required by the schema (give at least one board), and each field's type, default and meaning are documented in the input schema, so agents can build valid inputs without guessing.

### Data source and compliance

- Only public, unauthenticated, documented job-posting APIs are used. Companies publish these listings so that people can find and apply to them.
- Requests go only to the three official API hosts. Any other host, including redirects, is refused.
- Requests are paced: at least 0.5 s between requests to the same API host, and at most 4 boards at once. Errors are retried with backoff, and `Retry-After` is respected.
- Recruiter and hiring-manager contact details are removed. No candidate or employee data is collected.
- You are responsible for how you use the data, for example honoring each company's terms when you republish listings.

### Limits

- Greenhouse, Lever and Ashby only. Workday, SmartRecruiters, iCIMS and other ATS platforms aren't supported.
- Company-hosted careers pages aren't crawled; give the ATS board token or URL instead.
- The data is whatever each company publishes on its board, at the moment of the run.

### FAQ

- **A company isn't found.** Check the token in its careers URL. Ashby tokens can be case-sensitive. Lever EU boards are detected automatically.
- **Zero jobs from a valid board.** The company currently has no open postings. Nothing is charged.
- **Can I schedule it?** Yes. Use an Apify schedule with `postedSince: "24 hours"` to get only new jobs each day.
- **Something isn't working?** Open an issue on the Issues tab with the run ID.

# Actor input Schema

## `greenhouseBoards` (type: `array`):

Board tokens (e.g. 'stripe') or URLs like https://boards.greenhouse.io/stripe or https://job-boards.greenhouse.io/stripe.

## `leverCompanies` (type: `array`):

Lever site names (e.g. 'spotify') or URLs like https://jobs.lever.co/spotify. EU-hosted sites are found automatically.

## `ashbyBoards` (type: `array`):

Ashby board names (e.g. 'linear') or URLs like https://jobs.ashbyhq.com/linear.

## `careerUrls` (type: `array`):

Mixed Greenhouse / Lever / Ashby board URLs; the platform is detected from the URL. Company-domain career pages are not scraped. Example: \["https://boards.greenhouse.io/stripe", "https://jobs.lever.co/spotify", "https://jobs.ashbyhq.com/linear"].

## `keyword` (type: `string`):

Comma-separated; a job matches if its title contains any of them (case-insensitive). Example: "engineer".

## `keywordInDescription` (type: `boolean`):

Off: keywords match the job title only. On: also match the description text. Default: false.

## `location` (type: `string`):

Comma-separated; matches any location of the job (e.g. 'London, New York, remote').

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

Keep only jobs the platform marks remote (Lever/Ashby) or whose location/office says remote (Greenhouse). Default: false.

## `department` (type: `string`):

Comma-separated, e.g. 'engineering, data'.

## `postedSince` (type: `string`):

A date (2026-10-01) or a period ('24 hours', '7 days', '2 weeks'). Uses the posted date, or the updated date where the platform gives no posted date. Example: "2026-10-01".

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

Turn off for smaller, faster output without descriptionHtml/descriptionText. Default: true.

## `maxItemsPerBoard` (type: `integer`):

Maximum number of jobs saved from each board, after filters. 0 = no limit. Default: 0.

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

0 = no limit. You are only charged for jobs actually saved. Default: 0.

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

How many boards to fetch at once (1-8). Default: 4.

## `minRequestIntervalSecs` (type: `string`):

Politeness delay between requests to the same API host. Default: "0.5".

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

Not needed: these are official public APIs. Leave off.

## Actor input object example

```json
{
  "greenhouseBoards": [
    "stripe",
    "discord"
  ],
  "leverCompanies": [
    "spotify"
  ],
  "ashbyBoards": [
    "linear"
  ],
  "keywordInDescription": false,
  "remoteOnly": false,
  "includeDescription": true,
  "maxItemsPerBoard": 0,
  "maxItems": 0,
  "maxConcurrency": 4,
  "minRequestIntervalSecs": "0.5",
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All jobs saved by the run (default dataset, overview view).

## `runStats` (type: `string`):

RUN_STATS record with per-board 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 = {
    "greenhouseBoards": [
        "stripe",
        "discord"
    ],
    "leverCompanies": [
        "spotify"
    ],
    "ashbyBoards": [
        "linear"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("phaux/ats-jobs-greenhouse-lever-ashby").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 = {
    "greenhouseBoards": [
        "stripe",
        "discord",
    ],
    "leverCompanies": ["spotify"],
    "ashbyBoards": ["linear"],
}

# Run the Actor and wait for it to finish
run = client.actor("phaux/ats-jobs-greenhouse-lever-ashby").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 '{
  "greenhouseBoards": [
    "stripe",
    "discord"
  ],
  "leverCompanies": [
    "spotify"
  ],
  "ashbyBoards": [
    "linear"
  ]
}' |
apify call phaux/ats-jobs-greenhouse-lever-ashby --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,phaux/ats-jobs-greenhouse-lever-ashby"
        }
    }
}
```

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/SzlyprOp1uP6NLSbR/builds/BP0QaCNjE5iyODhKn/openapi.json
