# Greenhouse, Lever, Ashby & Workable Jobs Scraper API (`sampon/ats-job-board-jobs`) Actor

Job board scraper for Greenhouse, Lever, Ashby and Workable: get every open job from company careers pages, read from each system's public job board API. One row per job in the same fields for all four, with filters and a new-jobs-only mode.

- **URL**: https://apify.com/sampon/ats-job-board-jobs.md
- **Developed by:** [Sampon](https://apify.com/sampon) (community)
- **Categories:** Jobs, Automation
- **Security:** Limited permissions
- **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?

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, Lever, Ashby & Workable Jobs

Many companies host their careers page on Greenhouse, Lever, Ashby or Workable. Each of these systems publishes a public job board feed so the company can show its openings on its own website. This Actor reads those feeds and gives you every open job as one clean table, with the same columns no matter which system the company uses.

Typical uses: tracking who is hiring in your market, feeding a niche job board, watching a list of target companies for new roles, or sizing a team's growth by department.

### What you put in

A list of companies, one per line. Any of these forms works:

| You enter | Example |
|---|---|
| The public board address | `https://job-boards.greenhouse.io/gitlab`, `https://jobs.lever.co/spotify`, `https://jobs.ashbyhq.com/linear`, `https://apply.workable.com/huggingface/` |
| A short name with the system in front | `greenhouse:gitlab`, `lever:spotify`, `lever-eu:companyname`, `ashby:linear`, `workable:huggingface` |
| The company's own careers page | `https://linear.app/careers` |

When you give the company's own careers page, the Actor reads it once and looks for a link to, or an embed of, one of the four boards. That works for most pages that link to their board. Pages that build their job list with their own code may not show the board at all; in that case, open one job on the page, copy the board address from your browser, and use that instead.

```json
{
  "sources": ["https://job-boards.greenhouse.io/gitlab", "lever:spotify", "https://linear.app/careers"],
  "keywords": ["engineer", "designer"],
  "locations": ["remote", "london"],
  "postedWithinDays": 30,
  "maxItemsPerBoard": 100
}
```

Optional settings:

| Setting | What it does |
|---|---|
| `maxItemsPerBoard` | Newest jobs first, up to this many per company. Default 50. |
| `keywords` | Keeps jobs whose title contains one of the words. Case does not matter. |
| `locations` | Keeps jobs whose location (any of them, for multi-location jobs) contains one of the words. |
| `departments` | Keeps jobs whose department or team contains one of the words. |
| `remoteOnly` | Keeps jobs the board marks as remote, or whose location says remote. |
| `postedWithinDays` | Skips jobs first published longer ago than this. |
| `onlyNew` | Returns only jobs that were not on the board at the previous run. See below. |
| `includeDescription` | Adds the job description as plain text. On by default. |
| `maxDescriptionChars` | Cuts long descriptions at a word break. Default 4000, `0` for no limit. |
| `includeGreenhousePay` | Fetches the published pay range for Greenhouse jobs (one extra request per Greenhouse job). |

### What you get

One row per job. The same fields come back for every system; anything a system does not publish is `null`.

```json
{
  "source": "lever",
  "board": "spotify",
  "company": "spotify",
  "jobId": "4852ad53-b028-4161-b53b-24c62759257f",
  "title": "Staff Product Designer - Agentic Experiences",
  "department": "Design and User Experience",
  "team": "Experience",
  "location": "London",
  "locations": ["London", "Stockholm"],
  "country": "GB",
  "remote": true,
  "workplaceType": "remote",
  "employmentType": "Permanent",
  "postedAt": "2026-10-09T17:54:45Z",
  "updatedAt": null,
  "url": "https://jobs.lever.co/spotify/4852ad53-b028-4161-b53b-24c62759257f",
  "applyUrl": "https://jobs.lever.co/spotify/4852ad53-b028-4161-b53b-24c62759257f/apply",
  "salary": null,
  "salaryText": null,
  "description": "We design Spotify's consumer experience end to end, moment to moment, across every screen ...",
  "isNew": null,
  "scrapedAt": "2026-10-10T08:03:50Z"
}
```

Notes on the fields:

- `source` is the system (`greenhouse`, `lever`, `ashby`, `workable`) and `board` the company's name on it.
- `company` is the company name when the system publishes one (Greenhouse, Workable). Lever and Ashby feeds do not include it, so you get the board name instead.
- `postedAt` is the first publication time in UTC. Workable only publishes a date, so you get `YYYY-MM-DD` there. Greenhouse also gives `updatedAt`.
- `salary` holds `min`, `max`, `currency` and `interval` when the company publishes a range: Ashby and Lever include it in the feed, Greenhouse only with `includeGreenhousePay`, Workable does not publish it in its public feed. `salaryText` keeps the range as the company wrote it.
- `isNew` is `true` on every row returned with `onlyNew` turned on, and `null` otherwise.

The output describes jobs, not people. Recruiter or hiring-manager fields are never collected, custom fields are left out, and e-mail addresses, phone numbers and links to personal profiles (LinkedIn, X, GitHub and similar) inside descriptions are replaced with `[email removed]`, `[phone removed]` and `[profile link removed]`. A name written in the running text of a description cannot be removed reliably and stays as written.

### Watching companies for new jobs

Turn on `onlyNew` and schedule the Actor, for example daily. The first run returns everything and remembers which jobs each board had, in a key-value store called `ats-job-board-jobs-seen` in your Apify account. Every later run returns only jobs that were not on the board the previous time. Jobs held back by `maxItemsPerBoard` or by your filters are remembered too, so changing filters later does not flood you with old jobs.

### Price

Free for now. You only pay Apify's usual platform usage, which is small because each company is one request to a feed: in our test on a local machine, the default three companies returned 131 jobs in about 3 seconds. A per-job price may be added later. If that happens, it will be shown on this page before it applies to you.

### How it treats the sources

- It only uses the public, read-only job board feeds that each system documents for showing jobs on a careers page. No login, no private data, no application forms.
- It reads each host's robots.txt first and follows it, including any crawl delay.
- By default it reads two boards at a time and waits at least one second between requests to the same host. On HTTP 429 or a server error it backs off and tries again.

### Limits

- Four systems are supported: Greenhouse, Lever, Ashby and Workable. SmartRecruiters is left out because its feed's robots.txt asks automated tools to stay away; Recruitee is left out because its documentation is contradictory about whether the feed is public.
- Jobs a company has marked as internal or unlisted are not in the public feeds and so are not in the output.
- A board name that does not exist returns nothing; the log says which ones could not be read.
- Greenhouse boards hosted in its EU region have not been tested.

This Actor is an independent tool. It is not made by, endorsed by, or affiliated with Greenhouse, Lever, Ashby, Workable or the companies whose job boards it reads.

# Actor input Schema

## `sources` (type: `array`):

One per line. Accepted: a board URL (job-boards.greenhouse.io/gitlab, jobs.lever.co/spotify, jobs.ashbyhq.com/linear, apply.workable.com/huggingface), a short name (greenhouse:gitlab, lever:spotify, lever-eu:name, ashby:linear, workable:huggingface), or the company's own careers page, which is then searched for an embedded board.

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

Newest jobs come first.

## `keywords` (type: `array`):

Keep only jobs whose title contains one of these words. Case does not matter.

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

Keep only jobs whose location contains one of these words, for example London, Germany or Remote.

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

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

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

Keep only jobs the board marks as remote, or whose location says remote.

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

Skip older jobs. Leave empty to get everything.

## `onlyNew` (type: `boolean`):

Remembers which jobs each board had, in a key-value store named ats-job-board-jobs-seen in your account. The first run returns everything; later runs return only jobs that were not on the board last time.

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

Plain text, with e-mail addresses, phone numbers and personal profile links removed.

## `maxDescriptionChars` (type: `integer`):

Longer descriptions are cut at a word break. 0 means no limit.

## `includeGreenhousePay` (type: `boolean`):

Greenhouse only shows pay ranges on each job's own record, so this makes one extra request per Greenhouse job.

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

Boards read at the same time. Capped at 4.

## `requestDelayMs` (type: `integer`):

A longer crawl delay in the host's robots.txt always wins.

## Actor input object example

```json
{
  "sources": [
    "https://job-boards.greenhouse.io/gitlab",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/linear"
  ],
  "maxItemsPerBoard": 50,
  "remoteOnly": false,
  "onlyNew": false,
  "includeDescription": true,
  "maxDescriptionChars": 4000,
  "includeGreenhousePay": false,
  "maxConcurrency": 2,
  "requestDelayMs": 1000
}
```

# Actor output Schema

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

One row per open job: company, title, department, location, dates, pay when published, and links.

# 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 = {
    "sources": [
        "https://job-boards.greenhouse.io/gitlab",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/linear"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("sampon/ats-job-board-jobs").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 = { "sources": [
        "https://job-boards.greenhouse.io/gitlab",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/linear",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("sampon/ats-job-board-jobs").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 '{
  "sources": [
    "https://job-boards.greenhouse.io/gitlab",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/linear"
  ]
}' |
apify call sampon/ats-job-board-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sampon/ats-job-board-jobs"
        }
    }
}
```

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/fO4FAAsiMGKoSbzyu/builds/9cJNXfTvrPHNwcEB2/openapi.json
