# Greenhouse, Lever, Ashby & Workday Jobs API (ATS Scraper) (`gazidev/ats-jobs-api`) Actor

Get every open job from any company's career page in ONE clean schema. Auto-detects Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, Personio and Workday from a URL or domain. Salary, remote, location, keyword and date filters plus delta mode (only new jobs).

- **URL**: https://apify.com/gazidev/ats-jobs-api.md
- **Developed by:** [Cemal Atakli](https://apify.com/gazidev) (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 job returneds

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

## Greenhouse, Lever, Ashby & Workday Jobs API (ATS Scraper)

Get **every open job from any company's career page in one clean JSON schema**. Paste career-page URLs, company domains or job-board links. The Actor detects which applicant tracking system (ATS) each company uses and reads that ATS's **official public job-board API**.

- **8 ATSs, one schema.** It covers Greenhouse, Lever, Ashby, Workday, Workable, SmartRecruiters, Recruitee and Personio, and auto-detects the ATS from a URL, a career page or just a domain (`stripe.com`).
- **Filters and delta mode.** Filter by keywords, locations, remote only and posted within N days. Scheduled runs return, and charge for, only jobs you haven't received yet.
- **$2 per 1,000 jobs, flat.** It is HTTP-only, with no browser, no proxies and no proxy fees.

### Quick start

This is the prefilled input: 2 jobs from Ramp's Ashby board in a few seconds, for less than $0.01. Raise `maxItems` for real use.

```json
{ "targets": ["https://jobs.ashbyhq.com/ramp"], "maxItems": 2 }
```

#### Sample output

| company | ats | title | workplace\_type | salary (USD) | posted\_at |
|---|---|---|---|---|---|
| Ramp | ashby | Senior Corporate Paralegal | hybrid | 185,000–250,000 | 2026-09-26 |
| Spotify | lever | Senior Program Manager - Podcast Sales Strategy & Solutions | hybrid | 96,693–138,134 | 2026-09-25 |
| Airbnb | greenhouse | Accountant | remote | 87,000–102,000 | 2026-09-25 |
| Hugging Face | workable | Senior Open-Source Python Engineer, ML Developer Tools - EMEA Remote | remote | – | 2026-09-21 |

#### Price comparison (Apify Store, September 2026)

| Actor | Price per 1,000 jobs | Coverage | Monthly users |
|---|---|---|---|
| **This Actor** | **$2 flat** (+ $0.005 per run) | 8 ATSs, auto-detect, delta mode | new |
| jobo.world/ats-jobs-api | $4 (free tier; lower on paid tiers) | multi-ATS | 150 |
| fantastic-jobs/workday-jobs-api | $2 (free tier; lower on paid tiers) | Workday only | 136 |

**Related Actors:** [Remote Jobs Scraper & Aggregator](https://apify.com/gazidev/remote-jobs-aggregator) for remote job boards, and [Wappalyzer Alternative – Bulk Tech Stack Detector](https://apify.com/gazidev/tech-stack-detector) to see the software stack of the companies that are hiring.

### What it does

1. **Resolve** each input:
   - Job-board URLs are recognized directly (`jobs.lever.co/…`, `boards.greenhouse.io/…`, `jobs.ashbyhq.com/…`, `*.myworkdayjobs.com/…`, `apply.workable.com/…`, `*.recruitee.com`, `*.jobs.personio.de`, `jobs.smartrecruiters.com/…`).
   - For a domain or career page, the actor scans the careers pages for ATS links and embeds.
   - If none are found, it probes each ATS's public API with the company name (`stripe.com` → `stripe`).
2. **Fetch** all open jobs from the public job-board endpoints that power the company's own career site.
3. **Normalize** everything into one schema, apply your filters and optionally skip jobs already delivered.

### Use cases

- **Job boards and aggregators:** fill a niche board (remote, AI, climate, EU startups) from hundreds of company career pages.
- **Sales and recruiting intelligence:** spot companies hiring for a role, such as "data engineer" at Series B fintechs. Hiring is a buying signal.
- **Job alerts:** schedule daily runs with delta mode and get only new postings. Send them to Slack, email or a Google Sheet with Apify integrations.
- **Salary benchmarking:** collect posted pay ranges by title, location and company.
- **Market and competitor research:** track headcount growth by department and new offices.
- **AI agents:** give an LLM agent a live, structured view of who is hiring (see below).

### Input example

```json
{
  "targets": [
    "https://boards.greenhouse.io/airbnb",
    "https://jobs.ashbyhq.com/ramp",
    "lever:spotify",
    "huggingface.co",
    "stripe.com",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
  ],
  "companies": [
    { "ats": "smartrecruiters", "slug": "Ubisoft2", "company": "Ubisoft" },
    { "ats": "personio", "slug": "1komma5grad" }
  ],
  "keywords": ["engineer*", "data scientist"],
  "locations": ["London", "Berlin", "remote"],
  "remoteOnly": false,
  "postedWithinDays": 14,
  "onlyNew": true,
  "includeDescription": false
}
```

The `targets` field accepts any of these:

| Input form | Example |
|---|---|
| Job-board URL | `https://jobs.lever.co/palantir` |
| Career page | `https://www.notion.so/careers` |
| Bare domain | `linear.app` |
| Shorthand | `greenhouse:airbnb`, `ashby:openai`, `lever/spotify`, `workable:huggingface`, `recruitee:bunq`, `personio:ottonova`, `smartrecruiters:Ubisoft2` |
| Workday career-site URL | `https://salesforce.wd12.myworkdayjobs.com/External_Career_Site` |

Workday tenants can't be guessed from a domain, so **pass the Workday URL from your browser's address bar**.

### Output example

```json
{
  "uid": "ashby:ramp:34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "company": "Ramp",
  "company_slug": "ramp",
  "ats": "ashby",
  "job_id": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "title": "Security Engineer, Cloud",
  "department": "Engineering",
  "team": "Backend",
  "locations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)", "Miami, FL"],
  "country": "USA",
  "remote": false,
  "workplace_type": "hybrid",
  "employment_type": "full_time",
  "employment_type_raw": "FullTime",
  "salary_min": 211400,
  "salary_max": 290600,
  "salary_currency": "USD",
  "salary_interval": "year",
  "salary_source": "ats",
  "posted_at": "2026-04-07T17:12:35Z",
  "updated_at": null,
  "job_url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "apply_url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
  "scraped_at": "2026-09-27T10:00:00Z"
}
```

With **Include job description** turned on, each job also gets `description_html` and `description_text`.

Normalized values:

- `employment_type`: `full_time`, `part_time`, `contract`, `internship`, `temporary` or `other`
- `workplace_type`: `remote`, `hybrid` or `onsite`, or `null` when the ATS doesn't say

The **OUTPUT** record in the key-value store reports, for every input:

- the ATS and slug it resolved to
- how it was detected (`url_pattern`, `page_scan`, `slug_guess` or `explicit`)
- jobs found and jobs matched
- any error

### Supported ATSs and data coverage

| ATS | Endpoint used | Salary | Posted date | Remote / workplace | Description |
|---|---|---|---|---|---|
| Greenhouse | `boards-api.greenhouse.io/v1/boards/{slug}/jobs` | from description text | ✅ | from metadata and location | ✅ |
| Lever | `api.lever.co/v0/postings/{slug}` (+ EU) | ✅ `salaryRange`, else from text | ✅ | ✅ `workplaceType` | ✅ |
| Ashby | `api.ashbyhq.com/posting-api/job-board/{slug}` | ✅ compensation | ✅ | ✅ | ✅ |
| Workday | `POST /wday/cxs/{tenant}/{site}/jobs` + job detail | from description text | ✅ (detail) | from location | ✅ (detail) |
| Workable | `apply.workable.com/api/v1/widget/accounts/{slug}` | from text | ✅ | ✅ `telecommuting` | ✅ |
| SmartRecruiters | `api.smartrecruiters.com/v1/companies/{id}/postings` | – | ✅ | ✅ | ✅ (1 extra request per job) |
| Recruitee | `{slug}.recruitee.com/api/offers/` | ✅ | ✅ | ✅ | ✅ |
| Personio | `{slug}.jobs.personio.de/xml` | – | ✅ | from office name | ✅ |

### Pricing

Pay per event:

- **$0.002 per job** returned ($2 per 1,000)
- **$0.005 per run** start
- No subscription, no proxy fees

In delta mode you pay only for **new** jobs.

| Actor (Apify Store, Sept 2026) | Price per job (free tier → top tier) | ATS coverage |
|---|---|---|
| **This actor** | **$0.002 flat** | 8 ATSs, auto-detect, delta mode |
| fantastic-jobs/career-site-job-listing-api | $0.012 → $0.004 | many (aggregated feed) |
| jobo.world/ats-jobs-api | $0.004 → $0.0013 | multi-ATS |
| memo23/career-site-ats-jobs-api | $0.004 → $0.0025 (+$0.03 per start) | multi-ATS |
| fantastic-jobs/greenhouse-jobs-api | $0.002 → $0.0012 | Greenhouse only |
| fantastic-jobs/workday-jobs-api | $0.002 → $0.0012 | Workday only |
| bovi/greenhouse-lever-ashby-job-scraper | $0.0015 → $0.0014 | 3 ATSs |

Single-ATS actors can be cheaper per job if you only need one ATS. This actor covers 8 ATSs in one schema, detects the ATS for you, and in delta mode charges only for new jobs.

Example: 50 companies checked daily with about 20 new jobs per day costs about $0.045 per day (about $1.35 per month).

### Use with AI agents / Apify MCP

The actor works well as a tool for LLM agents. Its input is simple (company names, domains or URLs) and its output is compact, typed JSON.

- **Apify MCP server:** add `https://mcp.apify.com/?tools=gazidev/ats-jobs-api` to Claude Desktop, Cursor, or any other MCP client. Then ask things like *"Which of these 30 fintech companies are hiring a staff data engineer in London? Include salary ranges."*
- **API / SDK:** call `run-sync-get-dataset-items` with the input JSON and get the jobs back in one HTTP request.
- Keep `includeDescription` off to save agent tokens. Turn it on only for jobs you want the model to read closely.

### FAQ

**Is this legal and allowed?** The actor only reads the public job-board endpoints that the ATS vendors provide so career sites and job aggregators can show open positions. The same data is shown publicly on the company's careers page. The actor does not log in, bypass protection or collect personal data.

**How does auto-detection work?** URLs are matched against known ATS URL patterns. For domains, the actor loads `/careers`, `/jobs`, `careers.<domain>` and the homepage, follows career links and looks for ATS board links or embeds. If none are found, it tries the domain name as a slug on each ATS's API. The detection method is reported per input. If a guess is wrong, pass the board URL or an explicit `{ats, slug}` entry.

**Why wasn't my company detected?** Some companies use an ATS this actor doesn't support (iCIMS, Taleo, SuccessFactors, Teamtailor and others), or load their jobs board with JavaScript on a custom domain. Workday can't be guessed from a domain. For Workday, paste the `…myworkdayjobs.com/...` URL.

**How does delta mode work?** Jobs you received are remembered, as hashed IDs, in a named key-value store called `ats-jobs-api-state`. The memory is keyed by your company list and filters, or by your own `stateKey`. Jobs are forgotten 120 days after they were last seen open.

**Are Workday results complete?** Workday lists at most about 2,000 postings per search. When you set keywords, they are also sent to Workday's own search, so large employers are covered for the roles you care about.

**How accurate is salary data?** `salary_source: "ats"` means the structured compensation fields published by the company. `"description"` means a pay range parsed from the job text, such as US pay-transparency statements. Parsed ranges are best-effort.

**Can I get the full description?** Yes. Turn on `includeDescription` to get both HTML and plain text.

# Actor input Schema

## `targets` (type: `array`):

One company per line. Accepts: a job-board URL (https://jobs.lever.co/palantir, https://boards.greenhouse.io/airbnb, https://jobs.ashbyhq.com/ramp, https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite ...), a company career page (https://www.notion.so/careers), a bare domain (stripe.com — the ATS is auto-detected from the career page or by probing each ATS), or a shorthand like greenhouse:airbnb, lever:spotify, ashby:openai, workable:huggingface, smartrecruiters:Ubisoft2, recruitee:bunq, personio:1komma5grad.

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

Optional. Explicit entries when you already know the ATS, e.g. \[{"ats": "greenhouse", "slug": "stripe"}, {"ats": "workday", "url": "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite", "company": "NVIDIA"}]. Supported ats values: greenhouse, lever, ashby, workable, smartrecruiters, recruitee, personio, workday. "company" overrides the display name.

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

Keep a job if ANY keyword matches its title, department or team (case/accent-insensitive whole words; trailing \* = prefix, e.g. "engineer\*"). Empty = all jobs.

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

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

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

Also match keywords against the full job description (broader, noisier). Off = title/department/team only.

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

Keep a job if ANY of these strings appears in its locations or country (substring, case-insensitive), e.g. "London", "Germany", "New York". "remote" also matches jobs flagged remote. Empty = all locations.

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

Only jobs flagged remote by the ATS or with 'Remote' in their location/title.

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

Only jobs posted (or, if the ATS has no posting date, updated) in the last N days. Empty = no date filter.

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

Remembers delivered jobs in a named key-value store (ats-jobs-api-state), keyed by your companies + filters. Scheduled runs then output (and charge) only newly posted jobs. The first run returns everything that matches.

## `stateKey` (type: `string`):

Custom name for the delta memory, so you can edit companies/filters and keep the history. Default: derived automatically from companies + filters.

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

Add description\_html and description\_text to each job. Off keeps the dataset small. (SmartRecruiters needs one extra request per job for this.)

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

Safety cap per job board (applied before filters for most ATSs; after title/date pre-filter for Workday).

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

Stop after this many jobs are output (you are charged per job output).

## `guessSlugs` (type: `boolean`):

For bare domains: if the career page has no recognisable ATS link, try the domain name (e.g. stripe.com -> 'stripe') on each ATS's public board API. Detection method is reported per company in the OUTPUT record.

## `workdayFetchDetails` (type: `boolean`):

Workday job lists only carry title, a location summary and a relative date. With this on (recommended), each matching job's detail is fetched for all locations, exact posting date, time type and description. Keyword filters are applied first (and sent to Workday search), so only relevant jobs cost a request.

## Actor input object example

```json
{
  "targets": [
    "https://jobs.ashbyhq.com/ramp"
  ],
  "companies": [],
  "keywords": [],
  "excludeKeywords": [],
  "searchInDescription": false,
  "locations": [],
  "remoteOnly": false,
  "onlyNew": false,
  "includeDescription": false,
  "maxJobsPerCompany": 2000,
  "maxItems": 2,
  "guessSlugs": true,
  "workdayFetchDetails": true
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `results` (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 = {
    "targets": [
        "https://jobs.ashbyhq.com/ramp"
    ],
    "maxItems": 2
};

// Run the Actor and wait for it to finish
const run = await client.actor("gazidev/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 = {
    "targets": ["https://jobs.ashbyhq.com/ramp"],
    "maxItems": 2,
}

# Run the Actor and wait for it to finish
run = client.actor("gazidev/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 '{
  "targets": [
    "https://jobs.ashbyhq.com/ramp"
  ],
  "maxItems": 2
}' |
apify call gazidev/ats-jobs-api --silent --output-dataset

```

## MCP server setup

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