# Greenhouse, Lever, Ashby & Workday Jobs Scraper – 10 ATS (`openharvest/ats-jobs-scraper`) Actor

Scrape open jobs from Greenhouse, Lever, Ashby, Workday, Workable, SmartRecruiters, Recruitee, Personio, Teamtailor and BambooHR in one format. Paste any careers page and the ATS is detected automatically. Filter by keyword, location, remote, department and date.

- **URL**: https://apify.com/openharvest/ats-jobs-scraper.md
- **Developed by:** [Enes Bulanık](https://apify.com/openharvest) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 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.

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 Scraper – 10 ATS

Scrape open jobs from Greenhouse, Lever, Ashby, Workday, Workable, SmartRecruiters, Recruitee, Personio, Teamtailor and BambooHR in one format. Paste any careers page and the ATS is detected automatically. Filter by keyword, location, remote, department and date.

**Price: $1 per 1,000 jobs.** No login, no API keys, no browser. Companies that can't be detected are reported in a free status row.

### What does ATS Job Scraper do?

Most companies publish their jobs through an ATS. Each ATS has its own public job-board feed with its own format. This Actor:

- **Detects the ATS** from any URL you paste. It recognizes job-board URLs (`jobs.lever.co/spotify`), finds the ATS link, embed script or iframe on a careers page (`linear.app/careers` → Ashby), follows one "Careers / Jobs" link, recognizes Teamtailor and Recruitee career sites on custom domains (`jobs.storytel.com`), and as a last resort tries the company name on every ATS API (`stripe.com/jobs` → Greenhouse `stripe`).
- **Downloads all open jobs** with pagination (Workday, SmartRecruiters, Lever and Workable boards with thousands of jobs work).
- **Normalizes everything** into one flat schema: title, department, team, location(s), country, remote / hybrid / on-site, employment type, posted & updated date, job URL, apply URL, salary range (when the ATS publishes it), requisition ID and an optional plain-text description.
- **Filters** by title keywords, excluded words, location, remote-only, department and "posted since" date.
- **Reports every company** in a free `company` row: which ATS was detected and how, board URL, total / matched / saved jobs or a clear error.

#### Supported ATSs and data sources

| ATS | Public endpoint used | Salary | Remote flag | Notes |
|---|---|---|---|---|
| Greenhouse | `boards-api.greenhouse.io/v1/boards/{token}/jobs?content=true` | – | from location text | departments, offices, requisition ID |
| Lever | `api.lever.co/v0/postings/{company}?mode=json` | yes | yes (remote / hybrid / on-site) | EU boards (`jobs.eu.lever.co`) supported |
| Ashby | `api.ashbyhq.com/posting-api/job-board/{org}?includeCompensation=true` | yes | yes | secondary locations, team |
| Workable | `apply.workable.com/api/v3/accounts/{sub}/jobs` (+ widget API) | – | yes | |
| SmartRecruiters | `api.smartrecruiters.com/v1/companies/{id}/postings` | – | yes | description via posting detail |
| Recruitee | `{sub}.recruitee.com/api/offers/` (or custom domain) | yes | yes | recruiter e-mails are never exported |
| Personio | `{sub}.jobs.personio.de/xml` (and `.com`) | – | from office name | English feed, falls back to the original language |
| Teamtailor | `{sub}.teamtailor.com/jobs.rss` (or custom domain) | – | yes | |
| BambooHR | `{sub}.bamboohr.com/careers/list` + `/careers/{id}/detail` | – | yes | |
| Workday | `POST {tenant}.wdN.myworkdayjobs.com/wday/cxs/{tenant}/{site}/jobs` + job detail | – | from location text | exact posting date & time type from detail |

### Use cases

- **Job boards & aggregators** – fill a niche job board (remote, AI, climate, Europe…) from hundreds of company career pages with one Actor.
- **Recruiting & sales intelligence** – see who is hiring, for which teams and where; trigger outreach when a target account opens roles.
- **Market & competitor research** – track hiring velocity, departments and locations of competitors over time.
- **Salary research** – collect published salary ranges (Lever, Ashby, Recruitee).
- **Job seekers & alerts** – run on a schedule with `postedSince: "1 day"` and keywords to get only new matching roles.
- **AI / LLM pipelines** – clean plain-text descriptions for RAG, classification or skill extraction.

### How to use

1. Click **Try for free** / **Start**.
2. Paste companies into **Companies** – careers pages, job-board URLs, `ats:slug` pairs or plain company names.
3. Optionally add **Job title keywords**, **Location contains**, **Remote jobs only**, **Department contains** and **Posted since**.
4. Turn on **Include job description** if you need the full text.
5. Click **Start** and export as JSON, CSV, Excel, XML or HTML – or connect to Make, Zapier, n8n, Google Sheets, webhooks or the Apify API.

### Input

| Field | Type | Description |
|---|---|---|
| `companies` | array of strings | Careers pages or job-board URLs (`https://linear.app/careers`, `https://jobs.lever.co/spotify`, `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`), `ats:slug` (`greenhouse:stripe`, `workday:nvidia.wd5/NVIDIAExternalCareerSite`) or company names (`Hugging Face`). Empty = demo run. |
| `keywords` | array | Keep jobs whose **title** contains any of these. |
| `excludeKeywords` | array | Drop jobs whose title contains any of these. |
| `locations` | array | Keep jobs whose location/country contains any of these (`remote` also matches remote jobs). |
| `remoteOnly` | boolean | Only remote jobs. `includeHybrid` also keeps hybrid jobs. |
| `departments` | array | Keep jobs whose department or team contains any of these. |
| `postedSince` | string | `2026-01-31` or `7 days`, `2 weeks`, `1 month`. |
| `includeDescription` | boolean | Add `descriptionText` (HTML converted to clean text). Default `false`. |
| `maxJobsPerCompany` | integer | Max jobs saved per company after filters. `0` = no limit. Default `100`. |
| `companyBoards` | array of objects | For API users: `[{"ats": "lever", "slug": "spotify", "name": "Spotify"}]`. |
| `guessAtsFromName` | boolean | Try the company name on all ATS APIs when detection fails. Default `true`. |
| `includeCompanySummary` | boolean | Add a free status row per company. Default `true` (error rows are always added). |
| `proxyConfiguration` | object | Optional; not needed for these public APIs. |

Example input:

```json
{
    "companies": [
        "https://linear.app/careers",
        "https://boards.greenhouse.io/stripe",
        "lever:spotify",
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
        "https://jobs.storytel.com/jobs",
        "Hugging Face"
    ],
    "keywords": ["engineer"],
    "excludeKeywords": ["intern"],
    "remoteOnly": true,
    "includeHybrid": true,
    "postedSince": "30 days",
    "maxJobsPerCompany": 50,
    "includeDescription": true
}
```

### Output

Every item has a `type`: `job` (charged) or `company` (free status row). Real sample output (description shortened):

**Job (Ashby, detected from `https://ramp.com/careers`)**

```json
{
    "type": "job",
    "ats": "ashby",
    "company": "Ramp",
    "companySlug": "ramp",
    "jobId": "4e64ab86-4e30-403b-b1b9-41dc052570ce",
    "title": "Software Engineer, Frontend",
    "department": "Engineering",
    "team": "Frontend",
    "location": "New York, NY (HQ)",
    "locations": ["New York, NY (HQ)", "Remote (Canada)", "San Francisco, CA", "Remote (US)", "Miami, FL"],
    "country": "USA",
    "remote": "hybrid",
    "workplaceType": "hybrid",
    "employmentType": "Full-time",
    "postedAt": "2023-03-09T17:44:00.817Z",
    "updatedAt": null,
    "url": "https://jobs.ashbyhq.com/ramp/4e64ab86-4e30-403b-b1b9-41dc052570ce",
    "applyUrl": "https://jobs.ashbyhq.com/ramp/4e64ab86-4e30-403b-b1b9-41dc052570ce/application",
    "salaryMin": 143200,
    "salaryMax": 284000,
    "salaryCurrency": "USD",
    "salaryPeriod": "year",
    "requisitionId": null,
    "descriptionText": "About Ramp\n\nRamp is building the smart infrastructure for finance teams... \n\nWhat You’ll Do\n• Build!\n• Design performant, beautiful, and usable interfaces\n...",
    "sourceInput": "https://ramp.com/careers",
    "scrapedAt": "2026-10-07T19:27:49.448Z"
}
```

**Company row (free)**

```json
{
    "type": "company",
    "input": "https://www.lifeatspotify.com/jobs",
    "company": "Spotify",
    "ats": "lever",
    "slug": "spotify",
    "detectedFrom": "slug-guess",
    "boardUrl": "https://jobs.lever.co/spotify",
    "status": "OK",
    "jobsTotal": 76,
    "jobsMatched": 2,
    "jobsSaved": 1,
    "message": "Saved 1 job (limit \"Max jobs per company\" reached; 76 open in total)."
}
```

`remote` is `true` (remote), `"hybrid"`, `false` (on-site) or `null` (not stated); `workplaceType` is the same as text (`remote` / `hybrid` / `onsite`). `salaryPeriod` is `year`, `month`, `week`, `day` or `hour`. Salary fields are filled only when the company publishes a range in the ATS – nothing is estimated.

`detectedFrom`: `input` (explicit `ats:slug`), `url` (job-board URL), `redirect`, `careers-page` (link/script on the page), `careers-link` (found one click away), `custom-domain` (Teamtailor/Recruitee site on the company's domain), `slug-guess` (company name tried on the ATS APIs).

`status`: `OK`, `NO_JOBS`, `NO_MATCHING_JOBS`, `CHARGE_LIMIT`, `DUPLICATE`, `NOT_DETECTED`, `BOARD_NOT_FOUND`, `INVALID_INPUT`, `FAILED`.

The dataset has ready-made views: **Overview**, **Full job details** and **Companies & detection**. To export only jobs, filter on `type = job` (or use the Overview view).

### Pricing

Pay-per-event – you pay only for jobs saved to the dataset. Platform usage is included.

| Event | Price | When it is charged |
|---|---|---|
| `job` | **$0.001** per job ($1 / 1,000) | Each job saved to the dataset |
| Company / detection rows | **Free** | Detection results, empty boards, errors, duplicates |

| Job | Cost |
|---|---|
| All jobs of 10 startups (~300 jobs) | **$0.30** |
| Remote engineering jobs from 200 companies (~2,000 jobs) | **$2.00** |
| 10,000 jobs | **$10.00** |

Filters are applied **before** charging – jobs that don't match are never billed. Set **Max cost per run** in the run options and the Actor stops exactly at your budget.

### Use with AI agents (MCP)

This Actor works as a tool for AI agents through the [Apify MCP server](https://mcp.apify.com) (Claude, ChatGPT, Cursor, n8n…). The input is small and typed and the output is flat JSON, so agents can call it directly. Example tool input an agent can send:

```json
{
    "companies": ["https://linear.app/careers", "https://jobs.lever.co/spotify"],
    "keywords": ["engineer"],
    "remoteOnly": true,
    "maxJobsPerCompany": 20
}
```

Typical prompts: *"Which remote engineering roles do Linear and Spotify have open?"*, *"Which ATS does notion.com use and how many jobs are open?"*, *"List data jobs posted at NVIDIA in the last 7 days."*

### FAQ

**Do I need an account or API key for any ATS?**
No. All 10 sources are the public job-board endpoints that power the companies' own career pages.

**What if my careers page isn't detected?**
Some companies render jobs with JavaScript from a private API or use an ATS outside this list (e.g. iCIMS, Taleo, SuccessFactors, Jobvite). You get a free `NOT_DETECTED` row. Open the page in a browser, click a job and paste the URL of the job board (e.g. `https://jobs.lever.co/acme`) – or use `ats:slug`.

**How do I enter a Workday company?**
Paste the Workday careers URL, e.g. `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite` (locale parts like `/en-US/` are fine), or `workday:nvidia.wd5/NVIDIAExternalCareerSite`. Workday needs the tenant, the `wdN` data center and the site name, so plain names can't be guessed – but careers pages that link to Workday (like nvidia.com/careers) are detected.

**Can slug guessing pick the wrong company?**
Guessing only accepts boards that have open jobs and – where the ATS exposes it – a company name matching the slug. Check `detectedFrom: "slug-guess"` rows if precision matters, or give explicit URLs.

**Why is `postedAt` empty or old for some jobs?**
It is the date the ATS reports. Greenhouse leaves it empty for some boards; evergreen roles can be years old. Jobs without a date are kept when you use **Posted since**.

**Why are there no departments for Workday jobs?**
Workday's public job feed doesn't include them. The department filter therefore drops Workday jobs.

**How fresh is the data?**
Every run reads the live job boards. Schedule the Actor (e.g. daily with `postedSince: "1 day"`) to get new jobs only.

**Is there a limit?**
No hard limit; large Workday or SmartRecruiters tenants with thousands of jobs are paginated. Use `maxJobsPerCompany` and Max cost per run to control size and cost.

### Is it legal to scrape job postings?

This Actor reads only **public job postings** that companies deliberately publish through their ATS's public job-board APIs – no login, no private data. It does not collect personal data of candidates or recruiters (e.g. recruiter e-mail addresses in Recruitee feeds are dropped). Job descriptions remain the property of the employers: respect each company's terms, attribute and link back to the original posting (`url`) when you republish, and comply with applicable law (e.g. GDPR, copyright). If unsure, consult a lawyer.

### Related actors

- **Website Tech Stack Detector – Bulk CMS, Analytics & Frameworks**: see which tools the hiring companies use
- **Hacker News Scraper – Stories, Comments & Who Is Hiring Jobs**: monthly Who Is Hiring posts
- **MyJobMag, Jobberman, BrighterMonday & Careers24 Jobs Scraper**: jobs from African job boards
- **Turkey Jobs Aggregator – Kariyer, SecretCV, Isbul, Eleman.net**: jobs from Turkish job boards

### Support & feedback

Found a company whose ATS isn't detected, or need another ATS? Open an issue in the **Issues** tab with the careers URL.

# Actor input Schema

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

One company per line. Accepted: a job-board URL (<code>https://jobs.lever.co/spotify</code>, <code>https://boards.greenhouse.io/stripe</code>, <code>https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite</code>), a company careers page (<code>https://linear.app/careers</code> - the ATS is detected automatically), an explicit <code>ats:slug</code> pair (<code>greenhouse:stripe</code>, <code>workday:nvidia.wd5/NVIDIAExternalCareerSite</code>) or just a company name (<code>Hugging Face</code> - the ATS is guessed). Leave empty to run a small demo.

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

Optional. Keep only jobs whose title contains at least one of these words or phrases (case-insensitive), e.g. <code>engineer</code>, <code>product manager</code>.

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

Optional. Drop jobs whose title contains any of these words, e.g. <code>intern</code>, <code>senior</code>.

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

Optional. Keep only jobs whose location (any of the job's locations or its country) contains at least one of these texts, e.g. <code>Berlin</code>, <code>United Kingdom</code>, <code>US</code>. The value <code>remote</code> also matches jobs flagged as remote.

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

Keep only jobs marked as remote by the ATS (or whose location says "Remote").

## `includeHybrid` (type: `boolean`):

With "Remote jobs only" on, also keep hybrid jobs.

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

Optional. Keep only jobs whose department or team contains one of these texts, e.g. <code>Engineering</code>, <code>Sales</code>. Note: Workday and Teamtailor often publish no department.

## `postedSince` (type: `string`):

Optional. Keep only jobs published on or after this date: an absolute date (YYYY-MM-DD) or a relative period such as <code>7 days</code>, <code>2 weeks</code>, <code>1 month</code>. Jobs whose ATS publishes no date are kept.

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

Add the full job description as clean plain text (<code>descriptionText</code>). Makes the dataset larger; for SmartRecruiters it needs one extra request per job.

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

Maximum number of jobs saved per company (after filters). 0 = no limit. You pay per job, so this also caps your cost.

## `companyBoards` (type: `array`):

Optional, for API / automation users. A JSON list of <code>{"ats": "greenhouse", "slug": "stripe", "name": "Stripe"}</code> objects. <code>ats</code> is one of greenhouse, lever, ashby, workable, smartrecruiters, recruitee, personio, teamtailor, bamboohr, workday. For Workday use <code>"slug": "tenant.wdN/Site"</code>.

## `guessAtsFromName` (type: `boolean`):

If a careers page has no recognizable ATS link (or you enter just a company name), try the company name on every supported ATS's public API. Only boards with open jobs and a matching company name are accepted.

## `includeCompanySummary` (type: `boolean`):

Add one free row (type = company) per company with the detected ATS, board URL and job counts. Rows for companies that could not be detected or scraped are always added. These rows are never charged.

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

Usually not needed - all ATS endpoints used are public APIs. Enable only if you see HTTP 429 (rate-limit) warnings on very large runs.

## Actor input object example

```json
{
  "companies": [
    "https://boards.greenhouse.io/stripe",
    "https://linear.app/careers",
    "lever:spotify",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "Hugging Face"
  ],
  "keywords": [
    "engineer",
    "data scientist"
  ],
  "excludeKeywords": [
    "intern",
    "principal"
  ],
  "locations": [
    "London",
    "Germany",
    "remote"
  ],
  "remoteOnly": false,
  "includeHybrid": false,
  "departments": [
    "Engineering",
    "Data"
  ],
  "includeDescription": false,
  "maxJobsPerCompany": 20,
  "companyBoards": [
    {
      "ats": "greenhouse",
      "slug": "stripe",
      "name": "Stripe"
    },
    {
      "ats": "workday",
      "slug": "nvidia.wd5/NVIDIAExternalCareerSite",
      "name": "NVIDIA"
    }
  ],
  "guessAtsFromName": true,
  "includeCompanySummary": true,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

All scraped items in the default dataset (JSON, CSV, Excel and more via the dataset export).

## `files` (type: `string`):

Records saved in the default key-value store, e.g. the run summary.

# 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": [
        "https://job-boards.greenhouse.io/discord",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/linear"
    ],
    "maxJobsPerCompany": 20,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("openharvest/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 = {
    "companies": [
        "https://job-boards.greenhouse.io/discord",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/linear",
    ],
    "maxJobsPerCompany": 20,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("openharvest/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 '{
  "companies": [
    "https://job-boards.greenhouse.io/discord",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/linear"
  ],
  "maxJobsPerCompany": 20,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call openharvest/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

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