# Multi-ATS Job Scraper (Greenhouse, Lever, Ashby) (`repertory/multi-ats-job-scraper`) Actor

Scrape public job postings from Greenhouse, Lever, Ashby & SmartRecruiters into one clean schema. Detect new postings for hiring-signal sales leads and recruiting market data. No login, no personal data.

- **URL**: https://apify.com/repertory/multi-ats-job-scraper.md
- **Developed by:** [Arush Dhawan](https://apify.com/repertory) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.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

## Multi-ATS Job Scraper — Greenhouse, Lever, Ashby & SmartRecruiters in one schema

Scrape **public job postings across multiple applicant tracking systems (ATS) at once** and get them back in a single, clean, normalized schema. Point it at any company's **Greenhouse**, **Lever**, **Ashby**, or **SmartRecruiters** job board and get every open role with title, location, department, remote flag, apply URL, and posting date — plus a built-in **new-postings monitor** for hiring-signal tracking.

No login, no API keys, no personal data — the Actor reads the same public job feeds companies use to power their own careers pages.

### Two things people use it for

1. **Hiring-signal sales leads.** Which companies are hiring for the roles you sell into? A surge of open reqs is one of the strongest B2B buying signals. Run on a schedule with **"Only new / changed postings"** on and get a feed of fresh openings across your target accounts.
2. **Recruiting & labor-market data.** Map every role a set of companies is hiring for — titles, departments, locations, remote — normalized across ATS platforms so you don't stitch four different formats together.

### Why this one

- **Multi-ATS, one schema.** Most scrapers do a single ATS. This normalizes Greenhouse, Lever, Ashby and SmartRecruiters into identical fields.
- **New-postings / change detection.** Turn on `onlyNew` and it remembers what it saw last run and emits only new or changed roles — a true monitor, not a one-time dump.
- **Reliable & maintenance-free.** It uses official public job-board endpoints, so runs succeed at ~100% with no proxies or anti-bot breakage.
- **Filter at the source.** Keyword-filter by title or location, or restrict to remote, so you pay only for the rows you want.

### Input

| Field | Type | What it does |
|---|---|---|
| `sources` | array | Boards to scrape: `[{ "ats": "greenhouse", "handle": "airbnb" }, …]`. Supported `ats`: `greenhouse`, `lever`, `ashby`, `smartrecruiters`. |
| `onlyNew` | boolean | Emit only new/changed postings vs. the previous run (hiring-signal monitor). |
| `includeDescription` | boolean | Include full plain-text job description. |
| `titleKeywords` | array | Keep only titles containing any term (e.g. `["engineer","data"]`). |
| `locationKeywords` | array | Keep only matching locations. |
| `remoteOnly` | boolean | Keep only remote roles. |
| `maxItemsPerBoard` | integer | Cap results per board (0 = all). |

**Finding a company's handle:** it's the token in its careers URL — `boards.greenhouse.io/<handle>`, `jobs.lever.co/<handle>`, `jobs.ashbyhq.com/<handle>`, `jobs.smartrecruiters.com/<handle>`.

### Output

One record per posting:

```json
{
  "ats": "greenhouse",
  "company": "Airbnb",
  "jobId": "7995153",
  "title": "Acquisition Manager",
  "department": "Sales",
  "location": "Berlin, Germany",
  "remote": null,
  "employmentType": null,
  "url": "https://careers.airbnb.com/positions/7995153",
  "postedAt": "2026-06-10T12:50:56.000Z",
  "updatedAt": "2026-07-31T11:59:51.000Z",
  "compensation": null,
  "isNew": true,
  "isChanged": false,
  "scrapedAt": "2026-08-10T00:00:00.000Z"
}
```

### Pricing

Pay-per-event: you pay per posting returned, plus a small event when a **new** posting is first detected. Filter by keyword/location/remote to control cost. No monthly fee.

### Notes

Reads only public, non-personal job-posting facts (no logins, no candidate data). More ATS platforms are added over time.

# Actor input Schema

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

List of ATS job boards to fetch. Each item is an object with `ats` and `handle`. Supported `ats`: greenhouse, lever, ashby, smartrecruiters. The `handle` is the company token in the careers URL, e.g. boards.greenhouse.io/<handle>, jobs.lever.co/<handle>, jobs.ashbyhq.com/<handle>, jobs.smartrecruiters.com/<handle>.

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

Emit only postings that are new or changed since the previous run with the same sources. Turns the Actor into a scheduled hiring-signal / freshness monitor. Uses a named key-value store to remember what it has seen.

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

Include the full plain-text job description in each record. Produces larger output.

## `titleKeywords` (type: `array`):

Keep only postings whose title contains any of these terms (case-insensitive). Empty = keep all. Example: \["engineer", "data"].

## `locationKeywords` (type: `array`):

Keep only postings whose location contains any of these terms (case-insensitive). Empty = keep all.

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

Keep only postings detected as remote.

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

Cap results per board (0 = no cap).

## `dedupeStoreName` (type: `string`):

Named key-value store for cross-run state when 'Only new' is on. Defaults to a hash of your sources, so each source set tracks its own history.

## Actor input object example

```json
{
  "sources": [
    {
      "ats": "greenhouse",
      "handle": "airbnb"
    },
    {
      "ats": "lever",
      "handle": "leverdemo"
    },
    {
      "ats": "ashby",
      "handle": "ramp"
    }
  ],
  "onlyNew": false,
  "includeDescription": false,
  "titleKeywords": [],
  "locationKeywords": [],
  "remoteOnly": false,
  "maxItemsPerBoard": 0
}
```

# Actor output Schema

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

No description

# 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": [
        {
            "ats": "greenhouse",
            "handle": "airbnb"
        },
        {
            "ats": "lever",
            "handle": "leverdemo"
        },
        {
            "ats": "ashby",
            "handle": "ramp"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("repertory/multi-ats-job-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 = { "sources": [
        {
            "ats": "greenhouse",
            "handle": "airbnb",
        },
        {
            "ats": "lever",
            "handle": "leverdemo",
        },
        {
            "ats": "ashby",
            "handle": "ramp",
        },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("repertory/multi-ats-job-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 '{
  "sources": [
    {
      "ats": "greenhouse",
      "handle": "airbnb"
    },
    {
      "ats": "lever",
      "handle": "leverdemo"
    },
    {
      "ats": "ashby",
      "handle": "ramp"
    }
  ]
}' |
apify call repertory/multi-ats-job-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,repertory/multi-ats-job-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/wrpcvRozFFeGQhm5K/builds/HzJlNHcfluRfcVMsR/openapi.json
