# Career Site Jobs Scraper (Workday, Greenhouse, Lever & more) (`folt/ats-jobs-scraper`) Actor

Open jobs from company career sites on 12 ATSs (Workday, Greenhouse, Lever, Ashby, Workable, Personio & more) in one schema. Paste careers URLs or domains, or search thousands of companies at once by title and country. Get only new jobs; pay per matching job.

- **URL**: https://apify.com/folt/ats-jobs-scraper.md
- **Developed by:** [Folt Labs](https://apify.com/folt) (community)
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 jobs

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

## Career Site Jobs Scraper — Workday, Greenhouse, Lever, Ashby & 8 more ATSs

Get **every open job from company career sites** in one clean, consistent format — straight from the applicant tracking system (ATS) the company uses. Paste careers page URLs or just company domains; the ATS is detected automatically. Track **only new or removed jobs** between runs, and **pay for jobs that match your filters** (plus $0.05 per 1,000 companies checked). No company list? **Search all companies** in our directory instead.

### What you get

- ✅ **12 ATSs in one schema:** Workday, Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee, Teamtailor, Personio, Breezy HR, Pinpoint, BambooHR
- 🌍 **Search all companies:** no list? Search the live job boards of 11,200+ companies by title, country or location in one run
- 🔎 **Auto-detection:** give it `stripe.com`, a careers page URL or a job board URL — it finds the board (a guessed board is only accepted when the company name matches, so you never get another company's jobs)
- 🔔 **Change detection:** return only jobs that are **new** since your last run, or **new + removed** — for alerts, CRMs and hiring-signal tracking
- 🎯 **Filters before you pay:** title keywords (include/exclude), location, country, remote/hybrid/on-site, department, posted within N days
- 💰 **Salary data** where the ATS publishes it (min, max, currency, interval)
- 🧹 **Clean descriptions** as plain text (LLM-ready), HTML, or none
- 🛡️ **Privacy by default:** recruiter names and contact fields are never included; emails and phone numbers inside descriptions are redacted
- ⚡ **Fast and cheap:** uses the ATSs' public job feeds — no browser, no proxies, low memory

### Supported ATSs

| ATS | Add a company with | Notes |
| --- | --- | --- |
| **Workday** | careers URL, e.g. `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite` | Exact locations, countries and dates are read from each job page, so large boards take a few minutes. Up to 2,000 jobs per careers site (Workday's own limit). Workday doesn't publish departments. |
| **Greenhouse** | `greenhouse:gitlab` or `https://job-boards.greenhouse.io/gitlab` | EU-hosted boards included |
| **Lever** | `lever:spotify` or `https://jobs.lever.co/spotify` | EU-hosted boards included |
| **Ashby** | `ashby:openai` or `https://jobs.ashbyhq.com/openai` | Structured salary when published |
| **SmartRecruiters** | `smartrecruiters:Wix2` | |
| **Workable** | `workable:huggingface` or the company domain | Includes careers pages that embed the Workable widget |
| **Recruitee** | `recruitee:tbibank` or the company domain | Includes custom career domains |
| **Teamtailor** | `teamtailor:polestar` or the career site URL | Includes custom career domains (e.g. `career.avenga.com`) |
| **Personio** | `personio:fadata-group` or the company domain | |
| **Breezy HR** | `breezy:navaide` or `https://navaide.breezy.hr` | Includes careers pages that link to the board or embed its widget. Pay is published as text (`salary.summary`) |
| **Pinpoint** | `pinpoint:carto` or `https://carto.pinpointhq.com` | Pay range when the company shows it |
| **BambooHR** | `bamboohr:prezi` or `https://prezi.bamboohr.com/careers` | Includes careers pages that embed the BambooHR widget (e.g. `prezi.com/jobs`). Dates, full addresses and pay text are read from each job's page |

Or just paste the company's domain (`payhawk.com`, `dronamics.com`, `navaide.com`) — the Actor scans the careers page for a link to one of these ATSs and, for all but Workday, Breezy HR, Pinpoint and BambooHR, also tries the board named after the domain.

### Search all companies — no company list needed

Don't have a list? Turn on **Search all companies** and the Actor searches the open jobs of **every company in the Folt Labs company directory: 11,200+ companies with 480,000+ open jobs** on Greenhouse, Ashby, SmartRecruiters, Teamtailor, Personio, Lever and Recruitee (directory built October 2026 and refreshed regularly).

- Add at least one filter — job title keywords, countries, locations or departments — e.g. *"data engineer" in Germany*.
- Every company is checked **live** at run time, so you get today's openings, not a stale copy.
- With a country filter, only companies that had jobs in those countries are scanned, which keeps runs fast and cheap.
- Combine it with **Change detection → Only new jobs** and a daily schedule for a market-wide feed of new openings.
- Pick platforms with **Directory: job platforms**, or cap the scan with **Directory: max companies to scan** (largest companies first).
- Speed: about 3,000 companies in 6 minutes; the whole directory in roughly 20 minutes. Runs need at least 1 GB of memory (the default).

```json
{
  "searchAllCompanies": true,
  "titleKeywords": ["data engineer", "analytics engineer"],
  "countryCodes": ["DE", "NL"],
  "postedWithinDays": 14,
  "changeDetection": "new",
  "descriptionFormat": "text"
}
```

The directory comes from public web-archive data (Common Crawl): job-board addresses of companies on these platforms, each checked against the platform's public job feed. Companies on Workday and other systems aren't in it — add those with a company list (both work in the same run).

### How to use

1. Add companies, one per line (any form from the table above).
2. Optionally add filters — jobs are only charged when they pass them.
3. For monitoring, set **Change detection** to *Only new jobs* and **schedule** the Actor (e.g. daily). Runs with nothing new cost almost nothing.

#### Input example

```json
{
  "companies": ["gitlab.com", "lever:spotify", "https://adobe.wd5.myworkdayjobs.com/external_experienced"],
  "titleKeywords": ["engineer", "developer"],
  "excludeTitleKeywords": ["intern"],
  "countryCodes": ["DE", "GB"],
  "workplaceTypes": ["remote", "hybrid"],
  "changeDetection": "new",
  "descriptionFormat": "text"
}
```

### Ready-made recipes

Each recipe is an input you save as a **Task**, plus a **Schedule** and an **Integration** in Apify Console (no code).

| Goal | Input highlights | Schedule | Send results to |
| --- | --- | --- | --- |
| **Market-wide job feed** — every new data-engineer opening in Germany, no list needed | `searchAllCompanies: true`, `titleKeywords: ["data engineer"]`, `countryCodes: ["DE"]`, `changeDetection: "new"` | daily | Slack / Google Sheets / webhook |
| **Hiring signals for sales** — know when target accounts start hiring for a role | your prospect list, `titleKeywords: ["data engineer"]`, `changeDetection: "new"`, `descriptionFormat: "none"` | daily | Slack / email / HubSpot (Integrations tab or webhook) |
| **Niche job board** — fresh postings from hundreds of companies | your company list, `countryCodes`, `descriptionFormat: "html"`, `changeDetection: "new_and_removed"` | every 6–24 h | your site via webhook, Make, Zapier or n8n |
| **Competitor hiring tracker** | 10–50 competitors, `changeDetection: "new_and_removed"` | weekly | Google Sheets |
| **Local job feed** — e.g. every tech job in Bulgaria | companies with offices there, `countryCodes: ["BG"]` | daily | Slack / email |

### Output example

```json
{
  "id": "ashby:openai:8fb1615c-34bf-47c4-a1d1-b7b2f836bbd3",
  "ats": "ashby",
  "companyName": "OpenAI",
  "title": "Technical Program Manager, Compute Infrastructure",
  "department": "Technical Program Management",
  "employmentType": "full_time",
  "workplaceType": null,
  "location": "San Francisco",
  "locations": ["San Francisco"],
  "countryCodes": ["US"],
  "salary": { "min": 257000, "max": 335000, "currency": "USD", "interval": "year", "summary": "$257K - $335K" },
  "postedAt": "2026-03-12T16:38:15.322Z",
  "url": "https://jobs.ashbyhq.com/openai/8fb1615c-34bf-47c4-a1d1-b7b2f836bbd3",
  "applyUrl": "https://jobs.ashbyhq.com/openai/8fb1615c-34bf-47c4-a1d1-b7b2f836bbd3/application",
  "description": "About the Team\n\nThe compute infrastructure team runs the GPU fleet...",
  "changeStatus": "new",
  "firstSeenAt": "2026-10-06T09:00:00.000Z"
}
```

| Field | Notes |
| --- | --- |
| `id` | Stable across runs: `ats:board:jobId` |
| `employmentType` | `full_time`, `part_time`, `contract`, `internship`, `temporary`, `other` (raw value in `employmentTypeRaw`) |
| `workplaceType` | `remote`, `hybrid`, `onsite`, or `null` when the ATS doesn't say |
| `countryCodes` | ISO 3166-1 alpha-2; also inferred from city-only locations such as "Sofia" or "London" |
| `salary` | `null` unless the company publishes pay on its ATS (Ashby compensation, Lever salary range, Recruitee salary, Pinpoint pay range). Breezy HR and BambooHR publish pay as free text, kept in `summary` with `min`/`max`/`currency` left `null`. Many companies only mention pay inside the description text, which is kept as-is |
| `changeStatus` / `firstSeenAt` | Only when change detection is on. Removed jobs come back with `changeStatus: "removed"` |

A **SUMMARY** record in the run's key-value store lists every input with the detected ATS, how it was detected, and how many jobs were returned — so you can see at a glance which companies couldn't be resolved.

### Pricing

| What | Price |
| --- | --- |
| Each job returned | **$0.0015** ($1.50 per 1,000) |
| Each company checked (your list or the directory) | **$0.00005** ($0.05 per 1,000) |
| Each run | **$0.002** |
| Platform usage | included — you pay nothing else |

Filters and change detection are applied *before* charging jobs. Examples:

- a daily run over 50 companies that finds ~5 new jobs a day costs about **$0.35 a month**;
- a one-off export of 10,000 jobs costs **$15**;
- a "Search all companies" run for *data engineers in Germany* that checks ~1,000 companies and returns 150 jobs costs about **$0.28** ($0.05 for the companies + $0.23 for the jobs).

Cap any run with Apify's *maximum cost per run* — the Actor stops cleanly when it's reached.

### Use cases

- **Job boards & aggregators:** feed fresh postings from hundreds of companies into your site
- **Sales & hiring signals:** know when target accounts start hiring for a role
- **Recruiting & market research:** track openings, locations and salary bands across competitors
- **AI agents & RAG:** clean, plain-text job descriptions in one schema

### FAQ

**My company isn't detected.** Paste the job board URL from the company's careers page (e.g. `https://jobs.lever.co/acme`) or an explicit pair like `lever:acme`. For Workday, paste the careers site URL. Breezy HR, Pinpoint and BambooHR boards are found from a link or widget on the careers page, or from the board URL / `ats:slug` you paste. Companies on other systems (iCIMS, Taleo, SAP SuccessFactors, Oracle Recruiting Cloud, Rippling, their own custom site, or job portals only) aren't supported.

**Why are some fields null?** Each ATS publishes different data. For example, Greenhouse doesn't expose employment type, Workday and Teamtailor rarely expose departments or employment type, Breezy HR boards rarely set departments, Pinpoint has no country field (countries are inferred from the location text), and most companies don't publish salaries.

**Why is a Workday or BambooHR company slower?** Workday's job list only says "3 Locations" or "Posted 30+ Days Ago", and BambooHR's list has no dates and often no country, so the Actor opens each job's page for the exact locations, countries and date — that's what makes country and date filters correct. Title filters are applied first, and in monitoring mode jobs you already have are not opened again.

**How does change detection remember jobs?** In a named key-value store in your Apify account (`ats-jobs-scraper-state`). Runs with the same *Watchlist name* (or the same company list) compare against each other. The first run returns everything as `new`.

**Is this legal?** The Actor reads public job feeds that companies publish for job distribution. It does not log in, bypass protections, or collect applicant data. You're responsible for how you use the data.

### Support

Found a company that should work but doesn't? Open an issue on the Actor's **Issues** tab with the URL — we usually fix it within a few days.

# Changelog

This Actor's version history is a separate document: https://apify.com/folt/ats-jobs-scraper/changelog.md

# Actor input Schema

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

One company per line. Accepts a job board or careers URL (e.g. https://jobs.lever.co/spotify, https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite), a company domain (e.g. gitlab.com), or an explicit ats:slug pair (greenhouse:gitlab, lever:spotify, ashby:openai, smartrecruiters:Wix2, workable:huggingface, recruitee:tbibank, teamtailor:polestar, personio:fadata-group, breezy:navaide, pinpoint:carto, bamboohr:prezi). Workday companies need the careers site URL; Breezy HR, Pinpoint and BambooHR need the board URL, the ats:slug pair, or a careers page that links to the board.

## `searchAllCompanies` (type: `boolean`):

Also search the open jobs of every company in the Folt Labs company directory — thousands of companies hiring on Greenhouse, Ashby, SmartRecruiters, Teamtailor, Personio, Lever and Recruitee — without listing them yourself. Needs at least one filter below (job title, country, location or department). Every company is checked live at run time. Costs $0.05 per 1,000 companies checked plus $1.50 per 1,000 matching jobs. Needs at least 1 GB of run memory (the default).

## `directoryAts` (type: `array`):

Search only companies on these platforms. Leave empty for all.

## `maxDirectoryCompanies` (type: `integer`):

Largest companies are scanned first. With a country filter, only companies that had jobs in those countries (or mostly unlabelled/remote locations) are scanned. Leave empty to scan all that match.

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

Plain text is best for AI/LLM pipelines and spreadsheets. Choose 'none' for the fastest, smallest output.

## `changeDetection` (type: `string`):

Return all jobs, only jobs that are new since your previous run, or new jobs plus jobs that disappeared. Jobs are charged only when returned, so monitoring runs with nothing new cost almost nothing (the run fee plus $0.05 per 1,000 companies checked).

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

Names the memory used by change detection. Runs with the same name compare against each other. Leave empty to derive it from the company list.

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

Keep jobs whose title contains at least one of these words or phrases (case-insensitive).

## `excludeTitleKeywords` (type: `array`):

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

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

Keep jobs whose location text contains at least one of these (e.g. Berlin, London, Remote).

## `countryCodes` (type: `array`):

Keep jobs in these countries (ISO 3166-1 alpha-2, e.g. DE, GB, US). Jobs whose ATS does not report a country are kept only if their location text matches the country name.

## `workplaceTypes` (type: `array`):

Keep only these workplace types. Leave empty for all.

## `departmentKeywords` (type: `array`):

Keep jobs whose department or team contains one of these (e.g. Engineering, Sales).

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

Keep jobs first published within this many days. Jobs without a publish date are kept.

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

Stop after this many matching jobs per company. 0 means no limit.

## `redactContactDetails` (type: `boolean`):

Replaces email addresses and phone numbers found in job descriptions with \[redacted]. Recruiter names and contact fields from the ATS are never included.

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

How many companies to process in parallel. Default: 5 for your company list, 10 when searching the directory.

## Actor input object example

```json
{
  "companies": [
    "https://job-boards.greenhouse.io/gitlab",
    "lever:spotify"
  ],
  "searchAllCompanies": false,
  "descriptionFormat": "text",
  "changeDetection": "off",
  "maxJobsPerCompany": 20,
  "redactContactDetails": true
}
```

# Actor output Schema

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

Open jobs from the selected career sites (or only new/removed ones in change-detection mode).

## `allFields` (type: `string`):

The same records with every field, as JSON.

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

Per-input status, counts and anything that could not be processed.

# 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/gitlab",
        "lever:spotify"
    ],
    "maxJobsPerCompany": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("folt/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/gitlab",
        "lever:spotify",
    ],
    "maxJobsPerCompany": 20,
}

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

```

## MCP server setup

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