# ATS Job Scraper – Greenhouse, Lever, Workday & More (`sauliusautomatesit/ats-job-scraper`) Actor

Scrape live job postings from company career sites across Greenhouse, Lever, Ashby, Workday, Workable, SmartRecruiters, Recruitee, and BambooHR. Enter company names or career-page URLs, auto-detect the ATS, and get clean structured job data.

- **URL**: https://apify.com/sauliusautomatesit/ats-job-scraper.md
- **Developed by:** [Saulius Saulenas](https://apify.com/sauliusautomatesit) (community)
- **Categories:** Jobs, AI, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.70 / 1,000 job postings

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/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 Job Scraper — Greenhouse, Lever, Ashby, Workday, Workable & more

Get **live job postings straight from company career sites** — not from a stale
database, not from LinkedIn. Give this Actor a list of **company names** (or career-page
URLs) and it automatically finds each company's job board across **8 ATS platforms** and
returns every open position as clean, structured data:

| Platform | Detection | Descriptions | Typical users |
|---|---|---|---|
| **Greenhouse** | ✅ automatic | ✅ included | Stripe, Duolingo, Figma… |
| **Lever** | ✅ automatic | ✅ included | Spotify, Palantir… |
| **Ashby** | ✅ automatic | ✅ included (+ salary ranges) | OpenAI, Linear, Ramp… |
| **Workday** | ✅ auto for many tenants (URL always works) | ✅ included | NVIDIA and much of the Fortune 500 |
| **Workable** | ✅ automatic | ✅ included | Thousands of SMBs |
| **SmartRecruiters** | ✅ automatic | ✅ included | Bosch, Visa, IKEA… |
| **Recruitee** | ✅ automatic | ✅ included | European scale-ups |
| **BambooHR** | ✅ automatic | ✅ included | US SMBs |

**Workday and Workable coverage is what sets this Actor apart** — most cheap ATS scrapers
skip the platform where most enterprise jobs actually live.

### Why teams use it

- **Recruiting & talent intelligence** — monitor competitors' hiring by team, seniority, location.
- **Job boards & aggregators** — fill your board with fresh, deduplicated postings straight from the source.
- **Sales & lead generation** — a company hiring "Head of RevOps" or "Kubernetes Engineer" is a buying
  signal. Track your target-account list daily and route new postings to your CRM.
- **Market research** — salary transparency (Ashby publishes ranges), remote-work trends, hiring velocity.

### What makes it different

1. **Company names in, jobs out.** No slugs, no URLs, no homework. `"Stripe"` is enough — the Actor
   probes all 8 platforms and finds the board. (URLs work too, in any format, including plain
   careers pages which get sniffed for embedded boards.)
2. **Monitoring mode** (`onlyNewJobs`): schedule the Actor daily and receive **each job exactly once,
   ever** — only postings it hasn't returned before. You pay per *new* job, so a daily run over a
   500-company watchlist costs cents on a quiet day.
3. **Honest data.** Fields the source platform doesn't expose (e.g. true posting dates on
   SmartRecruiters) come back as `null` with the raw value preserved — never guessed.
4. **No proxies, no per-seat SaaS fee, no rate-limit drama.** These are the same public APIs that
   power the companies' own career pages.

### Input

```json
{
    "companies": ["Stripe", "Duolingo", "OpenAI", "Bosch Group"],
    "careerPageUrls": ["https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"],
    "titleKeywords": ["engineer", "developer"],
    "locationKeywords": ["Remote", "London"],
    "postedAfter": "7d",
    "onlyNewJobs": false,
    "includeDescriptions": true,
    "maxJobsPerCompany": 500,
    "maxTotalJobs": 5000
}
```

Everything except `companies`/`careerPageUrls` is optional. Filters: `titleKeywords`,
`titleExcludeKeywords`, `locationKeywords`, `remoteOnly`, `postedAfter` (`YYYY-MM-DD` or `7d`/`24h`/`2w`).

### Output

One record per job posting:

```json
{
    "id": "ashby:openai:8f2a…",
    "platform": "ashby",
    "companyName": "OpenAI",
    "title": "Software Engineer, Applied AI",
    "url": "https://jobs.ashbyhq.com/openai/8f2a…",
    "applyUrl": "https://jobs.ashbyhq.com/openai/8f2a…/application",
    "location": "San Francisco",
    "locations": ["San Francisco", "Remote"],
    "remote": true,
    "workplaceType": "remote",
    "department": "Engineering",
    "team": "Applied AI",
    "employmentType": "FullTime",
    "salaryMin": 257000,
    "salaryMax": 335000,
    "salaryCurrency": "USD",
    "salaryInterval": "year",
    "salaryRaw": "$257K – $335K",
    "postedAt": "2026-07-30T18:04:11.000Z",
    "descriptionHtml": "<p>…full posting…</p>",
    "descriptionText": "…plain-text version…",
    "matchConfidence": "verified",
    "scrapedAt": "2026-08-11T09:12:44.000Z"
}
```

`matchConfidence` tells you how the company was matched: `url` (you gave the board URL),
`verified` (the board's own API confirmed the company name), `exact-slug` (you typed the exact
board slug), or `guessed` (slug derived from the company name and the platform doesn't expose a
name to verify against — for guessed boards `companyName` is the board slug, never your input
text, so a mismatched board can't masquerade as your target company).

Export as JSON, CSV, Excel or feed it to the [Apify API](https://docs.apify.com/api/v2),
n8n, Make, Zapier, or your warehouse like any Apify dataset. A per-company breakdown
(which platform was detected, jobs found, companies with no board) is saved to the
`SUMMARY` key of the run's key-value store.

### Monitoring mode — get each job once, pay for it once

Set `onlyNewJobs: true` and **set a fixed `monitorId`** (e.g. `"my-watchlist"`). The Actor remembers
every job it has already returned for that monitor — durably, updated as the run progresses — and
outputs only new postings on subsequent runs. Combine with
[Apify Schedules](https://docs.apify.com/platform/schedules) for a zero-maintenance
"new jobs at my target companies" feed.

Two things worth knowing:

- If you don't set `monitorId`, one is derived from your company list — which means **editing the
  list starts a fresh memory** and re-delivers currently open jobs. A fixed `monitorId` keeps the
  memory when your watchlist evolves (the run log warns about this too).
- Avoid running two monitors with the same `monitorId` at the same instant; overlapping runs may
  deliver the same brand-new job twice.

### Pricing

**$2.00 per 1,000 job postings** returned, plus a symbolic $0.00005 per Actor start.
Platform usage (compute, storage) is included — you pay only for the results.

- 5 companies, ~150 jobs → **about $0.30**
- Daily monitoring of 50 companies, ~40 new jobs/day → **about $0.08/day**
- Full pull of a 5,000-job enterprise watchlist → **about $10.00**

`maxJobsPerCompany` (default 500) and `maxTotalJobs` (default 5,000) cap every run, and Apify's
per-run cost limit is always enforced — you can never be surprised by a bill.

### Good to know

- **Descriptions on Workday, SmartRecruiters, Workable and BambooHR** require one extra request per
  job (that's how those platforms work). Runs are still fast; set `includeDescriptions: false` if you
  only need titles/locations/links and want maximum speed.
- **Workday auto-detection** finds most tenants from the company name alone (it discovers the
  career-site token automatically). For unusual setups, paste the career-site URL — the one
  containing `myworkdayjobs.com` — and it always works.
- **posting dates**: `postedAt` is only filled when the platform exposes a real date. SmartRecruiters
  only publishes a "released" timestamp that refreshes constantly, so SR jobs have `postedAt: null`
  and the raw timestamp in `postedAtRaw`. Workday exposes relative dates ("Posted 5 Days Ago") which
  are converted to approximate dates and kept raw in `postedAtRaw`.
- **A company isn't found?** The summary lists exactly what was tried. Companies whose careers run on
  unsupported systems (e.g. iCIMS, Taleo, Phenom) won't be detected — tell us which platform you're
  missing and it moves up the roadmap.
- **Pasting a single job's URL** (e.g. `boards.greenhouse.io/acme/jobs/12345`) scrapes that
  company's **whole board** — board URLs and job URLs share the same slug. Use `titleKeywords` to
  narrow the output if you only need certain roles.
- **EU-hosted boards** (`boards.eu.greenhouse.io`, `jobs.eu.lever.co`) are fully supported.
- **Is this legal?** The Actor reads the same public, unauthenticated APIs that render each company's
  own public careers page — no logins, no personal data, only public job advertisements.

### Roadmap

Personio, Teamtailor, iCIMS, JazzHR adapters · salary extraction from description text ·
webhook alerts. Missing something? Open an issue on the Actor's page.

# Actor input Schema

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

Company names or ATS slugs (e.g. `Stripe`, `duolingo`, `Sporty Group`). The Actor probes all supported ATS platforms and detects where each company hosts its jobs. Use this OR (and) Career page URLs below.

## `careerPageUrls` (type: `array`):

Direct links to job boards or career pages. Supported directly: boards.greenhouse.io/…, jobs.lever.co/…, jobs.ashbyhq.com/…, apply.workable.com/…, careers.smartrecruiters.com/…, \*.recruitee.com, \*.bamboohr.com, \*.myworkdayjobs.com. Any other company careers URL is fetched once and sniffed for an embedded ATS board.

## `platforms` (type: `array`):

Restrict auto-detection to these ATS platforms. Fewer platforms = faster detection.

## `includeDescriptions` (type: `boolean`):

Include full job description (HTML + plain text). For Workday, SmartRecruiters, Workable and BambooHR this needs one extra request per job, which makes runs slower — turn it off if you only need titles/locations/links.

## `titleKeywords` (type: `array`):

Keep only jobs whose title contains at least one of these words/phrases (case-insensitive). Leave empty to keep all.

## `titleExcludeKeywords` (type: `array`):

Drop jobs whose title contains any of these words/phrases (case-insensitive).

## `locationKeywords` (type: `array`):

Keep only jobs whose location contains at least one of these values (case-insensitive), e.g. `London`, `Remote`, `Germany`.

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

Keep only jobs detected as remote.

## `postedAfter` (type: `string`):

Keep only jobs posted on/after this date. Accepts `YYYY-MM-DD` or a relative period like `7d`, `24h`, `2w`. Jobs whose platform does not expose a posting date are kept (see README).

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

Remember which jobs were already returned in previous runs with the same Monitor ID, and only output jobs not seen before. Ideal for scheduled runs — get each job exactly once, pay for each job exactly once.

## `monitorId` (type: `string`):

Identifier for the monitoring memory used by 'Only new jobs'. Runs sharing a Monitor ID share the seen-jobs memory. If left empty, an ID is derived from the input company list.

## `dedupeAcrossPlatforms` (type: `boolean`):

If the same job (company + title + location) appears on more than one ATS platform, output it only once.

## `detectAllPlatforms` (type: `boolean`):

By default the first ATS platform found for a company is used. Enable to keep probing and scrape every platform where the company has a live board (rarely needed).

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

Safety cap per company. Large enterprises (Workday tenants) can host thousands of postings.

## `maxTotalJobs` (type: `integer`):

Safety cap for the whole run — the run stops pushing results once reached, so you can never be surprised by the bill.

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

Maximum parallel requests. The default is polite and fast enough for most runs.

## `proxyConfiguration` (type: `object`):

All 8 ATS APIs are public and need no proxy — leave as-is (none). Only set a proxy if your runs are affected by rare per-IP rate limits.

## Actor input object example

```json
{
  "companies": [
    "Stripe",
    "Duolingo",
    "Linear"
  ],
  "platforms": [
    "greenhouse",
    "lever",
    "ashby",
    "smartrecruiters",
    "recruitee",
    "workable",
    "bamboohr",
    "workday"
  ],
  "includeDescriptions": true,
  "remoteOnly": false,
  "onlyNewJobs": false,
  "dedupeAcrossPlatforms": true,
  "detectAllPlatforms": false,
  "maxJobsPerCompany": 500,
  "maxTotalJobs": 5000,
  "maxConcurrency": 10
}
```

# Actor output Schema

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

One item per job posting, with title, company, location, remote flag, employment type, department, salary (min/max/currency/interval plus the raw string), posting date, job and apply URLs, the source ATS platform, and — when enabled — the full description as HTML and plain text. Download as JSON, CSV or Excel, or read it from this API endpoint.

## `summary` (type: `string`):

A JSON report of the run: totals (jobs pushed, filtered out, duplicates and previously-seen jobs skipped) and, for each company you asked for, which ATS platform was detected, the board slug, how many jobs were found and how many were returned. Also lists companies where no board was detected and any invalid URLs.

# 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",
        "Duolingo",
        "Linear"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("sauliusautomatesit/ats-job-scraper").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",
        "Duolingo",
        "Linear",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("sauliusautomatesit/ats-job-scraper").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",
    "Duolingo",
    "Linear"
  ]
}' |
apify call sauliusautomatesit/ats-job-scraper --silent --output-dataset

```

## MCP server setup

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

```

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/4vnJcdDw3N9TPZUQc/builds/ky3iLuvfINz1wUb4c/openapi.json
