# ATS Jobs API — Greenhouse, Lever, Workday & 7 more | $1/1K (`steadyrow/ats-jobs`) Actor

Paste any company careers URL and get its open jobs in one clean schema. Auto-detects 10 ATSs (Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Personio, Teamtailor, Pinpoint, Gem) via their official public job feeds, extracts salaries, and monitor mode returns only new jobs.

- **URL**: https://apify.com/steadyrow/ats-jobs.md
- **Developed by:** [Steadyrow](https://apify.com/steadyrow) (community)
- **Categories:** Jobs, 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 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/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 API — Greenhouse, Lever, Workday & 7 more ATSs

**Paste a careers URL, get clean job data. $1 per 1,000 jobs.**

This Actor pulls open positions straight from **10 applicant tracking systems (ATS)** and returns them in **one unified schema** — no matter which ATS the company uses. It detects the ATS from the URL for you, extracts **salary ranges**, and has a **monitor mode** that returns only the jobs that are new since your last run.

- ✅ **10 ATSs, 1 schema** — Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Personio, Teamtailor, Pinpoint, Gem
- ✅ **Just paste a URL** — board URLs, job URLs, company careers pages that embed an ATS, `ats:company` identifiers, or a bare company name
- ✅ **Salary extraction** — structured pay ranges from Ashby, Lever, Pinpoint and Greenhouse pay transparency, plus ranges parsed from the description (Workday, SmartRecruiters, Workable, Gem…)
- ✅ **Monitor mode** — schedule it daily and pay only for new jobs
- ✅ **Official public job-board APIs and feeds** — the endpoints and XML/RSS feeds the vendors publish for career sites and job boards. No browser, no proxies, no login → fast and reliable
- ✅ **Filters** — keywords, excluded words, location, remote, department, posted within N days
- ✅ **Privacy-safe** — no recruiter names or contact details; e-mail addresses inside descriptions are removed

### What can I use it for?

- **Job boards & aggregators** — feed hundreds of company boards into your site with one schema
- **Job alerts** — run daily in monitor mode and push new roles to Slack, e-mail or a Google Sheet
- **Sales intelligence** — hiring signals: which companies are hiring for which teams, where, and at what pay
- **Labor-market & salary research** — compare roles, locations and pay ranges across companies
- **AI agents** — a clean, typed job feed your agent can call through the Apify API or MCP

### Supported inputs

Put one source per line in **Career pages or ATS boards**. Mix ATSs freely.

| ATS | Paste any of these |
|---|---|
| Greenhouse | `https://boards.greenhouse.io/airbnb`, `https://job-boards.greenhouse.io/figma`, `https://job-boards.eu.greenhouse.io/<company>`, `greenhouse:stripe` |
| Lever | `https://jobs.lever.co/spotify`, `https://jobs.eu.lever.co/<company>`, `lever:palantir` |
| Ashby | `https://jobs.ashbyhq.com/ramp`, `ashby:openai` |
| Workday | `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`, `https://wd3.myworkdaysite.com/recruiting/<tenant>/<site>`, `workday:nvidia.wd5/NVIDIAExternalCareerSite` |
| SmartRecruiters | `https://careers.smartrecruiters.com/BoschGroup`, `smartrecruiters:ServiceNow` |
| Workable | `https://apply.workable.com/european-dynamics/`, `https://apply.workable.com/katmai/j/<code>`, `workable:katmai` |
| Personio | `https://1komma5grad.jobs.personio.de`, `https://ottonova.jobs.personio.com`, `personio:ottonova` |
| Teamtailor | `https://sats.teamtailor.com/jobs`, a Teamtailor career site on the company's own domain (e.g. `https://careers.oatly.com/jobs`), `teamtailor:sats`, `teamtailor:careers.oatly.com` |
| Pinpoint | `https://impulsespace.pinpointhq.com`, `pinpoint:intermedia` |
| Gem | `https://jobs.gem.com/nominal`, `gem:function-health` |
| Any | A company's own careers page (we look for the embedded ATS board), or just a name like `openai` (tried on Greenhouse, Ashby, Lever and SmartRecruiters — for the other ATSs use a URL or an `ats:` prefix) |

Job URLs work too — `https://jobs.lever.co/spotify/<job-id>` scrapes the whole Spotify board.

### Input example

```json
{
    "sources": [
        "https://boards.greenhouse.io/airbnb",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ramp",
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
        "https://careers.smartrecruiters.com/BoschGroup",
        "https://apply.workable.com/european-dynamics/",
        "https://1komma5grad.jobs.personio.de",
        "https://sats.teamtailor.com/jobs",
        "https://impulsespace.pinpointhq.com",
        "https://jobs.gem.com/nominal"
    ],
    "keywords": ["engineer", "data"],
    "excludeKeywords": ["intern"],
    "locations": ["United States", "Remote"],
    "remote": "any",
    "postedWithinDays": 14,
    "maxItemsPerSource": 200,
    "includeDescription": true,
    "monitorMode": false
}
```

| Field | What it does |
|---|---|
| `sources` | Boards to scrape (see above). Required. |
| `keywords` | Keep jobs whose **title** contains any of these words (whole word, case-insensitive). Set `searchInDescription` to also search the description. |
| `excludeKeywords` | Drop jobs whose title contains any of these words. |
| `locations` | Keep jobs whose location, any secondary location, or country contains one of these words. `remote` matches remote jobs. |
| `remote` | `any`, `remote_only`, or `exclude_remote`. |
| `departments` | Keep jobs whose department or team contains one of these strings. |
| `postedWithinDays` | Only jobs posted in the last N days. |
| `maxItemsPerSource` / `maxItems` | Limits per board / per run (0 = no limit). |
| `includeDescription` / `includeDescriptionHtml` | Plain-text and/or HTML description. |
| `fetchGreenhouseSalary` | Read Greenhouse pay-transparency ranges (one extra request per Greenhouse job without a salary in its text). |
| `monitorMode` | Output only jobs not delivered in earlier runs. |
| `monitorStateName` | Name of the store that remembers delivered jobs — use a different name per independent monitor. |

### Output example

Every job has the same fields, whatever the ATS:

```json
{
    "job_id": "ashby:ramp:34413f8d-26bf-4bbc-8ade-eb309a0e2245",
    "ats": "ashby",
    "company": "Ramp",
    "company_board_id": "ramp",
    "title": "Security Engineer, Cloud",
    "department": "Engineering",
    "team": "Backend",
    "location": "New York, NY (HQ)",
    "locations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)", "Miami, FL"],
    "country": "USA",
    "remote": true,
    "workplace_type": "hybrid",
    "employment_type": "FULL_TIME",
    "posted_at": "2026-04-07T17:12:35.753Z",
    "updated_at": null,
    "salary_min": 211400,
    "salary_max": 290600,
    "salary_currency": "USD",
    "salary_period": "YEAR",
    "salary_source": "ats",
    "description_text": "About Ramp\n\nRamp is building the smart infrastructure for finance teams…",
    "apply_url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
    "source_url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
    "input": "https://jobs.ashbyhq.com/ramp",
    "scraped_at": "2026-10-19T20:21:09.179Z"
}
```

| Field | Notes |
|---|---|
| `job_id` | Stable across runs: `ats:board:job`. |
| `remote` | `true` when the job can be done remotely (Ashby "remote allowed", Workable "telecommuting", Lever/SmartRecruiters/Teamtailor/Pinpoint/Gem workplace type, or "Remote" in a location). `null` when the ATS does not say. |
| `workplace_type` | `remote`, `hybrid`, `onsite`, or `null` when the ATS does not say. |
| `employment_type` | `FULL_TIME`, `PART_TIME`, `CONTRACT`, `TEMPORARY`, `INTERN`, `OTHER`, or `null`. |
| `salary_*` | `salary_source` is `ats` (structured field) or `description` (parsed from text); all `null` when no range is published. |
| `posted_at` | First-published date (Workday and Workable: day precision). Pinpoint does not publish a date, so it is `null` there. |

A run summary (per board: listed / output / errors) is saved in the default key-value store as `SUMMARY`.

### Monitor mode: only new jobs

1. Turn on **Monitor mode** and save the input as a **task**.
2. Add a **schedule** (e.g. every morning).
3. The first run returns all matching jobs. Every later run returns **only jobs you have not received yet** — and you only pay for those.

Delivered job IDs are kept per board in a named key-value store (`monitorStateName`, default `ats-jobs-monitor`) for 90 days after a job was last seen. If you run several independent monitors on the same boards, give each task its own `monitorStateName`.

### Pricing

Pay per event — you pay only for what you get:

| Event | Price |
|---|---|
| Job in the dataset | **$0.001** ($1 per 1,000 jobs) |
| Actor start | $0.002 per run |

Examples: 1,000 jobs ≈ $1. A daily monitor on 20 boards that finds 30 new jobs a day ≈ $0.03 + $0.002 per day. Apify's free plan credits cover the first few thousand jobs every month. Set **Maximum cost per run** on the run to cap spending — the Actor stops cleanly at the limit.

### Good to know

- **Workday** returns at most ~2,000 jobs per search. For very large Workday sites, add `keywords` (they are sent to Workday's own search) to get past the cap.
- **Workday** does not publish departments, so the `departments` filter removes Workday jobs.
- Lever, Ashby, Workday, Pinpoint and Gem do not expose a display name for the company; `company` is derived from the board name (e.g. `nvidia` → `Nvidia`). Greenhouse, SmartRecruiters, Workable, Personio and Teamtailor return the real company name.
- **Personio** feeds contain the description only when the company publishes it in its XML feed (often only part of the jobs); the other fields are always there. Personio has no country field.
- **Teamtailor** and **Pinpoint** feeds have no employment type; **Pinpoint** has no posting date, so `postedWithinDays` cannot filter Pinpoint jobs (they are kept).
- **Workable** short links (`apply.workable.com/j/<code>`) do not name the company; we open the page to find the account, but pasting `apply.workable.com/<account>` is faster.
- SmartRecruiters company IDs are case-sensitive (`BoschGroup`, not `boschgroup`). An unknown ID is reported in the log and in `SUMMARY`.
- Some company careers pages block automated requests. In that case paste the ATS board URL directly — we never try to bypass bot protection.
- Requests are rate-limited (about 4 per second per ATS host) to be a good citizen.

### Is it legal?

The Actor reads **public job postings** through the ATS vendors' **official public job-board APIs** — the same endpoints that power the companies' own career pages. It does not log in, does not bypass any protection, and does not output personal data: recruiter names and contact details are never collected, and e-mail addresses inside job descriptions are replaced with `[email removed]`. You are responsible for how you use the data; if in doubt, consult a lawyer.

### Feedback

Missing an ATS or a field? Open an issue on the Actor's **Issues** tab — requests are usually handled within a few days.

# Actor input Schema

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

One per line. Paste an ATS board URL (boards.greenhouse.io/stripe, jobs.lever.co/spotify, jobs.ashbyhq.com/ramp, nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite, careers.smartrecruiters.com/BoschGroup, apply.workable.com/european-dynamics, 1komma5grad.jobs.personio.de, sats.teamtailor.com, impulsespace.pinpointhq.com, jobs.gem.com/nominal), a company's own careers page that embeds one of them, an identifier like `greenhouse:stripe` / `lever:spotify` / `ashby:ramp` / `smartrecruiters:BoschGroup` / `workday:nvidia.wd5/NVIDIAExternalCareerSite` / `workable:katmai` / `personio:ottonova` / `teamtailor:sats` / `pinpoint:intermedia` / `gem:nominal`, or just a board name like `stripe` (probed on Greenhouse, Ashby, Lever and SmartRecruiters only).

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

Keep jobs whose title contains at least one of these words (whole-word, case-insensitive). Leave empty to keep all.

## `searchInDescription` (type: `boolean`):

By default keywords are matched against the job title only.

## `excludeKeywords` (type: `array`):

Drop jobs whose title contains any of these words (e.g. Senior, Intern).

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

Keep jobs whose location, any of their locations, or country contains one of these words (whole-word, case-insensitive; e.g. London, Germany, US). Use `remote` to match remote jobs.

## `remote` (type: `string`):

Filter by remote eligibility.

## `departments` (type: `array`):

Keep jobs whose department or team contains one of these strings (e.g. Engineering). Workday does not publish departments, so this filter drops all Workday jobs.

## `postedWithinDays` (type: `integer`):

Only jobs posted in the last N days. 0 = no limit.

## `maxItemsPerSource` (type: `integer`):

Stop after this many jobs from one board. 0 = all jobs.

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

Stop the whole run after this many jobs. 0 = no limit.

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

Adds `description_text`. Turning it off makes SmartRecruiters runs much faster (no per-job request).

## `includeDescriptionHtml` (type: `boolean`):

Adds `description_html` (cleaned original HTML).

## `fetchGreenhouseSalary` (type: `boolean`):

Greenhouse publishes pay-transparency ranges only on each job's own endpoint. When on, we make one extra request per Greenhouse job that has no salary in its description. Turn off for faster runs on very large boards.

## `monitorMode` (type: `boolean`):

Remembers every job already delivered (per board) and outputs only jobs you have not received before. The first run outputs all matching jobs. Ideal for a daily schedule — you only pay for new jobs.

## `monitorStateName` (type: `string`):

Name of the key-value store that keeps the list of delivered jobs. Use a different name per task if you run several independent monitors.

## `maxConcurrency` (type: `integer`):

Detail requests run in parallel up to this number. Requests to one ATS host are additionally limited to about 4 per second.

## Actor input object example

```json
{
  "sources": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://careers.smartrecruiters.com/BoschGroup"
  ],
  "searchInDescription": false,
  "remote": "any",
  "postedWithinDays": 0,
  "maxItemsPerSource": 20,
  "maxItems": 0,
  "includeDescription": true,
  "includeDescriptionHtml": false,
  "fetchGreenhouseSalary": true,
  "monitorMode": false,
  "monitorStateName": "ats-jobs-monitor",
  "maxConcurrency": 4
}
```

# Actor output Schema

## `results` (type: `string`):

All job postings collected in this run, one row per job.

# 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": [
        "https://boards.greenhouse.io/stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ramp",
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
        "https://careers.smartrecruiters.com/BoschGroup"
    ],
    "maxItemsPerSource": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadyrow/ats-jobs").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": [
        "https://boards.greenhouse.io/stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ramp",
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
        "https://careers.smartrecruiters.com/BoschGroup",
    ],
    "maxItemsPerSource": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("steadyrow/ats-jobs").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": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://careers.smartrecruiters.com/BoschGroup"
  ],
  "maxItemsPerSource": 20
}' |
apify call steadyrow/ats-jobs --silent --output-dataset

```

## MCP server setup

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

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/QNeCTI8OqUPagKhYk/builds/q6BlpWFH9JNccYe72/openapi.json
