# ATS Job Scraper: Greenhouse, Lever, Ashby & More (`planetlinux98/ats-job-scraper`) Actor

Get every open job from company careers pages on Greenhouse, Lever, Ashby, Workable, Recruitee and Personio in one clean format. Filter by keyword, location and remote, or track only new and closed jobs.

- **URL**: https://apify.com/planetlinux98/ats-job-scraper.md
- **Developed by:** [Elliott Tyson](https://apify.com/planetlinux98) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 job saveds

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

**ATS Job Scraper** collects every open job from company careers pages that run on **Greenhouse, Lever, Ashby, Workable, Recruitee or Personio**, and gives them to you in one consistent format. Paste a list of companies, add a few filters if you like, and get job titles, locations, remote status, pay ranges (when published), posting dates, apply links and full descriptions. The results are ready for a spreadsheet, your own job board, a CRM or an AI tool.

It reads the public job feeds these systems provide for companies' own careers pages. No logins and no browser automation, so it's fast (thousands of jobs a minute) and cheap.

### What can this ATS job scraper do?

- **Six job systems, one format.** Greenhouse, Lever, Ashby, Workable, Recruitee and Personio jobs all come out with the same fields, so you don't have to clean up each one differently.
- **Paste whatever you have.** A job-board link (`https://jobs.lever.co/spotify`), a short form (`greenhouse:stripe`), the company's own careers page (we look for the job-board link on it), or just the company name (we try each system).
- **Filter before you pay.** Keep only jobs matching your keywords, locations, departments, remote status or posting date. Filters run before anything is saved, and you only pay for the jobs you keep.
- **Track new and closed jobs.** Turn on monitoring and schedule a daily run. You get only the jobs that are new since last time, plus a row for each job that was taken down.
- **Clear run summary.** For every company you entered, the summary says which system it uses, how many jobs it found, and what went wrong if something did.
- **Built to be polite.** It respects each site's robots.txt and spaces out its requests.

### What data does it collect?

| Field | What it is |
| --- | --- |
| `title`, `companyName`, `companySlug` | Job title, company name (when the system provides it) and the company's board name |
| `ats`, `jobId` | Which system the job came from, and its ID there |
| `location`, `locations`, `isRemote`, `workplaceType` | Main location, all listed locations, remote flag, and remote / hybrid / onsite |
| `employmentType`, `department`, `team` | Full-time, part-time, contract, temporary or internship, plus department and team |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryText` | Pay range, when the company publishes it |
| `postedAt`, `updatedAt` | When the job was posted and last changed |
| `jobUrl`, `applyUrl` | Links to the job and to the application |
| `descriptionText`, `descriptionHtml` | The full description as plain text and/or HTML |
| `changeType` | In monitoring mode: `new` or `closed` |
| `inputEntry`, `scrapedAt` | The line of your input it came from, and when it was collected |

### How to scrape jobs from company careers pages

1. Click **Try for free** (you'll need a free Apify account).
2. In **Companies**, add one company per line. A job-board link is the most reliable, for example `https://job-boards.greenhouse.io/stripe`.
3. Optionally add **keywords**, **locations** or other filters, and choose how you want the job description.
4. Click **Start**. Most runs finish in under a minute.
5. Download the jobs as CSV, Excel or JSON, or connect them to Google Sheets, Make, Zapier, n8n or your own code through the Apify API.

#### Which address should I paste?

Open any job ad on the company's careers site and look at the address bar or the "Apply" link. If it includes `greenhouse.io`, `lever.co`, `ashbyhq.com`, `workable.com`, `recruitee.com` or `personio.de` / `personio.com`, paste that link. If the careers page is on the company's own website, paste that page and we'll look for the job-board link on it.

### Input example

```json
{
    "companies": ["https://jobs.lever.co/spotify", "https://jobs.ashbyhq.com/ramp", "greenhouse:stripe", "Hugging Face"],
    "keywords": ["designer", "engineer"],
    "locations": ["Toronto", "Canada", "Remote"],
    "postedWithinDays": 30,
    "descriptionFormat": "text",
    "maxJobsPerCompany": 100
}
```

### Output example

![Table of sample results from a real run: seven remote engineering jobs at Stripe from its Greenhouse board, with columns for job title, company, system, location, remote and posted date.](https://api.apify.com/v2/key-value-stores/Q61NH7quzCxD8IodC/records/ats-job-scraper-results.png)

One result as JSON:

```json
{
    "title": "Security Engineer, Cloud",
    "companyName": null,
    "companySlug": "ramp",
    "ats": "ashby",
    "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
    "location": "New York, NY (HQ)",
    "locations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)"],
    "isRemote": true,
    "workplaceType": "hybrid",
    "employmentType": "full-time",
    "department": "Engineering",
    "team": "Backend",
    "salaryMin": 211400,
    "salaryMax": 290600,
    "salaryCurrency": "USD",
    "salaryPeriod": "year",
    "salaryText": "$211.4K - $290.6K",
    "postedAt": "2026-04-07T17:12:35.753Z",
    "updatedAt": null,
    "jobUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
    "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
    "changeType": null,
    "descriptionText": "About Ramp\n\nRamp is building the smart infrastructure for finance teams...",
    "descriptionHtml": null,
    "inputEntry": "https://jobs.ashbyhq.com/ramp",
    "scrapedAt": "2026-10-13T12:00:00.000Z"
}
```

### How much does it cost to scrape jobs?

You pay **$0.50 per 1,000 jobs** saved, plus a tiny start fee of $0.00005 per run. There's no monthly fee, and you only pay for jobs that pass your filters.

- 100 jobs: about $0.05
- A large company's whole board (700 jobs): about $0.35
- A daily monitor of 50 companies that finds about 20 new jobs a day: about $0.30 a month

Apify's free plan includes $5 of monthly credit, which covers about 10,000 jobs. To cap any single run, set a maximum cost when you start it; the run stops cleanly when it gets there.

### Track new and closed jobs (monitoring)

Turn on **Only new jobs since the last run** and schedule the Actor (for example daily) in Apify Console:

- The first run returns every matching job. After that, you only get jobs that are new since the previous run.
- With **Also report jobs that were taken down** on, you also get a row marked `closed` for each job that disappeared from the board.
- Give each monitor a **Monitor name** if you run several schedules. Keep the same company list and filters between runs.
- Jobs held back by a limit or the run's maximum cost aren't marked as seen, so they show up next time instead of being lost.

### Run summary

Each run saves a `RUN_SUMMARY` record in its key-value store. For each line of your input it shows one of these statuses:

- `ok` or `no_open_jobs`: the board was found and read.
- `not_found`: no board was found at that address or under that name.
- `ats_not_detected`: we couldn't find a supported job board on the careers page you gave.
- `blocked_by_robots_txt`: the site asks automated tools not to access that address, so we didn't.
- `skipped`: a duplicate of an earlier line, or the run stopped early.
- `error`: something else went wrong; the message explains what.

### Limits and good to know

- **Supported systems:** Greenhouse, Lever, Ashby, Workable, Recruitee and Personio.
- **Not supported:**
  - Workday, iCIMS, Taleo and SuccessFactors: there's no documented public feed.
  - SmartRecruiters: its robots.txt asks automated tools not to use its API.
  - Job boards such as LinkedIn or Indeed.
- **Company names:** Lever and Ashby don't publish them, so `companyName` is empty for those. Use `companySlug` or your input line instead.
- **Pay:** included when the company publishes it in a structured way (Ashby, Lever, Recruitee). Many Greenhouse companies put pay in the description text instead.
- **Typing a company name:** we use the first board with open jobs we find under that name, and a different company with the same name is possible. The run summary flags these matches; check `companyName` and the job links.
- **Careers pages built with JavaScript** may hide their job-board link from us. If a page gives `ats_not_detected`, open a job ad and paste the job-board link instead.
- **Very long lists:** a run stops by itself shortly before its time limit (one hour by default), so it still finishes successfully. Companies not reached are marked `skipped` in the run summary. For big lists, raise the run's timeout or split the list.
- **Email addresses are removed from descriptions** so no personal contact details are collected. Use the apply link to apply.

### FAQ

#### Is it legal to scrape job postings?

This Actor only reads the public job feeds that these systems publish so companies can show jobs on their own careers sites. It doesn't log in, respects robots.txt, and doesn't collect personal data. Job descriptions belong to the employers, so if you plan to republish them in full (for example on a public job board), check that your use is allowed. If you're unsure, ask a lawyer.

#### Can I use it through the API or with AI agents?

Yes. Every run can be started and read through the Apify API, with ready-made clients for JavaScript and Python. AI agents can call it through Apify's MCP server. It's also available in Make, Zapier, n8n and Google Sheets through Apify's integrations.

#### Why did I get fewer jobs than the careers page shows?

Usually it's your filters or the per-company limit. Some companies also list a few jobs on a second system or a separate board, and unlisted jobs (shared only by direct link) aren't in the public feed.

#### Can you add another job system or field?

Please open an issue on the **Issues** tab with an example careers page. Bug reports are welcome there too, and I aim to reply within a couple of days.

# Actor input Schema

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

One company per line. The most reliable option is the address of the company's job board, for example <code>https://jobs.lever.co/spotify</code> or <code>https://job-boards.greenhouse.io/stripe</code>. You can also write <code>system:board</code> (for example <code>greenhouse:stripe</code>), paste the company's own careers page (we look for a job-board link on it), or just type the company name and we'll try each system. Supported: Greenhouse, Lever, Ashby, Workable, Recruitee and Personio.

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

Only keep jobs whose title contains at least one of these words. Matching ignores case and works on the start of words, so <code>engineer</code> also matches "Engineering", but <code>AI</code> doesn't match "Maintenance". Leave empty to keep every job.

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

Turn this on to also match keywords anywhere in the job description, not just the title.

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

Drop jobs whose title contains any of these words, for example <code>intern</code> or <code>senior</code>.

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

Only keep jobs whose location mentions at least one of these places, for example <code>Toronto</code>, <code>Canada</code> or <code>London</code>. Add <code>Remote</code> to also keep remote jobs. Leave empty for all locations.

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

Only keep jobs the company marks as remote.

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

Only keep jobs in departments or teams whose name contains one of these words, for example <code>Engineering</code> or <code>Sales</code>.

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

Only keep jobs posted in the last this-many days. Jobs without a posting date are left out when this is set. Leave empty or 0 for no date filter.

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

How to include the job description. Plain text is easiest to read and to use with AI tools. Choose "None" for smaller, faster results.

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

The newest jobs are kept first. 0 means no limit.

## `maxJobs` (type: `integer`):

Stop once this many jobs have been saved across all companies. 0 means no limit. You can also cap spending with the run's maximum cost setting.

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

Remembers which jobs it has already given you and only returns jobs that are new since the last run. The first run returns every matching job. Ideal for scheduled runs and job alerts. Keep the same company list and filters between runs.

## `includeClosedJobs` (type: `boolean`):

In monitoring mode, also add a row for each job that was open last time but has since disappeared from the board (marked <code>changeType: closed</code>).

## `monitorName` (type: `string`):

Optional. Give each monitor a name (for example <code>toronto-design-jobs</code>) so several schedules can keep separate memories. If left empty, the name is based on your company list. Monitor memory is kept in a key-value store called <code>ats-job-scraper-monitors</code> in your account.

## Actor input object example

```json
{
  "companies": [
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp",
    "greenhouse:stripe"
  ],
  "searchInDescription": false,
  "remoteOnly": false,
  "descriptionFormat": "text",
  "maxJobsPerCompany": 20,
  "maxJobs": 0,
  "onlyNewJobs": false,
  "includeClosedJobs": true
}
```

# Actor output Schema

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

One item per job posting (or per closed job in monitoring mode).

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

What happened for each company you entered: which job system was found, how many jobs, and any problems.

# 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.lever.co/spotify",
        "https://jobs.ashbyhq.com/ramp",
        "greenhouse:stripe"
    ],
    "maxJobsPerCompany": 20
};

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

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

```

## MCP server setup

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