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

Scrape live job postings straight from company career pages on Greenhouse, Lever and Ashby. One unified schema across all three, deduplicated, with salary ranges parsed into real numbers. HTTP-only and fast. $6 per 1,000 jobs.

- **URL**: https://apify.com/quarrytools/ats-jobs-scraper.md
- **Developed by:** [Aabis Hussain](https://apify.com/quarrytools) (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

$6.00 / 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/platform/actors/running/actors-in-store#pay-per-event

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

## ATS Jobs Scraper - Greenhouse, Lever & Ashby

Get **real job postings straight from company career pages** - not from a job-board aggregator that lags days behind, republishes stale roles, and strips out half the fields.

This Actor reads the official public job APIs of **Greenhouse**, **Lever** and **Ashby**, and returns everything in **one consistent schema**, deduplicated, with **salary ranges parsed into real numbers**.

**$6 per 1,000 jobs.** No subscription, no minimum, and no charge for rows you filtered out.

### Why scrape ATS boards instead of job boards?

| | Job-board aggregators | This Actor |
|---|---|---|
| **Freshness** | Hours to days behind | The moment the company publishes |
| **Accuracy** | Reposts, ghost jobs, expired roles | Live board state - if it's gone, it's gone |
| **Apply link** | Redirects and trackers | Direct link to the company's own application |
| **Salary** | Usually missing | Parsed into salaryMin / salaryMax / salaryCurrency |
| **Duplicates** | Same role many times | Removed by default |

If a company posts a role at 9am, this Actor sees it at 9am.

### What you get

- **Three ATS platforms, one schema.** Greenhouse, Lever and Ashby return wildly different shapes. You get identical field names across all of them - merge without writing glue code.
- **Auto-detection.** Don't know which ATS a company uses? Give the company slug and it works it out.
- **Salary parsing.** Handles `$150,000 - $200,000`, `£80k - £100k`, `USD 120,000 to 160,000`, hourly rates, and Ashby's structured compensation data. Period normalised to hourly / daily / weekly / monthly / yearly.
- **Deduplication by default.** Companies list one role under five locations. You are not charged for the copies.
- **Real filters** - title, location, department, remote-only, has-salary-only, posted-after - all applied **before billing**.
- **HTTP-only.** No headless browser, no proxies, no cookies. Fast runs and a low price, because there is no browser overhead to pass on to you.
- **Agent-ready.** Pay-per-event pricing and limited permissions, so it works inside automated and agentic workflows.

### Input

Point it at companies using the **board token** - the company slug in its careers URL:

| Careers URL | Board token |
|---|---|
| boards.greenhouse.io/**stripe** | `stripe` |
| jobs.lever.co/**palantir** | `palantir` |
| jobs.ashbyhq.com/**ashby** | `ashby` |

```json
{
  "companies": ["stripe", "lever:palantir", "ashby:ashby"],
  "includeDescription": true,
  "titleIncludes": ["engineer"],
  "postedAfter": "2026-08-01",
  "maxItems": 500
}
```

Platform is auto-detected. Prefix with `greenhouse:`, `lever:` or `ashby:` to force it and skip the lookup.

#### Filters

| Field | Does |
|---|---|
| titleIncludes / titleExcludes | Keep or drop by job title |
| locationIncludes | Match against every listed location |
| departmentIncludes | Match department or team |
| remoteOnly | Only remote roles |
| hasSalaryOnly | Only roles where a salary was found |
| postedAfter | Only roles published on/after a date - **ideal for daily scheduled runs** |
| maxItems / maxItemsPerCompany | Hard caps so one big employer cannot eat the run |

### Output

```json
{
  "jobId": "5061642004",
  "source": "greenhouse",
  "companyToken": "stripe",
  "companyName": "Stripe",
  "title": "Backend Engineer, Payments",
  "department": "Engineering",
  "team": "Payments",
  "employmentType": "Full-time",
  "location": "London, United Kingdom",
  "locations": ["London, United Kingdom", "Dublin, Ireland"],
  "country": "GB",
  "isRemote": false,
  "workplaceType": "hybrid",
  "postedAt": "2026-08-04T09:12:00.000Z",
  "updatedAt": "2026-08-08T14:01:00.000Z",
  "salaryMin": 120000,
  "salaryMax": 165000,
  "salaryCurrency": "GBP",
  "salaryPeriod": "yearly",
  "salaryRaw": "£120,000 - £165,000",
  "jobUrl": "https://boards.greenhouse.io/stripe/jobs/5061642004",
  "applyUrl": "https://boards.greenhouse.io/stripe/jobs/5061642004",
  "description": "About the role...",
  "scrapedAt": "2026-08-10T12:00:00.000Z"
}
```

Export to **JSON, CSV, Excel, XML** or pull via API.

### What people use it for

- **Recruiting and talent intelligence** - track exactly who your competitors are hiring, in which teams, at what salary
- **Job boards and aggregators** - a clean, fresh supply feed you own, without licensing an aggregator
- **Market research** - headcount direction, salary benchmarking, geographic expansion signals
- **Sales prospecting** - hiring is a buying signal; a company staffing a new function has new budget
- **Job-seeker tools** - power alerts and personalised feeds from source-of-truth data

### Pricing

**$6 per 1,000 jobs** ($0.006 per job returned).

You are charged **only for rows delivered to your dataset** - never for duplicates removed, rows excluded by filters, or boards that returned nothing.

> Tip: run with `includeDescription: false` for a fast, lightweight sweep, then re-run on the shortlist with descriptions on. Same price per row either way, but far quicker.

### Notes and limits

- Only **public** job boards are read. Nothing private, nothing authenticated, no logins, no cookies.
- The board token is the **slug in the careers URL**, not the company's display name. `Stripe, Inc.` becomes `stripe`. This is the single most common cause of an empty result.
- If a company does not use Greenhouse, Lever or Ashby, it will not be found. More platforms are being added - **tell me which one you need and I will prioritise it.**
- Ashby exposes structured compensation, so its salary data is the most reliable. Greenhouse and Lever need `includeDescription: true` for salary parsing, since the figures live in the description text.
- Every run writes a `RUN_SUMMARY` record to the key-value store: rows pushed, duplicates removed, rows filtered, and any board tokens that could not be resolved.

### Feedback

Missing a field? Need another ATS? Hit an edge case in salary parsing? **Open an issue on this Actor** - I read every one and ship fixes fast.

# Actor input Schema

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

The company slug from its careers URL. boards.greenhouse.io/<b>stripe</b> gives <code>stripe</code>. Platform is auto-detected, or force it with a prefix: <code>lever:palantir</code>, <code>ashby:ashby</code>, <code>greenhouse:stripe</code>.

## `platform` (type: `string`):

Leave on Auto-detect unless every company uses the same ATS - forcing it makes runs faster.

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

Full plain-text description. Turn off for faster runs - but salary parsing needs the description, so salary fields will be empty for Greenhouse and Lever when this is off.

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

Hard cap across all companies. 0 = no limit.

## `maxItemsPerCompany` (type: `integer`):

Keeps one large employer from consuming the whole run. 0 = no limit.

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

Only jobs published on or after this date (YYYY-MM-DD). Ideal for daily scheduled runs.

## `titleIncludes` (type: `array`):

Case-insensitive substring match, e.g. engineer, designer.

## `titleExcludes` (type: `array`):

Case-insensitive. Useful for stripping senior, intern, manager.

## `locationIncludes` (type: `array`):

Matches against all listed locations, e.g. london, united states.

## `departmentIncludes` (type: `array`):

e.g. engineering, sales.

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

Uses each platform's remote flag plus location text.

## `hasSalaryOnly` (type: `boolean`):

Keep only rows where a salary range was found.

## `deduplicate` (type: `boolean`):

Companies often list one role under several locations. Leave on - you are not charged for removed duplicates.

## Actor input object example

```json
{
  "companies": [
    "stripe",
    "lever:palantir",
    "ashby:ashby"
  ],
  "platform": "auto",
  "includeDescription": true,
  "maxItems": 0,
  "maxItemsPerCompany": 0,
  "remoteOnly": false,
  "hasSalaryOnly": false,
  "deduplicate": true
}
```

# Actor output Schema

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

Every job posting found across the requested ATS boards, in a single unified schema: company, title, department, location, remote flag, parsed salary range with currency and period, posting dates and the canonical apply URL.

## `compensation` (type: `string`):

The same job feed narrowed to the compensation columns - parsed salary min, max, currency, period and the raw matched salary text - for pay benchmarking and comp-data workflows.

# 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": [
        "stripe",
        "lever:palantir",
        "ashby:ashby"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("quarrytools/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 = { "companies": [
        "stripe",
        "lever:palantir",
        "ashby:ashby",
    ] }

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

```

## MCP server setup

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