# ATS Jobs Scraper — Greenhouse, Lever, Ashby & SmartRecruiters (`fabrikit/ats-jobs-api`) Actor

Scrape job postings from Greenhouse, Lever, Ashby & SmartRecruiters into one normalized JSON feed. Auto-detects the ATS, dedupes, filters remote. No proxies.

- **URL**: https://apify.com/fabrikit/ats-jobs-api.md
- **Developed by:** [Fabrikit](https://apify.com/fabrikit) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.20 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 & SmartRecruiters

Scrape job postings from any company that hires through **Greenhouse, Lever, Ashby or SmartRecruiters** and get them back as **one clean, normalized dataset** — the same 21 fields whether the job came from a Greenhouse board or a Lever posting. Feed it company slugs (or just company names — the Actor auto-detects the ATS), and export to JSON, CSV, Excel, Google Sheets or your database.

What you get from a single run:

- **Every open job at every company you list** — title, location, remote flag, department, employment type, salary range when published, direct apply URL and posting dates — deduplicated, in one schema.
- **Zero setup and zero breakage risk**: the Actor reads the official public job-board APIs of the four ATS vendors, not their HTML. No API key, no login, no proxies, no browser — runs finish in seconds and don't break when a careers page changes its CSS.
- **A per-company summary** (jobs found, which ATS was detected, any errors) in the run's key-value store, so pipelines can verify coverage automatically.

Example output item (real Ashby posting):

```json
{
    "ats": "ashby",
    "company": "ramp",
    "sourceId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
    "title": "Security Engineer, Cloud",
    "location": "New York, NY (HQ)",
    "allLocations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)", "Miami, FL"],
    "remote": true,
    "workplaceType": "hybrid",
    "department": "Engineering",
    "team": "Backend",
    "employmentType": "FullTime",
    "url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
    "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
    "publishedAt": "2026-04-07T17:12:35.753Z"
}
```

### How to scrape Greenhouse jobs (or Lever, Ashby, SmartRecruiters)

1. Add one entry per company to **companies**. Two formats work:
   - `ats:slug` when you know the platform — `greenhouse:stripe`, `lever:spotify`, `ashby:ramp`, `smartrecruiters:Devoteam`.
   - A bare slug (e.g. `stripe`) — the Actor probes all four platforms and picks the one that has the company's board. No need to know which ATS they use.
2. Press **Start**. Results land in the dataset within seconds and can be downloaded as JSON, CSV or Excel, or pushed to Sheets, webhooks, Zapier, Make or the Apify API.

The slug is the identifier in the company's careers URL:

| ATS | Careers URL pattern | Example slug |
|---|---|---|
| Greenhouse | `boards.greenhouse.io/<slug>` or `job-boards.greenhouse.io/<slug>` | `stripe` |
| Lever | `jobs.lever.co/<slug>` | `spotify` |
| Ashby | `jobs.ashbyhq.com/<slug>` | `ramp` |
| SmartRecruiters | `careers.smartrecruiters.com/<slug>` | `Devoteam` |

#### Example input

```json
{
    "companies": [
        "greenhouse:stripe",
        "lever:spotify",
        "ashby:ramp",
        "smartrecruiters:Devoteam",
        "some-company-slug"
    ],
    "maxJobsPerCompany": 50,
    "remoteOnly": false,
    "includeDescription": false
}
```

- **companies** *(required)* — list of `ats:slug` or bare slugs (auto-detected).
- **maxJobsPerCompany** — cap per company; `0` = unlimited. The form prefills `50` so your first run stays small and cheap — set it to `0` for full boards.
- **remoteOnly** — only output jobs detected as remote. Instant remote-jobs API for any company.
- **includeDescription** — add the full plain-text job description to each item (larger dataset; not available for SmartRecruiters, whose list API doesn't include it).

#### Scrape remote jobs only

```json
{
    "companies": ["greenhouse:stripe", "ashby:ramp"],
    "remoteOnly": true,
    "maxJobsPerCompany": 0
}
```

### ATS job board API — output fields

Every item has the same fields, regardless of the source ATS:

| Field | Type | Description |
|---|---|---|
| `ats` | string | Source platform: `greenhouse`, `lever`, `ashby`, `smartrecruiters` |
| `company` | string | The company slug you passed in |
| `companyName` | string | Company display name when the API provides it |
| `sourceId` | string | Posting ID in the source ATS |
| `title` | string | Job title |
| `location` | string | null | Primary location as shown on the posting |
| `allLocations` | string\[] | All listed locations/offices |
| `remote` | boolean | null | `true` if detected as remote, `null` if the source doesn't say |
| `workplaceType` | string | null | `remote`, `hybrid`, `onsite`, or `null` |
| `department` | string | null | Department |
| `team` | string | null | Team / function, when the ATS distinguishes it |
| `employmentType` | string | null | e.g. `FullTime`, `Permanent`, `Full-time` |
| `salaryMin` | number | null | Lower bound of salary range (Lever) |
| `salaryMax` | number | null | Upper bound of salary range (Lever) |
| `salaryCurrency` | string | null | Salary currency (Lever) |
| `salaryInterval` | string | null | Salary interval, or Ashby's compensation summary string |
| `url` | string | Public posting URL |
| `applyUrl` | string | Direct application URL |
| `publishedAt` | string | null | ISO 8601 first-published date |
| `updatedAt` | string | null | ISO 8601 last-updated date (Greenhouse) |
| `description` | string | null | Plain-text description (only if `includeDescription`) |
| `scrapedAt` | string | ISO 8601 timestamp of this run |

### What can you build with a jobs scraper?

- **Niche job boards** — remote-only, one industry, one country — fed automatically on a schedule.
- **Recruiting intelligence & talent mapping**: who is hiring for which roles, right now, across your whole target list.
- **Sales & investing signals**: a competitor opening 30 engineering roles is a growth signal you can query daily.
- **Lead generation for recruiters and agencies**: companies actively hiring for the roles you place.
- **Salary benchmarking** from the postings that publish ranges (Lever ranges, Ashby compensation summaries).
- **LLM pipelines**: one consistent schema means one prompt — pipe the dataset into an LLM via Apify integrations or MCP.

### Pricing — pay per result, nothing else

This Actor uses Apify's **pay-per-event** model and charges **per job saved to the dataset**, plus a tiny run-start fee. Platform usage is included: no subscription, no rental fee, no proxy costs — because the Actor talks to official public APIs, runs are fast and compute usage is minimal. From 30 October 2026, 1,000 jobs cost US$2 on the Apify Free plan; paid Apify plans pay less per job — see the **Pricing** tab for your plan's rate. You only pay for jobs actually delivered to your dataset.

### FAQ

**Does Greenhouse have a public API for job postings?**
Yes — Greenhouse, Lever, Ashby and SmartRecruiters all publish official, unauthenticated job-board APIs. This Actor reads those endpoints directly, which is why it needs no API key, no login and no proxies, and why it's far more reliable than scrapers that parse careers-page HTML.

**How do I find a company's board slug?**
Open the company's careers page and look at the URL: `boards.greenhouse.io/stripe` → slug `stripe`; `jobs.lever.co/spotify` → `spotify`; `jobs.ashbyhq.com/ramp` → `ramp`; `careers.smartrecruiters.com/Devoteam` → `Devoteam`. If you're unsure which ATS a company uses, just pass the bare slug and the Actor auto-detects it.

**Can I export the jobs to CSV or Excel?**
Yes. Every Apify dataset can be downloaded as CSV, Excel, JSON, XML or HTML, or synced to Google Sheets — no code needed.

**Is it legal to scrape job postings?**
See *Is it legal to scrape Greenhouse, Lever, Ashby and SmartRecruiters jobs?* below.

**Does it include salary data?**
When the source publishes it: Lever postings expose numeric salary ranges (`salaryMin`/`salaryMax`/`salaryCurrency`), Ashby exposes a compensation summary string. Greenhouse and SmartRecruiters boards rarely publish salary via their list APIs.

**Can it monitor jobs on a schedule?**
Yes — schedule the Actor in Apify Console (hourly, daily, weekly) and connect the dataset to Sheets, a webhook or your ATS of choice. `sourceId` is stable per posting, so downstream dedup across runs is trivial.

**What if a company isn't on any of the four platforms?**
The Actor logs a warning and records it in the run's `SUMMARY` (key-value store) with `"error": "ATS not detected"` — the run continues with the remaining companies.

### Is it legal to scrape Greenhouse, Lever, Ashby and SmartRecruiters jobs?

This Actor only reads job data that the ATS vendors themselves publish through official public APIs intended for exactly this purpose (powering careers pages and job boards), without logging in. The structured fields are about jobs, not people, but full descriptions (`includeDescription`) can mention a hiring manager or recruiter by name, which is personal data under laws such as the EU GDPR, Chile's Ley 21.719 or the California CCPA; you are responsible for a lawful purpose and for respecting each platform's and company's terms. Job descriptions are the employers' content, so link back to the original posting when you republish. This is general information, not legal advice.

### Integrations

Run the ATS Jobs Scraper from the Apify API or the JavaScript and Python clients, push new postings onward with webhooks, or schedule it hourly, daily or weekly in Apify Console. It works with n8n, Make and Zapier (for example, to post new jobs to Slack or append them to Google Sheets), and with LangChain and LlamaIndex through their Apify integrations. AI agents in Claude, ChatGPT, Cursor and other MCP clients can find and run it through Apify's MCP server. Export as JSON, CSV or Excel, or read from the dataset API.

### Use with AI agents

Use this actor to list the open jobs of specific companies that hire through Greenhouse, Lever, Ashby or SmartRecruiters, in one normalized schema; pass `ats:slug` or a bare slug and it detects the platform.

```json
{ "companies": ["greenhouse:stripe", "ashby:ramp"], "maxJobsPerCompany": 20 }
```

Main output fields: `ats`, `company`, `title`, `location`, `remote`, `workplaceType`, `department`, `employmentType`, `salaryMin`, `salaryMax`, `salaryCurrency`, `url`, `applyUrl`, `publishedAt`, and `description` with `includeDescription`. It is pay-per-event: an agent pays a small start fee plus a fixed price per job saved, not for compute time.

### Related actors

- **[Greenhouse Jobs Scraper](https://apify.com/fabrikit/greenhouse-jobs-scraper)** — Greenhouse-only boards with departments, offices and full descriptions.
- **[Seek Jobs Scraper](https://apify.com/fabrikit/seek-jobs-scraper)** — job ads from Seek Australia and New Zealand.
- **[GetOnBrd Jobs Scraper](https://apify.com/fabrikit/getonbrd-jobs-scraper)** — tech jobs in Latin America with salary ranges.
- **[Trabajando.cl Jobs Scraper](https://apify.com/fabrikit/trabajando-cl-jobs-scraper)** — job listings in Chile.
- **[App Reviews Scraper — App Store & Google Play](https://apify.com/fabrikit/app-reviews-unified)** — scrape reviews from both mobile stores in one run and one schema: rating, text, version, helpful votes and developer replies.

#### Ready-made examples

- [Remote jobs from tech company career pages](https://apify.com/fabrikit/ats-jobs-api/examples/remote-jobs-tech-companies)
- [Job postings with full descriptions](https://apify.com/fabrikit/ats-jobs-api/examples/job-descriptions-for-ai)

### Technical details

- Data sources (all official, public, unauthenticated):
  - Greenhouse: `boards-api.greenhouse.io/v1/boards/<slug>/jobs?content=true`
  - Lever: `api.lever.co/v0/postings/<slug>?mode=json` (paginated)
  - Ashby: `api.ashbyhq.com/posting-api/job-board/<slug>` (with compensation data)
  - SmartRecruiters: `api.smartrecruiters.com/v1/companies/<slug>/postings` (paginated)
- Auto-detection probes all four APIs and picks the first platform where the slug has a non-empty board.
- Items are deduplicated within a run by `ats:company:sourceId`.
- Plain HTTP with a 30 s timeout per request; no headless browser, no proxy usage.
- A per-company run summary is stored in the key-value store under `SUMMARY`.

### Changelog

- **2026-10-16** — README: legal notes, integrations, AI-agent usage, related actors and example tasks added; pricing text updated.
- **2026-10-15** — Tiered pricing announced, effective 2026-10-30 (lower per-job price on paid Apify plans).
- **2026-08-21** — Builds 0.1.4–0.1.5: fixed the dataset schema, which rejected items on the platform (it required an `id` field; jobs carry `sourceId`); the input form now pre-fills 50 jobs per company for a quick first run; README rewritten.
- **2026-08-20** — Builds 0.1.1–0.1.3: initial release with Greenhouse, Lever, Ashby and SmartRecruiters connectors and ATS auto-detection; published on the Apify Store.

# Actor input Schema

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

One entry per company. Either `ats:slug` (e.g. `greenhouse:stripe`, `lever:spotify`, `ashby:ramp`, `smartrecruiters:Devoteam`) or just a bare slug (e.g. `stripe`) and the Actor will auto-detect which ATS the company uses by probing all four. The slug is the identifier from the company's careers page URL (e.g. `boards.greenhouse.io/<slug>`, `jobs.lever.co/<slug>`, `jobs.ashbyhq.com/<slug>`, `careers.smartrecruiters.com/<slug>`).

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

Stop after this many jobs per company. 0 = no limit.

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

Only output jobs detected as remote.

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

Add the full plain-text job description to each item. Makes the dataset much larger.

## Actor input object example

```json
{
  "companies": [
    "greenhouse:stripe",
    "lever:spotify",
    "ashby:ramp",
    "smartrecruiters:Devoteam"
  ],
  "maxJobsPerCompany": 50,
  "remoteOnly": false,
  "includeDescription": false
}
```

# Actor output Schema

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

All normalized job postings collected during the run

# 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": [
        "greenhouse:stripe",
        "lever:spotify",
        "ashby:ramp",
        "smartrecruiters:Devoteam"
    ],
    "maxJobsPerCompany": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("fabrikit/ats-jobs-api").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": [
        "greenhouse:stripe",
        "lever:spotify",
        "ashby:ramp",
        "smartrecruiters:Devoteam",
    ],
    "maxJobsPerCompany": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("fabrikit/ats-jobs-api").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": [
    "greenhouse:stripe",
    "lever:spotify",
    "ashby:ramp",
    "smartrecruiters:Devoteam"
  ],
  "maxJobsPerCompany": 50
}' |
apify call fabrikit/ats-jobs-api --silent --output-dataset

```

## MCP server setup

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

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/NVjj7Xq6m9c3qG8eJ/builds/3AaNCYuSchZ2a9LrM/openapi.json
