# ATS Jobs Scraper - Greenhouse, Lever, Ashby, Workday & more (`lsdflying/ats-jobs-scraper`) Actor

Scrape job postings from Greenhouse, Lever, Ashby, Workday, SmartRecruiters and Workable career sites. Input company names, domains or careers URLs. Normalized fields, salary, remote flag and an 'only new jobs' mode for daily monitoring.

- **URL**: https://apify.com/lsdflying/ats-jobs-scraper.md
- **Developed by:** [Liang Undef](https://apify.com/lsdflying) (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

from $1.00 / 1,000 job scrapeds

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 Scraper – Greenhouse, Lever, Ashby, Workday, SmartRecruiters & Workable

**Scrape job postings straight from company career sites** powered by the six most popular applicant tracking systems (ATS): **Greenhouse, Lever, Ashby, Workday, SmartRecruiters and Workable**. Give it company names, domains, careers pages or board URLs – the Actor auto-detects the ATS, calls the same public job-board endpoints the career pages use, and returns **one clean, normalized format** for every job: title, company, location, remote flag, department, employment type, **salary**, posted date, apply URL and description.

Turn on **Only new jobs since last run** and schedule it daily to get a feed of fresh openings – perfect for job alerts, recruiting and sales-intelligence workflows.

### Why this ATS scraper?

- ✅ **6 ATS in one Actor** – no need to chain separate Greenhouse/Lever/Ashby/Workday scrapers.
- 🔎 **Company auto-discovery** – type `stripe.com`, `Notion` or `https://linear.app/careers`; the Actor finds the job board for you.
- 🧹 **Normalized output** – same fields regardless of ATS, so you can merge, filter and dedupe easily.
- 💰 **Salary extraction** – structured pay ranges from Ashby/Lever, plus pay-transparency ranges found in descriptions (e.g. `$168K - $285K`, `€132.000—€165.000`).
- 🆕 **Only-new mode** – remembers what it already returned; scheduled runs output only newly posted jobs.
- ⚡ **Fast & cheap** – uses public JSON endpoints, no browser. Hundreds of jobs in seconds.
- 🛡️ **Reliable** – retries with backoff, per-board error isolation, run summary with any boards that failed.

### Use cases

- **Job aggregators & job boards** – fill your site with fresh roles from hundreds of tech companies.
- **Job alerts** – "new remote data-engineer jobs at my 50 dream companies" every morning.
- **Sales intelligence / hiring signals** – companies hiring for "Salesforce admin" are prospects for your CRM consultancy.
- **Recruiting & talent intelligence** – track competitors' hiring by department and location.
- **Labor-market & salary research** – collect pay ranges by title and city.

### Input

| Field | Description |
|---|---|
| `companies` | Board URLs, `ats:slug`, careers pages, domains or company names (see examples below) |
| `keywords` / `excludeKeywords` | Filter by words in the job title |
| `locations` | Keep jobs whose location/country contains any value (e.g. `London`, `Germany`, `Remote`) |
| `departments` | Filter by department/team |
| `remoteOnly` | Only remote jobs |
| `postedWithinDays` | Only jobs posted in the last N days |
| `onlyNewJobs` + `stateKey` | Output only jobs not returned by previous runs |
| `maxJobsPerCompany`, `maxItems` | Limits (newest jobs first) |
| `descriptionFormat` | `text`, `html`, `both` or `none` |

#### Supported company formats

```
https://boards.greenhouse.io/airbnb          greenhouse:stripe
https://jobs.lever.co/spotify                lever:spotify     lever-eu:<slug>
https://jobs.ashbyhq.com/ramp                ashby:notion
https://nvidia.wd5.myworkdayjobs.com/en-US/NVIDIAExternalCareerSite
https://jobs.smartrecruiters.com/Equinox     smartrecruiters:Visa
https://apply.workable.com/skroutz           workable:skroutz
https://linear.app/careers   stripe.com   Notion      (auto-detected)
```

Example – daily feed of new remote engineering jobs:

```json
{
  "companies": ["stripe.com", "Notion", "https://jobs.ashbyhq.com/ramp", "lever:spotify"],
  "keywords": ["engineer", "developer"],
  "remoteOnly": true,
  "onlyNewJobs": true,
  "stateKey": "remote-eng",
  "maxJobsPerCompany": 0,
  "maxItems": 0
}
```

### Output

Each job is one dataset item (export to JSON, CSV, Excel, or via API/webhooks):

```json
{
  "jobId": "29a5da77-37f0-402a-92bc-072778e77ed8",
  "ats": "ashby",
  "companySlug": "ramp",
  "company": "Ramp",
  "title": "Software Engineer, Forward Deployed",
  "location": "New York, NY (HQ)",
  "locations": [
    "New York, NY (HQ)",
    "San Francisco, CA"
  ],
  "country": "USA",
  "isRemote": true,
  "workplaceType": "Hybrid",
  "department": "Engineering",
  "team": "Engineering",
  "employmentType": "Full-time",
  "salary": {
    "min": 168000,
    "max": 285000,
    "currency": "USD",
    "interval": "1 YEAR",
    "text": "$168K - $285K",
    "source": "ats"
  },
  "postedAt": "2026-10-22T19:20:16.077Z",
  "updatedAt": null,
  "jobUrl": "https://jobs.ashbyhq.com/ramp/29a5da77-37f0-402a-92bc-072778e77ed8",
  "applyUrl": "https://jobs.ashbyhq.com/ramp/29a5da77-37f0-402a-92bc-072778e77ed8/application",
  "requisitionId": null,
  "descriptionText": "About Ramp\nRamp is building the smart infrastructure for finance teams, embedded in the transaction flow of every dollar a business spends. We automat...",
  "scrapedAt": "2026-10-26T14:26:13.295Z"
}
```

Fields: `jobId, ats, companySlug, company, title, location, locations, country, isRemote, workplaceType, department, team, employmentType, salary {min, max, currency, interval, text, source}, postedAt, updatedAt, jobUrl, applyUrl, requisitionId, descriptionText, descriptionHtml, scrapedAt`. Fields the ATS does not publish are `null`.

A `RUN_SUMMARY` record in the key-value store lists boards scanned, unresolved companies and failures.

### Pricing

Pay per result: **$0.0012 per job** (about $1.20 per 1,000 jobs) plus a tiny start fee per run. Higher Apify plans get a discount. No proxy or browser costs.

### FAQ

**How does "only new jobs" work?**
Job IDs that were returned are stored in a named key-value store (`ats-jobs-scraper-state`, record `seen-<stateKey>`). Next run skips them. Use a different `stateKey` per watchlist. The first run sets the baseline and returns all matching jobs.

**My company isn't detected.**
Paste the direct job-board URL (open any job on their careers page and copy the link) or use `ats:slug`. Companies on other ATS (e.g. iCIMS, Taleo) are not supported yet – open an issue.

**Is a company name always matched to the right board?**
Name guessing tries the most likely slugs; for generic names it can hit a different company with the same slug. Check the `company` field or use a URL/domain for exact matching.

**Why is salary sometimes missing?**
Many employers don't publish pay. We return structured salary when the ATS provides it and otherwise extract a pay range from the description text when one is present (`salary.source` = `description`).

**Is this legal?**
The Actor reads only public job postings that companies publish for candidates, via public job-board endpoints, without login. Respect the sites' terms and applicable laws when reusing the data.

**Something broke?**
Open an issue in the Issues tab – we fix ATS changes quickly.

# Actor input Schema

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

One company per line. Accepts: ATS board URLs (boards.greenhouse.io/airbnb, jobs.lever.co/spotify, jobs.ashbyhq.com/ramp, nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite, jobs.smartrecruiters.com/Equinox, apply.workable.com/skroutz), explicit slugs (greenhouse:stripe, lever:spotify, lever-eu:slug, ashby:notion, workable:slug, smartrecruiters:Visa), company careers pages / domains (linear.app, stripe.com) or plain company names (Notion) - the ATS is auto-detected.

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

Keep only jobs whose title contains any of these words (case-insensitive), e.g. engineer, data, sales.

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

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

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

Keep only jobs whose location/country contains any of these strings, e.g. London, Germany, Remote, US.

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

Keep only jobs whose department or team contains any of these strings.

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

Keep only jobs flagged as remote (by the ATS or by the location text).

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

Keep only jobs posted in the last N days. 0 = any date.

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

Remember job IDs already returned (in a named key-value store) and output only jobs not seen in previous runs. Ideal for scheduled daily/hourly monitoring. The first run returns everything and sets the baseline.

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

Use different names to keep separate 'seen jobs' memories for different watchlists.

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

Newest jobs first. 0 = no limit.

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

Stop after saving this many jobs. 0 = no limit.

## `descriptionFormat` (type: `string`):

Include the job description as plain text, HTML, both, or not at all (smaller, faster output).

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

How many companies to process at the same time.

## Actor input object example

```json
{
  "companies": [
    "https://jobs.ashbyhq.com/ramp",
    "greenhouse:airbnb",
    "lever:spotify"
  ],
  "keywords": [],
  "excludeKeywords": [],
  "locations": [],
  "departments": [],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "onlyNewJobs": false,
  "stateKey": "default",
  "maxJobsPerCompany": 10,
  "maxItems": 30,
  "descriptionFormat": "text",
  "maxConcurrency": 3
}
```

# Actor output Schema

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

No description

## `allData` (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": [
        "https://jobs.ashbyhq.com/ramp",
        "greenhouse:airbnb",
        "lever:spotify"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("lsdflying/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://jobs.ashbyhq.com/ramp",
        "greenhouse:airbnb",
        "lever:spotify",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("lsdflying/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://jobs.ashbyhq.com/ramp",
    "greenhouse:airbnb",
    "lever:spotify"
  ]
}' |
apify call lsdflying/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

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