# ATS Jobs API: Greenhouse, Lever, Ashby & Workable Jobs (`dodiabak/ats-jobs-api`) Actor

Get every open job from company career sites on Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee and Personio as one normalized JSON schema: salary, remote/hybrid, seniority, Markdown description. Auto-detects the ATS. $2 per 1,000 jobs.

- **URL**: https://apify.com/dodiabak/ats-jobs-api.md
- **Developed by:** [Dodi Abak](https://apify.com/dodiabak) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.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, Ashby & Workable Jobs

**Get every open job from any company's career site as clean, normalized JSON, straight from the company's own applicant tracking system (ATS).** Supports **Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee and Personio**. Give it careers URLs, company websites or just company names. It finds the ATS for you and returns one schema for every system: title, department, locations, **remote / hybrid / on-site**, **salary range**, **seniority**, employment type, posting date, apply link, and the full description as **LLM-ready Markdown**.

**$2 per 1,000 jobs.** No login, no cookies, no proxies, no browser. Data comes from the public job-board APIs that companies publish for their own career pages, so it's fresh, first-party and complete.

### What can this ATS Jobs API do?

- 🏢 **7 ATS in one Actor.** Greenhouse, Lever (US + EU), Ashby, Workable, SmartRecruiters, Recruitee and Personio. Each one maps to the same 25+ field schema.
- 🔎 **Auto-detects the ATS.** Pass `huggingface.co` or `Notion` and the Actor reads the company's website for a careers link, or tries the company's slug on every supported ATS.
- 💰 **Salary ranges.** Uses the ATS's structured pay data where it exists (Ashby, Lever, Recruitee) and otherwise parses pay-transparency text in the description (`$190,000 — $230,000 USD`, `€60k–80k`, `USD 45 to 60 per hour`).
- 🌍 **Remote, hybrid or on-site**, from the ATS flag first and the title/location text second.
- 🧭 **Seniority and employment type**, normalized (`senior`, `staff`, `manager`, `intern`… / `full_time`, `contract`, `internship`…).
- 🧹 **Filters that save money.** Title keywords, excluded words, locations, countries, workplace type, departments, seniority, employment type, posted within N days, salary-only. You only pay for jobs that pass.
- 🔔 **Monitoring mode.** Turn on **Only new jobs**, schedule the Actor, and each run returns (and charges for) only jobs no earlier run delivered. Perfect for job alerts and hiring-signal feeds.
- 🤖 **Built for AI agents.** Stable field names, a one-line `summary` per job, Markdown descriptions, and free error rows with clear codes. Works through the [Apify MCP server](https://mcp.apify.com) in Claude, Cursor, ChatGPT and other MCP clients.

### Who uses it?

- **Job boards and aggregators:** build niche boards (remote AI jobs, climate jobs, jobs in Germany…) from first-party ATS data.
- **Sales and recruiting teams:** hiring is a buying signal. Track which companies are hiring for which roles, and where.
- **Market and salary research:** compare pay ranges, remote share and seniority mix across companies.
- **AI agents and job-search copilots:** "find senior remote data engineering jobs at these 50 startups, posted this week".

### How to use it

1. Add companies to **Companies**, one per line, in any of these forms:
   - a careers URL: `https://boards.greenhouse.io/stripe`, `https://jobs.lever.co/palantir`, `https://jobs.ashbyhq.com/openai`, `https://apply.workable.com/huggingface`, `https://careers.smartrecruiters.com/BoschGroup`, `https://bunq.recruitee.com`, `https://personio.jobs.personio.de`
   - a shortcut: `greenhouse:stripe`, `lever:palantir`, `ashby:notion`
   - a company website: `huggingface.co`
   - a company name: `Notion` (the Actor tries the slug on each ATS; check the `boardUrl` it found)
2. Optionally add filters (keywords, locations, remote…).
3. Click **Start**. Open the **Overview** view, or download JSON, CSV, Excel or XML, or call the API.

#### Input example

```json
{
  "companies": ["greenhouse:stripe", "https://jobs.ashbyhq.com/openai", "https://jobs.lever.co/palantir", "huggingface.co"],
  "keywords": ["engineer", "data", "product manager"],
  "workplaceTypes": ["remote", "hybrid"],
  "postedWithinDays": 30,
  "descriptionFormat": "markdown",
  "onlyNewJobs": false
}
```

#### Output example (one job, description shortened)

```json
{
  "recordType": "job",
  "jobId": "ashby:openai:c857b732-3556-4b65-8ae9-f64b92091729",
  "ats": "ashby",
  "company": "OpenAI",
  "title": "Engineering Manager, Preparedness",
  "department": "Safety Systems",
  "team": "Preparedness",
  "location": "San Francisco",
  "locations": ["San Francisco"],
  "workplaceType": "hybrid",
  "isRemote": false,
  "employmentType": "full_time",
  "seniority": "manager",
  "salary": { "min": 445000, "max": 510000, "currency": "USD", "interval": "year", "text": "$445K - $510K", "source": "ats" },
  "postedAt": "2026-10-19T21:55:42.352Z",
  "url": "https://jobs.ashbyhq.com/openai/c857b732-3556-4b65-8ae9-f64b92091729",
  "applyUrl": "https://jobs.ashbyhq.com/openai/c857b732-3556-4b65-8ae9-f64b92091729/application",
  "summary": "Engineering Manager, Preparedness at OpenAI · San Francisco · hybrid · USD 445k–USD 510k/year · posted 2026-10-19",
  "descriptionMarkdown": "**About the Team**\n\nThe Preparedness Engineering team is responsible for…",
  "descriptionChars": 5467,
  "isNew": null,
  "input": "https://jobs.ashbyhq.com/openai"
}
```

A company that can't be read becomes a free error row: `{"recordType": "error", "input": "acme", "errorCode": "ATS_NOT_FOUND", "error": "No supported ATS board found…"}`. A per-company run report (boards found, detection method, open / matched / delivered counts) is saved as the `OUTPUT` record in the key-value store.

### Output fields

| Field | Meaning |
| --- | --- |
| `jobId` | Stable id `ats:companySlug:postingId`, the same on every run |
| `ats`, `companySlug`, `company`, `boardUrl` | Where the job came from |
| `title`, `department`, `team` | As published |
| `location`, `locations`, `countryCode` | All published locations |
| `workplaceType`, `isRemote` | `remote` / `hybrid` / `onsite` |
| `employmentType`, `employmentTypeRaw` | Normalized and original |
| `seniority` | From the title: `intern` … `executive` |
| `salary` | `{min, max, currency, interval, text, source}`, where `source` is `ats` (structured) or `description` (parsed) |
| `postedAt`, `updatedAt` | ISO 8601 |
| `url`, `applyUrl` | Job page and application page |
| `descriptionMarkdown` / `descriptionText` | Full description |
| `summary` | One line for humans and LLMs |
| `isNew` | `true` in Only-new-jobs mode |

### How much does it cost?

**Pay per event: $0.002 per job delivered ($2 per 1,000 jobs)**, plus Apify's tiny per-run start fee. Error rows, filtered-out jobs and (in monitoring mode) jobs you already received are **never charged**. The Actor respects your **Max cost per run** and stops cleanly when it's reached.

Examples: all 726 open Stripe jobs ≈ $1.45. A daily alert on 100 startups that returns 40 new jobs a day ≈ $2.40 a month.

### Use it from AI agents (MCP) and code

- **MCP:** add `https://mcp.apify.com?tools=actors,dodiabak/ats-jobs-api` to Claude Desktop, Cursor, VS Code or any MCP client, then ask *"List remote senior backend jobs at Stripe, Notion and Hugging Face posted this month, with salaries."*
- **API:** `POST https://api.apify.com/v2/acts/dodiabak~ats-jobs-api/run-sync-get-dataset-items?token=YOUR_TOKEN` with the input JSON as the body.
- **Integrations:** Make, Zapier, n8n, Google Sheets, Slack and webhooks through Apify integrations.

### Tips

- For **company names**, slug guessing can match a different company with the same name. Check `boardUrl`, or pass the careers URL for 100% accuracy.
- **SmartRecruiters** lists no descriptions, so the Actor fetches them only for jobs that pass your filters. Use `descriptionFormat: "none"` for the fastest runs.
- Schedule with **Only new jobs** to get a clean daily feed of new openings.
- Workday, iCIMS, Taleo and other systems are not supported yet. [Open an issue](https://apify.com/dodiabak/ats-jobs-api/issues) if you need one.

### FAQ

**Is this legal?** The Actor reads public job-board endpoints that the ATS vendors provide so companies can show their jobs on their own websites. It collects job postings, not personal data, and needs no login. You're responsible for how you use the data.

**How fresh is the data?** Live. Every run reads the ATS directly.

**Why is a field null?** Not every ATS publishes every field. For example, Greenhouse has no structured remote flag or salary, so those come from the text when present.

**Support:** use the Issues tab. Feature requests are welcome.

# Changelog

This Actor's version history is a separate document: https://apify.com/dodiabak/ats-jobs-api/changelog.md

# Actor input Schema

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

Careers page URLs (boards.greenhouse.io/stripe, jobs.lever.co/palantir, jobs.ashbyhq.com/openai, apply.workable.com/huggingface, careers.smartrecruiters.com/BoschGroup, bunq.recruitee.com, personio.jobs.personio.de), "ats:slug" shortcuts such as "greenhouse:stripe", company websites ("huggingface.co") or plain company names ("Notion"). Websites and names are auto-detected.

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

Keep jobs whose title contains ANY of these words or phrases. Leave empty for all jobs. Short terms match whole words, so "go" won't match "Google".

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

Drop jobs whose title contains any of these, e.g. "intern", "senior".

## `searchDescription` (type: `boolean`):

Also match keywords against the full job description (finds 'Python' or 'Kubernetes' anywhere in the ad).

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

Keep jobs whose location contains any of these, e.g. "London", "Germany", "Remote", "EMEA".

## `countries` (type: `array`):

ISO country codes (US, GB, DE, SA) or country names. Matched against the ATS country code and the location text.

## `workplaceTypes` (type: `array`):

remote, hybrid, onsite or unknown.

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

Keep jobs whose department or team contains any of these, e.g. "Engineering", "Sales".

## `employmentTypes` (type: `array`):

Normalized employment type.

## `seniority` (type: `array`):

Detected from the job title.

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

Only jobs first published in the last N days. 0 = any date. Jobs without a date are dropped when this is set.

## `salaryOnly` (type: `boolean`):

Keep only jobs where a pay range was published (by the ATS or in the description).

## `descriptionFormat` (type: `string`):

Markdown is best for LLMs. 'None' makes runs faster and smaller.

## `onlyNewJobs` (type: `boolean`):

Remember delivered jobs in a named key-value store (ats-jobs-api-seen) and return only jobs that no earlier run returned. Ideal for schedules and job alerts. You are only charged for new jobs.

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

Stop after this many matching jobs per company. Lower it for a quick sample; jobs are charged per returned row.

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

Hard cap on the total number of jobs returned in this run across all companies. Also keeps your cost predictable.

## `probeByName` (type: `boolean`):

For websites with no ATS link and for plain names, try the company slug on each supported ATS. Turn off to use careers URLs only.

## `atsToProbe` (type: `array`):

Limit slug guessing to these systems. Empty = all seven.

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

How many companies are fetched at the same time (1-10). Higher is faster; 4 is a safe default.

## `includeErrorRows` (type: `boolean`):

Error rows (recordType = error) are free and explain what went wrong.

## Actor input object example

```json
{
  "companies": [
    "greenhouse:stripe",
    "https://jobs.ashbyhq.com/openai",
    "https://jobs.lever.co/palantir",
    "huggingface.co"
  ],
  "keywords": [
    "engineer",
    "data",
    "product manager"
  ],
  "searchDescription": false,
  "postedWithinDays": 0,
  "salaryOnly": false,
  "descriptionFormat": "markdown",
  "onlyNewJobs": false,
  "maxJobsPerCompany": 5,
  "maxJobs": 10000,
  "probeByName": true,
  "concurrency": 4,
  "includeErrorRows": true
}
```

# Actor output Schema

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

No description

## `report` (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 = {
    "companies": [
        "greenhouse:stripe",
        "https://jobs.ashbyhq.com/openai",
        "https://jobs.lever.co/palantir",
        "huggingface.co"
    ],
    "keywords": [
        "engineer",
        "data",
        "product manager"
    ],
    "maxJobsPerCompany": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("dodiabak/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",
        "https://jobs.ashbyhq.com/openai",
        "https://jobs.lever.co/palantir",
        "huggingface.co",
    ],
    "keywords": [
        "engineer",
        "data",
        "product manager",
    ],
    "maxJobsPerCompany": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("dodiabak/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",
    "https://jobs.ashbyhq.com/openai",
    "https://jobs.lever.co/palantir",
    "huggingface.co"
  ],
  "keywords": [
    "engineer",
    "data",
    "product manager"
  ],
  "maxJobsPerCompany": 5
}' |
apify call dodiabak/ats-jobs-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dodiabak/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/50MJ42SicDIbvCXUo/builds/2LhKbbTW8fOteQaiV/openapi.json
