# ATS Jobs Scraper — Greenhouse, Lever, Ashby, SmartRecruiters (`quarry/ats-jobs-scraper`) Actor

Pull every open role from a company's real applicant tracking system in one unified schema. No proxies, no blocks, no HTML parsing.

- **URL**: https://apify.com/quarry/ats-jobs-scraper.md
- **Developed by:** [Quarry](https://apify.com/quarry) (community)
- **Categories:** Jobs, Business, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 job scrapeds

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 & SmartRecruiters in one schema

Pull every open role straight from a company's real applicant tracking system — the same system its careers page runs on. One input, one output schema, four ATS platforms.

No proxies. No blocks. No HTML parsing. These are public product APIs, so the data is complete and the actor doesn't break when a website gets redesigned.

### Sample output

```json
{
  "id": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "provider": "ashby",
  "board": "ramp",
  "company": "Ramp",
  "title": "Security Engineer, Cloud",
  "location": "New York, NY (HQ)",
  "remote": true,
  "department": "Engineering",
  "employmentType": "FullTime",
  "postedAt": "2026-04-07T17:12:35.753+00:00",
  "updatedAt": null,
  "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
  "descriptionText": "ABOUT RAMP\n\nRamp is building the smart infrastructure for finance teams…"
}
```

Every row has the same 13 fields regardless of which ATS it came from. Write your pipeline once.

### What you can do with it

- **Hiring-intent leads** — companies opening roles in a function are companies with budget. Filter by keyword and department to find them the week they start hiring.
- **Job boards and aggregators** — build a niche board from the source, with full descriptions and apply links, instead of re-scraping someone else's board.
- **Competitive and market research** — track headcount plans, new office locations, and which teams a competitor is growing.
- **Recruiting** — monitor target companies for openings that match your candidates.

### Input

| Field | What it does |
|---|---|
| `boards` | **Required.** `"greenhouse:stripe"`, `"lever:leverdemo"`, `"ashby:ramp"`, `"smartrecruiters:SGS"`. Pass a bare slug like `"stripe"` and every provider is tried. |
| `keywords` | Keep only roles matching any term, in title, department or description. |
| `locations` | Keep only roles whose location contains any of these. |
| `remoteOnly` | Keep only roles the ATS flags as remote. |
| `postedAfter` | ISO date. Keep only roles posted or updated since. |
| `includeDescription` | Full description as clean plain text. On by default. Free on Greenhouse, Lever and Ashby; on SmartRecruiters it costs one extra request per role, because SmartRecruiters' listing endpoint carries no description at all — a 4,000-role board takes a couple of extra minutes. Turn it off if you only want the metadata. |
| `maxJobs` | Stop after N rows. `0` = no limit. |
| `concurrency` | Boards fetched in parallel, 1–10. Default 5. |

Minimal run:

```json
{ "boards": ["greenhouse:stripe"] }
```

Hiring-intent lead run:

```json
{
  "boards": ["greenhouse:stripe", "ashby:ramp", "lever:leverdemo"],
  "keywords": ["sales", "account executive"],
  "postedAfter": "2026-07-01"
}
```

### Finding a company's board slug

Open the company's careers page and look at the URL:

| Careers URL | Use |
|---|---|
| `boards.greenhouse.io/stripe` | `greenhouse:stripe` |
| `jobs.lever.co/leverdemo` | `lever:leverdemo` |
| `jobs.ashbyhq.com/ramp` | `ashby:ramp` |
| `jobs.smartrecruiters.com/SGS` | `smartrecruiters:SGS` |

Don't know which one they use? Pass the bare slug — every provider is tried and only the one that answers returns rows.

### Scale

One large board returns hundreds of roles in under a second: Stripe 548, Lever demo 388, Ramp 126, SGS 4,219. Pass a list of companies and a single run returns tens of thousands of rows.

### Reliability

Every run reports what happened per board, in a `RUN_STATS` record:

- `ok` — board returned rows
- `empty` — board is healthy but has no open roles right now
- `drift` — the payload had records but the parser produced none
- `not_found` — that provider has no such board (expected when you pass a bare slug)
- `transport` / `rate_limited` — the provider did not answer

That third one matters. Most scrapers return an empty dataset whether the site changed or the company simply isn't hiring, and you can't tell which. This one tells you.

**No board answered means the run fails.** A typo'd slug used to finish green with zero rows forever; now it raises with the reason. One bad board among several still succeeds — that is resilience, not breakage — and a real company with no open roles is still a successful, empty run.

When a run legitimately matches nothing, the dataset gets one unbilled receipt row rather than being left empty:

```json
{ "type": "no_changes", "boardsChecked": 1, "boardsAttempted": 1, "jobsSeen": 4257, "checkedAt": "2026-08-05T12:16:28.133Z" }
```

Job rows never carry a `type` field, so your pipeline can branch on it.

### Pricing

Pay per job returned. You're charged for rows you actually receive — empty boards and failed lookups cost nothing.

### Limits

- Public job postings only. No salary data unless the ATS publishes it, and no applicant or private data.
- `remote` is only set when the provider exposes it. Roughly a third of rows carry it; the rest are `null` rather than a guess.
- `updatedAt` is only published by Greenhouse. Every other provider returns `null`; `postedAt` is populated everywhere.
- SmartRecruiters descriptions come from the per-posting detail record. Where a company left those sections blank in its ATS — some do — the field is `null` because there is nothing to return.
- Workable is not currently supported — its public listing endpoint stopped returning jobs (verified 2026-08-02).

# Actor input Schema

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

Boards to pull. Use "provider:board" (e.g. greenhouse:stripe) or just the company slug to try every provider. The slug is the last path segment of a company's careers URL.

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

Keep only jobs matching any of these. Leave empty for all roles.

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

Keep only jobs whose location contains any of these strings.

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

Keep only roles the provider flags as remote.

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

ISO date, e.g. 2026-07-01. Keeps only roles posted or updated since.

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

Fetch full job descriptions as plain text. Free on Greenhouse, Lever and Ashby. SmartRecruiters publishes no description in its listing endpoint, so each role there needs one extra request — a 4,000-role SmartRecruiters board takes a couple of minutes longer. Turn this off if you only want the metadata.

## `maxJobs` (type: `integer`):

Stop after this many rows. 0 means no limit.

## `concurrency` (type: `integer`):

Boards fetched in parallel.

## Actor input object example

```json
{
  "boards": [
    "greenhouse:stripe"
  ],
  "remoteOnly": false,
  "includeDescription": true,
  "maxJobs": 0,
  "concurrency": 5
}
```

# 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": [
        "greenhouse:stripe",
        "lever:leverdemo",
        "ashby:ramp"
    ],
    "includeDescription": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("quarry/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": [
        "greenhouse:stripe",
        "lever:leverdemo",
        "ashby:ramp",
    ],
    "includeDescription": True,
}

# Run the Actor and wait for it to finish
run = client.actor("quarry/ats-jobs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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": [
    "greenhouse:stripe",
    "lever:leverdemo",
    "ashby:ramp"
  ],
  "includeDescription": true
}' |
apify call quarry/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=quarry/ats-jobs-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/0lKMTg9fCnQCvdR1O/builds/NAedXgHiE88ewGbyI/openapi.json
