# 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
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

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

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 returned** (event `job-result`, **US$1.00 per 1,000 jobs**, i.e. US$0.001 per job) on top of standard Apify platform usage. No subscription, no rental fee, no proxy costs — because the Actor talks to official public APIs, runs are fast and compute usage is minimal. A 50-company sweep returning 5,000 jobs costs about US$5 in events. 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?**
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). It collects no personal data — only public job postings.

**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.

### More scrapers by Fabrikit

- **[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.

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

# 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/BiUNSHaCL08UN3wzi/openapi.json
