# ATS Job Scraper: Greenhouse, Lever, Workable & More (`i-scraper/ats-jobs-scraper`) Actor

Scrape jobs from Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, and Personio. Get normalized job data and detect new postings.

- **URL**: https://apify.com/i-scraper/ats-jobs-scraper.md
- **Developed by:** [i-Scraper](https://apify.com/i-scraper) (community)
- **Categories:** Jobs
- **Features:** Limited permissions
- **Stats:** 48 total users, 6 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.90 / 1,000 results

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

## Workday, Greenhouse, Lever & Workable ATS Job Scraper

Scrape public job postings from company career pages hosted on **Workday, Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, and Personio**. Search **1,900+ live-verified company boards** through built-in presets or add your own URLs. This multi-ATS job scraper returns one clean, normalized dataset with job titles, locations, departments, seniority levels, remote type, employment type, and detected tech stack. Track new postings between runs so you always work with fresh hiring signals.

Built for recruiting research, sales prospecting, competitor hiring intelligence, talent market analysis, and job aggregation. No browser automation and no flaky DOM selectors: the Actor reads public ATS job feeds directly, so output stays fast and stable.

***

### Use cases

- **Recruiters & sourcers** — monitor competitor hiring, find passive candidates by skill set, build pipelines from public openings.
- **Sales & RevOps teams** — detect buying signals (a company hiring a "Salesforce admin" hints at a Salesforce purchase).
- **Market researchers & analysts** — track hiring trends across industries, geographies, and seniority bands.
- **Founders & operators** — benchmark headcount growth, compensation bands, and tech-stack adoption against competitors.
- **Job board builders** — aggregate fresh tech jobs from hundreds of company career pages into a single feed.
- **Investors & VCs** — measure portfolio growth and hiring velocity with weekly snapshots.

***

### What you get

For every job posting, the scraper returns a structured record with:

- **Job basics**: title, company name and domain, location, department, employment type, source URL.
- **Detected attributes**: seniority (intern through executive), remote/hybrid/on-site, role category, technologies mentioned (Python, AWS, React, Kubernetes, …).
- **Tracking fields**: `firstSeen`, `lastSeen`, and `isNew` to spot fresh openings between scheduled runs.
- **Full descriptions** (text + HTML, optional) for deeper analysis or LLM enrichment.

***

### What this scraper does NOT do

The actor only extracts **publicly available** job postings. It does not:

- Scrape LinkedIn Jobs, Indeed, Glassdoor, or any other job aggregator.
- Access private ATS dashboards or recruiter views.
- Bypass logins, captchas, or anti-bot systems.
- Collect candidate data, applications, or private salary information.

This keeps the data legal, stable, and reliable across long-running schedules.

***

### Supported ATS platforms

| ATS | Career page URL pattern | Example slug |
|---|---|---|
| Workday | `https://{tenant}.{dataCenter}.myworkdayjobs.com/{locale}/{site}` or `https://{dataCenter}.myworkdaysite.com/recruiting/{tenant}/{site}` | `tenant/site` plus full URL |
| Greenhouse | `https://boards.greenhouse.io/{slug}` | `stripe` |
| Lever | `https://jobs.lever.co/{slug}` | `spotify` |
| Ashby | `https://jobs.ashbyhq.com/{slug}` | `notion` |
| Workable | `https://apply.workable.com/{slug}` | `huggingface` |
| SmartRecruiters | `https://careers.smartrecruiters.com/{slug}` | `SmartRecruiters` |
| Recruitee | `https://{slug}.recruitee.com` | `helloprint` |
| Personio | `https://{slug}.jobs.personio.com` or `.de` | company hostname |

The Actor uses public, no-login endpoints only. A Personio company must have its public XML feed enabled. For Workday, provide the full public career-site URL because the tenant, data center, and site name are all required.

***

### How to use the ATS jobs scraper

Paste career-page URLs, provide advanced explicit targets, or choose curated preset lists. You can combine all three methods; duplicate companies are merged automatically.

#### 1. Paste career-page URLs (recommended)

```json
{
  "careerPageUrls": [
    "https://nvidia.wd5.myworkdayjobs.com/en-US/NVIDIAExternalCareerSite",
    "https://boards.greenhouse.io/stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/notion"
  ],
  "keywords": ["python", "backend"],
  "locations": ["remote", "europe"],
  "includeDescriptions": true
}
```

You can paste either a career-page URL or an individual job URL. The Actor recognizes the ATS hostname and extracts the correct company board automatically.

#### 2. Advanced explicit targets

Use explicit `{ats, slug}` objects when you already store normalized ATS identifiers. A URL-only target works too:

```json
{
  "targets": [
    { "ats": "greenhouse", "slug": "stripe" },
    { "ats": "lever", "slug": "spotify" },
    { "url": "https://apply.workable.com/huggingface" },
    {
      "ats": "workday",
      "slug": "nvidia/NVIDIAExternalCareerSite",
      "url": "https://nvidia.wd5.myworkdayjobs.com/en-US/NVIDIAExternalCareerSite"
    }
  ]
}
```

You can override company metadata when an ATS uses an abbreviated or legacy board name:

```json
{
  "targets": [{
    "url": "https://jobs.lever.co/acme-holdings",
    "companyName": "Acme",
    "companyDomain": "acme.com"
  }]
}
```

The Actor takes company names from ATS data when available and otherwise creates a readable name from the board slug. It derives `companyDomain` only from a corporate job URL or an explicit override; ATS-owned domains are never reported as the employer's domain.

How to identify an explicit ATS target:

1. Open the company's careers page.
2. Look at the URL of any job listing (or the iframe/embed).
3. For Workday, copy the complete career-site URL. For other platforms, use the slug shown in the URL:
   - `nvidia.wd5.myworkdayjobs.com/en-US/NVIDIAExternalCareerSite` → full URL plus slug `nvidia/NVIDIAExternalCareerSite`
   - `wd1.myworkdaysite.com/recruiting/snapchat/snap` → full URL plus slug `snapchat/snap`
   - `boards.greenhouse.io/stripe` → slug is `stripe`
   - `jobs.lever.co/spotify` → slug is `spotify`
   - `jobs.ashbyhq.com/notion` → slug is `notion`
   - `apply.workable.com/huggingface` → slug is `huggingface`
   - `careers.smartrecruiters.com/SmartRecruiters` → slug is `SmartRecruiters`
   - `helloprint.recruitee.com` → slug is `helloprint`
   - `acme.jobs.personio.com` → slug is `acme`

#### 3. Preset company lists

Skip the manual research and run one or more curated or automatically live-verified bundles of companies:

```json
{
  "presetLists": ["ai-ml", "fintech"],
  "keywords": ["engineer", "ml"],
  "locations": ["remote", "europe"]
}
```

Currently available:

| Preset | Companies | Examples |
|---|--:|---|
| `all-active` | 1,929 boards | Maximum coverage across all eight ATS platforms |
| `startup-growth` | 900 boards | Active companies on Greenhouse, Lever, Ashby, and Workable |
| `large-employers` | 544 boards | Active employers on Workday and SmartRecruiters |
| `personio-recruitee` | 485 boards | Active employers using Personio or Recruitee |
| `top-tech` | 43 | OpenAI, NVIDIA, Adobe, Salesforce, Autodesk, CrowdStrike, Stripe, Anthropic, Notion, Spotify, Cloudflare, Datadog |
| `ai-ml` | 38 | Anthropic, OpenAI, NVIDIA, Hugging Face, Adobe, Cohere, Cursor, Perplexity, ElevenLabs, Pinecone, Scale AI |
| `fintech` | 24 | Stripe, Brex, Ramp, Monzo, Robinhood, Affirm, Adyen, bunq, Starling Bank, Carta, Lithic, Modern Treasury |
| `devtools` | 44 | GitLab, Supabase, PostHog, Linear, Cursor, Sentry, Render, Datadog, Cloudflare, Twilio, Autodesk, CrowdStrike |
| `eu-startups` | 45 | Hugging Face, Monzo, ElevenLabs, Adyen, Spotify, Doctolib, HelloFresh, Celonis, bunq, Fairphone, Ubisoft |
| `yc-alumni` | 24 | Stripe, Airbnb, Brex, Reddit, Notion, Gusto, Vanta, Webflow, Replit, Algolia, Coinbase, Figma |
| `crypto-web3` | 11 | Alchemy, Ripple, Anchorage Digital, Mysten Labs, OpenSea, Phantom, Foundation, Gemini, Coinbase |
| `remote-first` | 18 | GitLab, PostHog, Supabase, Linear, Hugging Face, Buffer, Zapier, Mattermost, Notion, Tailscale, Weaviate |

You can combine multiple presets and `targets` in the same run. Duplicates are removed automatically. Presets cover all eight supported ATS platforms, including full Workday career-site URLs. The bundled boards are live-verified before release; if a company later changes ATS provider, it shows up in `FAILED_TARGETS` as `not_found`.

For `all-active`, use `keywords` and `locations` and consider lowering `maxJobsPerTarget` for faster runs. The broad preset intentionally prioritizes maximum coverage and can take longer than the curated thematic lists.

***

### Filtering job postings

| Field | What it does |
|---|---|
| `keywords` | Keep only jobs whose title, department, location, or description contains at least one keyword (case-insensitive). |
| `excludeKeywords` | Drop any job matching any of these keywords (e.g. `intern`). |
| `locations` | Keep only jobs whose location contains one of these terms (e.g. `remote`, `europe`, `berlin`). |
| `maxJobsPerTarget` | Hard cap fetched per company before filters run (default: 200). Raise it to search deeper into large boards. |
| `includeDescriptions` | Whether to include full descriptions in output (default: true). |

***

### Example normalized job output

```json
{
  "jobId": "greenhouse:stripe:1234567",
  "source": "greenhouse",
  "sourceJobId": "1234567",
  "sourceUrl": "https://stripe.com/jobs/search?gh_jid=1234567",
  "companyName": "Stripe",
  "companyDomain": "stripe.com",
  "atsBoard": "stripe",
  "jobTitle": "Senior Python Engineer",
  "department": "Engineering",
  "locationText": "Remote, Europe",
  "locations": ["Remote", "Europe"],
  "remoteType": "remote",
  "employmentType": null,
  "seniority": "senior",
  "roleCategory": "engineering",
  "technologies": ["python", "django", "postgresql", "aws"],
  "descriptionText": "Build high-scale services at Stripe...",
  "descriptionHtml": "<p>Build high-scale services...</p>",
  "publishedAt": "2026-05-15T16:00:00Z",
  "scrapedAt": "2026-05-18T12:00:00Z",
  "firstSeen": "2026-05-01T08:00:00Z",
  "lastSeen": "2026-05-18T12:00:00Z",
  "isNew": false,
  "isMatchedByFilters": true,
  "matchedKeywords": ["python"],
  "dedupeKey": "greenhouse:stripe:1234567"
}
```

***

### Run summary

After each run, check the default **Key-Value Store**:

- `OUTPUT` — counts by source, started/finished timestamps, totals.
- `FAILED_TARGETS` — per-target errors (404 board not found, network, parse).
- `UNSUPPORTED_TARGETS` — preset names that were not found.

***

### Tracking new job postings

The actor keeps its own state in a named Key-Value Store (`JOB_STATE`). On every run:

- A job seen for the first time gets `isNew: true` and `firstSeen` = now.
- A job seen previously keeps its original `firstSeen` and updates `lastSeen`.

Run the actor on a schedule (every day, every hour, …) and filter the dataset by `isNew = true` to get a feed of fresh job postings only. Ideal for a daily "new tech jobs" alert, a Slack digest, or a webhook into your CRM.

***

### Troubleshooting

| Problem | Likely cause |
|---|---|
| `not_found` for a target | The slug does not exist on that ATS, or the company changed ATS provider. |
| No jobs returned | The company has no public openings, or your filters are too narrow. |
| `network_error` repeated | ATS endpoint is rate-limiting. Enable the Apify proxy or reduce concurrency. |
| Missing descriptions | Some ATS responses omit descriptions for some jobs (rare). Enable `includeDescriptions`. |
| Want LinkedIn jobs | Out of scope. Use a dedicated LinkedIn Jobs actor instead. |

***

### FAQ

**What is an ATS job scraper?**
An ATS (Applicant Tracking System) job scraper pulls openings directly from company career pages hosted on systems such as Workday, Greenhouse, Lever, and Workable. Because the data comes from each company's own public board, it is usually more timely and structured than data scraped from a job aggregator.

**Does this scraper work with LinkedIn Jobs or Indeed?**
No. The Actor supports the eight public ATS platforms listed above and intentionally avoids aggregators like LinkedIn, Indeed, and Glassdoor for legal and stability reasons.

**Can I scrape job postings from any company?**
You can scrape companies whose public career page uses Workday, Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, or Personio. iCIMS, Oracle Taleo, SAP SuccessFactors, and fully custom career sites are not yet supported.

**Can I combine different ATS platforms in one run?**
Yes. Paste URLs from any supported ATS and combine them with explicit targets or curated preset lists. Every posting is normalized into the same dataset schema, regardless of its source.

**Do I need to know which ATS a company uses?**
No. Paste its public career-page or job URL into `careerPageUrls`; the Actor detects the ATS and company board automatically. Custom career-site domains that hide the underlying provider still need an explicit target.

**Where do company names and domains come from?**
Company names come from the ATS response when available, with the board slug as a fallback. Company domains are returned only when a job points to the employer's own website or you provide `companyDomain`; the Actor does not mistake Workday, Greenhouse, Lever, or another ATS host for the employer's domain.

**Does the Actor scrape candidate or applicant data?**
No. It reads public job listings only. It never signs in to employer dashboards and does not collect applications, resumes, candidate profiles, or recruiter-only data.

**How often should I run the scraper?**
Daily is enough for most use cases. For time-sensitive hiring intelligence, hourly works too. Public ATS endpoints rarely rate-limit, but you can enable the Apify proxy if you hit issues. Use the `isNew` field to alert on fresh postings only.

**Does Apify proxy cost extra when scraping Greenhouse, Lever, or Ashby?**
The actor uses public, free ATS endpoints, so most runs do not need a proxy. Apify proxy is optional and only useful if a board starts rate-limiting your IP.

**How is this different from a generic web scraper?**
This Actor reads structured ATS feeds directly instead of parsing rendered career-page HTML, so output is stable, fast, and resilient to visual redesigns. You also get post-processing out of the box: seniority detection, remote-type detection, role categorization, technology tagging, cross-source deduplication, and new-job tracking.

***

### Roadmap

- **v0.6** — CSV target upload; industry-specific presets; hiring velocity metrics.
- **v0.7** — iCIMS and Oracle Taleo adapters.
- **v2** — Slack / webhook alerts on new jobs; watchlists; scheduled run templates.

# Actor input Schema

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

Paste company career-page or job URLs. The prefilled sample uses four boards for a quick first run. The ATS and company identifier are detected automatically. Supported hosts include myworkdayjobs.com, myworkdaysite.com, greenhouse.io, lever.co, ashbyhq.com, workable.com, smartrecruiters.com, recruitee.com, and personio.com/.de.

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

Optional explicit targets for API users. Use `{ats, slug}`, or provide `{url}` and let the Actor detect both fields. Add optional `companyName` and `companyDomain` to override discovered metadata. Workday explicit targets require `url`. Values from this field are combined with Career page URLs and preset lists.

## `presetLists` (type: `array`):

Curated and live-verified company lists bundled with the Actor. Pick any combination — duplicates across presets are merged automatically. `all-active` provides maximum coverage but can take longer, so use filters and a suitable per-company limit.

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

Only return jobs whose title, department, location, or description contains at least one of these keywords. Leave empty to return all jobs.

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

Drop any job matching one of these keywords.

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

Only return jobs whose location matches one of these (case-insensitive). Region keywords expand into their countries and cities: `europe` matches Dublin/London/Berlin/…, `americas` matches US cities/Canada/Mexico/…, `apac` matches Tokyo/Singapore/Sydney/Bengaluru/…, `emea` adds Middle East and Africa. Use plain terms like `remote`, `berlin`, or `united states` for literal matches.

## `maxJobsPerTarget` (type: `integer`):

Hard cap on jobs fetched per company before filters are applied. Increase it to search deeper into large boards; lower values make runs faster and cheaper but can miss matching jobs beyond the first results.

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

Return the full job description (HTML and plain text). Disable for smaller, lighter outputs.

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

Optional Apify proxy. Public ATS endpoints usually work without proxy.

## Actor input object example

```json
{
  "careerPageUrls": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/notion",
    "https://apply.workable.com/huggingface"
  ],
  "presetLists": [],
  "keywords": [
    "engineer"
  ],
  "excludeKeywords": [
    "intern"
  ],
  "locations": [
    "remote",
    "europe"
  ],
  "maxJobsPerTarget": 20,
  "includeDescriptions": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All normalized job postings.

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

Aggregate stats by ATS and target.

## `failedTargets` (type: `string`):

Per-target errors (404, network, parse).

# 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 = {
    "careerPageUrls": [
        "https://boards.greenhouse.io/stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/notion",
        "https://apply.workable.com/huggingface"
    ],
    "keywords": [
        "engineer"
    ],
    "excludeKeywords": [
        "intern"
    ],
    "locations": [
        "remote",
        "europe"
    ],
    "maxJobsPerTarget": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("i-scraper/ats-jobs-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 = {
    "careerPageUrls": [
        "https://boards.greenhouse.io/stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/notion",
        "https://apply.workable.com/huggingface",
    ],
    "keywords": ["engineer"],
    "excludeKeywords": ["intern"],
    "locations": [
        "remote",
        "europe",
    ],
    "maxJobsPerTarget": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("i-scraper/ats-jobs-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 '{
  "careerPageUrls": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/notion",
    "https://apply.workable.com/huggingface"
  ],
  "keywords": [
    "engineer"
  ],
  "excludeKeywords": [
    "intern"
  ],
  "locations": [
    "remote",
    "europe"
  ],
  "maxJobsPerTarget": 20
}' |
apify call i-scraper/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,i-scraper/ats-jobs-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/mFupByfZCBcPJokm1/builds/8Shw00YF8YXkfRCbK/openapi.json
