# ATS Jobs Scraper - Greenhouse, Lever, Ashby & Workable API (`offgrid-labs/ats-jobs-feed`) Actor

ATS jobs scraper for Greenhouse jobs, Lever jobs, Ashby jobs and Workable jobs: every open job from company career pages in one unified schema (title, department, location, remote, salary, posted date, apply URL, description). Official job board APIs. $1.50 per 1,000 jobs.

- **URL**: https://apify.com/offgrid-labs/ats-jobs-feed.md
- **Developed by:** [OFFGRID LABS](https://apify.com/offgrid-labs) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 80.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 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.

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

### ATS jobs scraper: Greenhouse, Lever, Ashby and Workable jobs in one format

**TL;DR:** ATS Jobs Scraper collects every open job from company career pages hosted on **Greenhouse, Lever, Ashby and Workable** and returns them in one unified schema: title, department, location, remote flag, salary range (where published), posted date, apply URL and description. It is built for sales teams tracking hiring signals, job boards, recruiters, researchers and AI agents. You pay **$1.50 per 1,000 jobs** saved; boards that aren't found cost nothing.

### Popular use cases

- Hiring-signal tracking: watch target accounts' ATS jobs for sales triggers
- Aggregate Greenhouse jobs, Lever jobs, Ashby jobs and Workable jobs into one job board
- Recruiter market mapping: which companies post which roles where
- Salary-range research from published pay bands

It reads each vendor's **official public job board API** (the same feeds that power the companies' own career pages), so there is no browser, no login and no CAPTCHA.

### What data you get

One dataset row per open job.

| Field | What it is | Example |
|---|---|---|
| `ats`, `company`, `companyName` | ATS vendor, board slug, company name when the ATS provides it | `ashby`, `ashby` |
| `jobId`, `title` | ATS job ID and job title | `Engineering Manager - EU` |
| `department`, `team` | Department and team | `Engineering`, `EMEA Engineering` |
| `location`, `locations[]`, `country` | Primary location, all listed locations, country | `Remote - European Union` |
| `remote`, `workplaceType` | Remote flag and workplace type | `true`, `Remote` |
| `employmentType` | Full-time, part-time, contract... (where provided) | `FullTime` |
| `postedAt`, `updatedAt` | ISO 8601 timestamps (where provided) | `2024-03-04T14:29:08.532Z` |
| `jobUrl`, `applyUrl` | Job page and application page | `https://jobs.ashbyhq.com/ashby/...` |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryInterval`, `salaryText` | Pay range where the company publishes it | `150000`, `250000`, `EUR`, `1 YEAR` |
| `descriptionText`, `descriptionHtml` | Full description as text (default) and optional HTML | `Hi, I'm Colin...` |
| `sourceUrl`, `scrapedAt` | API endpoint used and scrape time | `https://api.ashbyhq.com/posting-api/...` |

Fields an ATS does not publish are `null` (for example, Greenhouse's public API has no structured salary or employment type). A per-company summary (jobs on board, jobs saved, boards not found) is saved in the key-value store record `RUN_SUMMARY`.

| ATS | Board URL pattern | Notes |
|---|---|---|
| Greenhouse | `boards.greenhouse.io/<slug>` | Remote detected from the location text |
| Lever | `jobs.lever.co/<slug>` (EU: `lever-eu:<slug>`) | Remote detected from the location text |
| Ashby | `jobs.ashbyhq.com/<slug>` | Includes published compensation |
| Workable | `apply.workable.com/<slug>` | |

### Use cases

- **Hiring signals for sales and investors**: see which companies are hiring for which teams and where, and track changes over time.
- **Job boards and aggregators**: feed niche job boards with fresh, structured postings straight from the source ATS.
- **Recruiting and talent intelligence**: monitor open roles, locations, remote policies and published salary ranges.
- **Labor market research**: build datasets of roles, departments and compensation across hundreds of companies.

### How to scrape Greenhouse, Lever, Ashby and Workable jobs

1. Add companies to **Companies / job board URLs**, one per line:
   - Board URLs: `https://boards.greenhouse.io/gitlab`, `https://jobs.lever.co/spotify`, `https://jobs.ashbyhq.com/ashby`, `https://apply.workable.com/huggingface`
   - `ats:slug` pairs: `greenhouse:gitlab`, `lever:spotify`, `lever-eu:acme`, `ashby:ashby`, `workable:huggingface`
   - Bare slugs: `gitlab` - the scraper checks all four ATSs and uses the board with the most jobs.
2. Optional filters: **Job title contains**, **Exclude title keyword**, **Keyword scope** (title only by default, or title + description), **Location contains**, **Department or team contains**, **Remote jobs only**, **Posted within last N days**.
3. Click **Start** and download the jobs as JSON, CSV, Excel or HTML.

Tip: explicit board URLs are the most accurate, because some slugs also exist as abandoned boards on another ATS.

### Input example

```json
{
  "companies": ["https://boards.greenhouse.io/gitlab", "lever:spotify", "https://jobs.ashbyhq.com/ashby", "workable:huggingface"],
  "keyword": "engineer",
  "remoteOnly": false,
  "postedWithinDays": 30,
  "maxJobsPerCompany": 0,
  "includeDescription": true
}
```

### Output example

```json
{
  "ats": "ashby",
  "company": "ashby",
  "companyName": null,
  "jobId": "7458d4e9-da2e-47bd-98cb-adfda43d42b2",
  "title": "Engineering Manager - EU",
  "department": "Engineering",
  "team": "EMEA Engineering",
  "location": "Remote - European Union",
  "locations": ["Remote - European Union", "Spain", "Italy"],
  "country": "European Union",
  "remote": true,
  "workplaceType": "Remote",
  "employmentType": "FullTime",
  "postedAt": "2024-03-04T14:29:08.532Z",
  "updatedAt": null,
  "jobUrl": "https://jobs.ashbyhq.com/ashby/7458d4e9-da2e-47bd-98cb-adfda43d42b2",
  "applyUrl": "https://jobs.ashbyhq.com/ashby/7458d4e9-da2e-47bd-98cb-adfda43d42b2/application",
  "salaryMin": 150000,
  "salaryMax": 250000,
  "salaryCurrency": "EUR",
  "salaryInterval": "1 YEAR",
  "salaryText": "€150K – €250K • Offers Equity • Offers Bonus",
  "descriptionText": "Hi, I'm Colin, Director of Engineering, Europe. ...",
  "descriptionHtml": null,
  "sourceUrl": "https://api.ashbyhq.com/posting-api/job-board/ashby?includeCompensation=true",
  "scrapedAt": "2026-11-03T00:55:20.365Z"
}
```

The dataset has two views: **Overview - one row per job** and **All fields - jobs with descriptions**.

### How much does it cost to scrape ATS job boards?

Pay per event: you pay only for jobs saved after your filters. Compute and proxy costs are included. The price is the same on every Apify plan.

| Event | Charged when | Price |
|---|---|---|
| `result` | One job saved | **$1.50 per 1,000** ($0.0015 each) |
| `apify-actor-start` | Once per run (Apify's standard start fee) | $0.00005 |

**Worked examples**

- **1,000 jobs** = 1,000 × $0.0015 = **$1.50** (+ $0.00005 start fee).
- 50 companies × 40 matching jobs = 2,000 jobs = **$3.00**.
- Weekly refresh of the same 2,000 jobs for a month (4 runs) = **$12.00**; use **Posted within last N days** = 7 to pay only for new postings.
- Companies whose board is not found and jobs removed by filters are not charged.

Set **Maximum cost per run** in the run options; the scraper stops cleanly at the limit.

### How it compares

| | This actor | Calling each ATS API yourself | Scraping career pages with a browser |
|---|---|---|---|
| ATS coverage | Greenhouse, Lever (US + EU), Ashby, Workable | One integration per vendor | Per-site selectors |
| Schema | One unified schema, `null` where a field doesn't exist | 4 different formats to map | Whatever the page shows |
| Salary, remote, posted date | Normalized fields where published | You parse each format | Often missing |
| ATS auto-detection from a slug | Yes | No | No |
| Cost | $1.50 per 1,000 jobs, no compute bill | Your time + hosting | Browser compute per page |

### FAQ

#### Which ATS platforms are supported?

Greenhouse, Lever (US and EU instances), Ashby and Workable public job boards. Workday and SmartRecruiters are not supported yet.

#### How do I find a company's Greenhouse, Lever, Ashby or Workable slug?

Open its careers page and look at the job links: `boards.greenhouse.io/<slug>`, `jobs.lever.co/<slug>`, `jobs.ashbyhq.com/<slug>` or `apply.workable.com/<slug>`. Or paste that URL directly.

#### Can I get salary ranges from job postings?

Yes, where the company publishes them through the ATS: Ashby, Lever and Workable expose structured pay ranges for many postings (`salaryMin`, `salaryMax`, `salaryCurrency`, `salaryInterval`). Greenhouse's public API has no structured salary field.

#### How do I get only new jobs?

Set **Posted within last N days** (for example 7 for a weekly schedule). Jobs without a posted date are excluded when this filter is set.

#### How do I get only remote jobs?

Turn on **Remote jobs only**. Ashby and Workable mark remote jobs explicitly; for Greenhouse and Lever the scraper uses "remote" in the location text.

#### Is it legal to scrape ATS job boards?

The scraper uses the job board APIs that these ATS vendors publish for displaying public job listings, and only reads publicly posted jobs. Job descriptions can mention recruiters or hiring managers by name; if you store or process that, you are the data controller and must follow applicable privacy law (for example GDPR).

#### Why are some jobs missing a posted date?

Not every ATS exposes it for every posting. Such jobs are excluded only when you use **Posted within last N days**.

#### Can an AI agent use this jobs API?

Yes. Through the Apify MCP server an agent can find it with `search-actors`, read the input schema and run it, for example "list remote engineering jobs at these 20 companies". See **Integrations**.

#### Something wrong?

Open an issue on the Issues tab; we aim to respond within 24 hours.

### Integrations

- **API**: `POST https://api.apify.com/v2/acts/offgrid-labs~ats-jobs-feed/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>` with the input JSON as the body returns the jobs directly (runs under 5 minutes; otherwise start a run and read its dataset).
- **Python / JavaScript**: `pip install apify-client` / `npm install apify-client`, then `client.actor("offgrid-labs/ats-jobs-feed").call(run_input={...})`.
- **No-code**: Apify's integrations for **Make**, **Zapier** and **n8n**, plus Google Sheets, Slack, webhooks and Schedules (for example a weekly job feed into a sheet).
- **MCP / AI agents**: add `https://mcp.apify.com?tools=offgrid-labs/ats-jobs-feed` to your MCP client to expose this actor as a tool.

### Changelog

- **2026-11-06** · `keywordScope` input: keyword and exclude-keyword filters can now match the full description text too (`title+description`); the default `title` scope keeps previous results byte-identical. Jobs filtered out are never saved or charged.
- **2026-11-06** · `excludeKeyword` input: drop jobs whose title contains a keyword (for example `senior`); excluded jobs are not charged.
- **2026-11-03** · Default memory raised from 256 MB to 512 MB after an out-of-memory failure on a scheduled run with large boards.
- **2026-11-03** · Output schema with Overview and All fields views; Store title aligned with search intent.
- **2026-11-03** · First release: Greenhouse, Lever (US/EU), Ashby and Workable public board APIs, ATS auto-detection, title/location/department/remote/date filters, `RUN_SUMMARY`.

### Related actors

| Actor | What it does |
|---|---|
| Tech Stack Detector | Detect the CMS, e-commerce platform, analytics and CDN of the companies you track |
| Company Registry Lookup (KYB) | Official company records (UK, France, Norway, Finland) for the employers you track |
| Shopify Scraper | Products, variants and prices from any Shopify store |

***

```
// OFFGRID LABS ─────────────────────────────────────────────
// status: monitored 24/7 by a self-hosted agent swarm
// health checks: every 6h · auto-repair + rollback on failure
// sources: official public APIs and open data only
// support: open an issue — human-reviewed response within 24h
// ───────────────────────────────────────────────────────────
```

# Actor input Schema

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

Required. One company per line, in any of 3 formats: (1) job board URL - https://boards.greenhouse.io/gitlab, https://jobs.lever.co/spotify, https://jobs.ashbyhq.com/ashby, https://apply.workable.com/huggingface; (2) ats:slug pair - greenhouse:gitlab, lever:spotify, lever-eu:acme, ashby:ashby, workable:huggingface; (3) bare slug - gitlab (checks all 4 ATSs and uses the board with the most jobs). Board URLs are the most accurate. Boards not found are not charged.

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

Maximum jobs saved per company after filters; one job = one dataset row = one charged result. 0 (default) = all open jobs. Example: 100

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

Optional. Keep only jobs whose title contains this text (case-insensitive substring). Non-matching jobs are not charged. Example: engineer

## `excludeKeyword` (type: `string`):

Optional. Drop jobs whose title contains this text (case-insensitive substring); applied before other filters, excluded jobs are not charged. Example: senior

## `keywordScope` (type: `string`):

Where 'Job title contains' and 'Exclude title keyword' match. 'title' (default) matches only the job title (unchanged behavior). 'title+description' also searches the full description text. Filtered-out jobs are not charged.

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

Optional. Keep only jobs whose location or country contains this text (case-insensitive). Examples: London, Germany, Remote

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

Optional. Keep only jobs whose department or team contains this text (case-insensitive). Example: Sales

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

If true, keep only jobs the ATS marks as remote (for Greenhouse and Lever: 'remote' in the location text). Default false.

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

Optional. Keep only jobs posted in the last N days; jobs without a posted date are excluded when this is set. 0 (default) = any date. Example: 7

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

If true (default), adds the full job description as plain text in descriptionText.

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

If true, also adds the original HTML description in descriptionHtml. Default false; makes the dataset larger.

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

Optional. Not needed for these public job board APIs in normal use (default: no proxy).

## Actor input object example

```json
{
  "companies": [
    "https://boards.greenhouse.io/gitlab",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ashby",
    "https://apply.workable.com/huggingface"
  ],
  "maxJobsPerCompany": 5,
  "keywordScope": "title",
  "remoteOnly": false,
  "postedWithinDays": 0,
  "includeDescription": true,
  "includeDescriptionHtml": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All job rows with every field, including descriptions and salary fields.

## `overview` (type: `string`):

Compact table: company, title, department, location, remote, employment type, salary text, posted date, job link and ATS.

# 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://boards.greenhouse.io/gitlab",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ashby",
        "https://apply.workable.com/huggingface"
    ],
    "maxJobsPerCompany": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("offgrid-labs/ats-jobs-feed").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://boards.greenhouse.io/gitlab",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ashby",
        "https://apply.workable.com/huggingface",
    ],
    "maxJobsPerCompany": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("offgrid-labs/ats-jobs-feed").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://boards.greenhouse.io/gitlab",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ashby",
    "https://apply.workable.com/huggingface"
  ],
  "maxJobsPerCompany": 5
}' |
apify call offgrid-labs/ats-jobs-feed --silent --output-dataset

```

## MCP server setup

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

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/jkYur26dKi2RtVcxO/builds/g7CUkeVTqB8ZNy86y/openapi.json
