# Greenhouse Jobs Scraper: Lever, Ashby, Workday API (`adyg/ats-jobs-api`) Actor

ATS Jobs API: Greenhouse, Lever, Ashby, Workday, Workable & SmartRecruiters jobs scraper. Enter company domains or careers URLs; ATS is auto-detected. One normalized schema with salary, remote flag & dates, filters and new-job monitoring.

- **URL**: https://apify.com/adyg/ats-jobs-api.md
- **Developed by:** [Adyanth Ganesh](https://apify.com/adyg) (community)
- **Categories:** Jobs, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 job posting 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 API – Greenhouse, Lever, Ashby & Workday Jobs Scraper

**ATS Jobs API** is a Greenhouse, Lever, Ashby, Workday jobs scraper and API that pulls every open job posting straight from company applicant tracking systems (ATS) – plus Workable and SmartRecruiters – in one normalized format. Just paste company domains (`stripe.com`), careers page URLs or ATS board links: the Actor **auto-detects the ATS and board token** for you, applies your filters and can output **only new jobs since the last run**.

Because data comes from the official public job-board endpoints that power each company's careers page, results are fast, complete, and fresh – no proxies, no browsers, no login.

### What does ATS Jobs API do?

- 🔎 **Auto-detects the ATS** from a company domain, careers page, ATS board URL or bare board token
- 🧩 **6 ATS platforms in one Actor**: Greenhouse, Lever, Ashby, Workday, Workable, SmartRecruiters
- 📐 **One normalized output schema** for every ATS: company, title, department, team, locations, remote flag, workplace type, employment type, posted/updated dates, salary, description (text + HTML), apply URL, job ID
- 💰 **Salary data**: structured pay ranges from Ashby, Lever (and Greenhouse on request), plus salary ranges parsed from job descriptions
- 🎯 **Filters**: keywords, exclude keywords, locations, remote only, posted within N days, departments, employment types
- 🔔 **Monitoring mode**: only new (or changed) postings since the previous run – ideal for job alerts and schedules
- 🛡️ **Robust**: retries with backoff, per-company error isolation (one broken board never fails your run), and a per-company run summary
- ⚡ **Fast and cheap**: thousands of jobs per minute on the default 256 MB memory setting (large boards are streamed, not loaded into memory)

### Supported ATS platforms

| ATS | Example input | Notes |
|---|---|---|
| Greenhouse | `stripe.com`, `https://boards.greenhouse.io/stripe`, `greenhouse:stripe` | Full descriptions; optional structured pay ranges |
| Lever | `https://jobs.lever.co/spotify`, `lever:palantir` | US and EU (`jobs.eu.lever.co`) boards, structured salary when published |
| Ashby | `notion.so`, `https://jobs.ashbyhq.com/openai`, `ashby:ramp` | Structured compensation, remote flag, secondary locations |
| Workday | `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite` | Any `*.myworkdayjobs.com` or `myworkdaysite.com` careers site |
| Workable | `https://apply.workable.com/huggingface/`, `workable:skroutz` | Full descriptions |
| SmartRecruiters | `https://jobs.smartrecruiters.com/BoschGroup`, `smartrecruiters:ServiceNow` | Full descriptions, hybrid/remote flags |

### How to use ATS Jobs API

1. Click **Try for free** / **Start**.
2. In **Companies**, add one company per line – domains, careers URLs, ATS links or board tokens all work.
3. Optionally set filters (keywords, locations, remote only, posted within N days, departments) and **Max jobs per company**.
4. Run the Actor and download your jobs as JSON, CSV, Excel or HTML, or fetch them via the Apify API.
5. To get alerts, enable **Monitoring mode** and schedule the Actor (e.g. daily). Each run then returns only postings you haven't seen before.

#### How ATS auto-detection works

- **ATS URLs** (Greenhouse, Lever, Ashby, Workday, Workable, SmartRecruiters) are parsed directly.
- **Company domains and careers pages** are scanned for links to ATS boards; at the same time the likely board tokens (e.g. `stripe` for `stripe.com`) are checked against every supported ATS.
- **Bare tokens** (e.g. `notion`) are checked against Greenhouse, Lever, Ashby, Workable and SmartRecruiters; the board with the most open jobs wins.
- Use a **prefix** like `lever:acme` to skip detection. Workday sites need their careers URL (or must be linked from the company's careers page) because Workday URLs can't be guessed from a domain.

### Input example

```json
{
    "companies": [
        "stripe.com",
        "https://jobs.lever.co/spotify",
        "notion",
        "https://apply.workable.com/huggingface/",
        "https://jobs.smartrecruiters.com/BoschGroup",
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
    ],
    "keywords": ["engineer", "data scientist"],
    "locations": ["New York", "Remote"],
    "postedWithinDays": 14,
    "maxJobsPerCompany": 100,
    "monitoringMode": false
}
```

### Output example

Each job posting is one dataset item with the same fields regardless of ATS:

```json
{
    "company": "Ramp",
    "title": "Machine Learning Engineer",
    "department": "Engineering",
    "team": "Applied AI",
    "location": "New York, NY (HQ)",
    "locations": ["New York, NY (HQ)", "Remote (US)"],
    "country": "USA",
    "remote": true,
    "workplaceType": "Hybrid",
    "employmentType": "Full-time",
    "postedAt": "2026-09-23T19:39:11Z",
    "updatedAt": null,
    "salaryMin": 200000,
    "salaryMax": 330000,
    "salaryCurrency": "USD",
    "salaryInterval": "YEAR",
    "salaryText": "$200K - $330K",
    "salarySource": "structured",
    "descriptionText": "About Ramp\n\nRamp is building ...",
    "descriptionHtml": "<h1>About Ramp</h1><p>...",
    "jobUrl": "https://jobs.ashbyhq.com/ramp/2a1b...",
    "applyUrl": "https://jobs.ashbyhq.com/ramp/2a1b.../application",
    "ats": "ashby",
    "atsBoardToken": "ramp",
    "atsBoardUrl": "https://jobs.ashbyhq.com/ramp",
    "jobId": "2a1b...",
    "requisitionId": null,
    "sourceInput": "https://jobs.ashbyhq.com/ramp",
    "scrapedAt": "2026-10-08T06:39:38Z"
}
```

In monitoring mode every item also has `changeType`: `new` or `changed`.

The dataset has ready-made views: **Overview**, **Salaries**, **New & changed (monitoring)** and **Full details**. A per-company `SUMMARY` record (detected ATS, board URL, jobs found/saved, errors) is stored in the run's key-value store.

### Use cases

- **Job boards & aggregators**: build niche job boards (remote, AI, fintech, climate…) from hundreds of company career sites.
- **Recruiting & sales intelligence**: track which companies are hiring, for which teams and where – a strong buying-intent signal for B2B sales and staffing agencies.
- **Job alerts**: schedule monitoring mode and push new postings to Slack, email, Google Sheets or your ATS via Apify integrations, Make, Zapier or n8n.
- **Labor-market & salary research**: analyze pay ranges, remote share and hiring velocity across companies and ATS platforms.
- **AI agents & RAG**: feed clean, structured job data (text + HTML) to LLM pipelines.

### Pricing

This Actor uses **pay-per-event** pricing – you pay for job postings saved to your dataset, plus a tiny fee per run.

| Event | Price |
|---|---|
| Job posting saved (`job-result`) | **$1.50 per 1,000 jobs** ($0.0015 per job) |
| Actor start (`actor-start`) | $0.005 per run |

Platform usage (compute) is included. Set **Maximum cost per run** in run options to cap your spending – the Actor stops cleanly as soon as the limit is reached and never saves jobs it can't charge for. Companies that fail or return no jobs cost nothing; a monitoring run that finds no new postings costs only the start fee.

### Tips for best results

- Use **Max jobs per company** to keep test runs small; leave it empty for full career sites.
- Turn off **Include job descriptions** for the fastest runs when you only need titles, locations and links (especially for Workday and SmartRecruiters, which need one extra request per job for descriptions).
- Keywords and locations match whole words, case-insensitive (`US` matches "Austin, TX, US" but not "Austria"); enable **Also match keywords in job description** for broader keyword matching.
- Filters are lenient about missing data: jobs whose ATS doesn't publish a posting date or employment type are kept by **Posted within last N days** and **Employment types**.
- Use a separate **Monitoring state store name** for each independent alert/schedule.

### Related tools

Combine career-site jobs with Google Jobs for full coverage, or start from company names.

- [Google Jobs Scraper](https://apify.com/adyg/google-jobs-scraper): search job listings across all job boards by keyword and location
- [Company Name to Domain](https://apify.com/adyg/company-name-to-domain): turn a list of company names into official website domains
- [Website Contact Scraper](https://apify.com/adyg/website-contact-scraper): get emails, phone numbers and social links for each domain

All of these are maintained by the same developer and export flat JSON, CSV or Excel, so you can feed one tool's dataset into the next.

### FAQ

**Is it legal to scrape jobs from ATS job boards?**
The Actor only reads public job postings from the same public endpoints that power companies' own careers pages. It doesn't log in or collect personal data. You should still make sure your use of the data complies with applicable laws and the sites' terms.

**Why didn't my company domain get detected?**
Some companies host their careers pages on a separate domain, behind bot protection, or use an ATS that isn't supported. Paste the URL of the job board itself (e.g. the link of any job posting) or use a prefixed token such as `greenhouse:acme`. The run summary shows why each company failed.

**Which ATS platforms are not supported?**
iCIMS, Taleo, SuccessFactors, BambooHR, Personio, Recruitee, Teamtailor and other ATSs aren't supported yet. Let us know via the Issues tab which ones you need.

**How many jobs can I get from a Workday site?**
Workday's public search API can cap or truncate results for very large employers (often around 2,000 postings). For such employers, use keywords (they are sent to Workday's own search) to reach more specific roles.

**How does monitoring mode work?**
For each company the Actor stores the IDs and a fingerprint (title, locations, department, employment type, salary) of jobs it has saved, in a named key-value store in your account. The next run outputs only postings that are new or whose fingerprint changed. The first run saves everything and creates the baseline. Jobs that were not saved because of **Max jobs per company** / **Max jobs in total** are not remembered, so they appear on a later run. Entries not seen for 120 days are cleaned up automatically.

**Do I need proxies?**
No. The Actor uses public JSON endpoints and works without proxies.

**Can I use it via API or with AI agents?**
Yes. Run it via the Apify API, the Apify client for Python/JavaScript, integrations, or the Apify MCP server, and read the results from the default dataset.

**I found a bug or need a feature.**
Please open an issue in the Issues tab – feedback is very welcome and usually handled quickly.

# Actor input Schema

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

One company per line. Accepted formats:<br>• Company domain: <code>stripe.com</code> (careers page is scanned and ATS boards are probed)<br>• Careers page URL: <code>https://www.figma.com/careers/</code><br>• ATS board URL: <code>https://boards.greenhouse.io/stripe</code>, <code>https://jobs.lever.co/spotify</code>, <code>https://jobs.ashbyhq.com/notion</code>, <code>https://apply.workable.com/huggingface</code>, <code>https://jobs.smartrecruiters.com/BoschGroup</code>, <code>https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite</code><br>• Board token: <code>notion</code> (all token-based ATSs are tried)<br>• Prefixed token to skip detection: <code>greenhouse:stripe</code>, <code>lever:spotify</code>, <code>ashby:notion</code>, <code>workable:huggingface</code>, <code>smartrecruiters:BoschGroup</code><br>Workday sites must be given as a URL (or found on the company's careers page).

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

Maximum number of job postings to save per company (after filters). Leave empty or 0 for no limit. Large Workday and SmartRecruiters employers can have thousands of open roles.

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

Stop after saving this many jobs across all companies. Leave empty or 0 for no limit. The run also stops automatically when your 'Maximum cost per run' is reached.

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

Keep only jobs whose title contains at least one of these words or phrases (case-insensitive, whole words). Example: <code>engineer</code>, <code>data scientist</code>, <code>product manager</code>.

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

Drop jobs whose title contains any of these words. Example: <code>intern</code>, <code>senior</code>.

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

When enabled, keywords are matched against the title and the full job description.

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

Keep only jobs where any location (or the country) contains one of these words or phrases, case-insensitive, whole words (<code>US</code> won't match <i>Austria</i>). Remote jobs also match <code>Remote</code>. Example: <code>New York</code>, <code>London</code>, <code>Germany</code>, <code>Remote</code>.

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

Keep only jobs marked as remote by the ATS (or with 'Remote' in the location).

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

Keep only jobs first published within this many days. Jobs whose ATS does not expose a posting date are kept.

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

Keep only jobs whose department or team contains one of these strings. Example: <code>Engineering</code>, <code>Sales</code>.

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

Keep only these normalized employment types: <code>Full-time</code>, <code>Part-time</code>, <code>Contract</code>, <code>Internship</code>, <code>Temporary</code>. Jobs whose ATS doesn't publish an employment type (common on Greenhouse) are kept.

## `monitoringMode` (type: `boolean`):

Remember every job saved per company in a named key-value store and on later runs save only postings that are new (or changed, see below). Perfect for scheduled runs that feed alerts, Slack, CRMs or job boards. The first run saves everything and creates the baseline. Jobs skipped because of the max-jobs limits are not remembered, so they show up on a later run.

## `monitoringIncludeChanged` (type: `boolean`):

In monitoring mode, also save jobs whose title, location, department, employment type or salary changed since they were last seen (marked with changeType = 'changed').

## `monitoringStoreName` (type: `string`):

Name of the key-value store holding the 'already seen' state. Use a different name for each independent monitor (e.g. one per schedule or client).

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

Save the full description as plain text and HTML. Turn off for faster, smaller runs (Workday and SmartRecruiters need one extra request per job for descriptions).

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

Greenhouse only exposes structured pay ranges per job. Enable to make one extra request per Greenhouse job to get exact min/max salary. When disabled, salary is still extracted from the description text when present.

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

How many companies are scraped at the same time.

## Actor input object example

```json
{
  "companies": [
    "stripe.com",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/notion",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
  ],
  "maxJobsPerCompany": 10,
  "searchInDescription": false,
  "remoteOnly": false,
  "monitoringMode": false,
  "monitoringIncludeChanged": true,
  "monitoringStoreName": "ats-jobs-monitor",
  "includeDescription": true,
  "fetchGreenhouseSalary": false,
  "maxConcurrency": 5
}
```

# Actor output Schema

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

No description

## `summary` (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": [
        "stripe.com",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/notion",
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
    ],
    "maxJobsPerCompany": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("adyg/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": [
        "stripe.com",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/notion",
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    ],
    "maxJobsPerCompany": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("adyg/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": [
    "stripe.com",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/notion",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
  ],
  "maxJobsPerCompany": 10
}' |
apify call adyg/ats-jobs-api --silent --output-dataset

```

## MCP server setup

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