# ATS Jobs Scraper: Workday, Greenhouse, Lever & More (`zaiq/ats-jobs-scraper`) Actor

Read public jobs from Workday, Greenhouse, Lever, Ashby, SmartRecruiters, Workable and Recruitee in one schema. Get descriptions, salary evidence and application links. Compare complete snapshots for openings and closures.

- **URL**: https://apify.com/zaiq/ats-jobs-scraper.md
- **Developed by:** [Zaiq](https://apify.com/zaiq) (community)
- **Categories:** Jobs, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.25 / 1,000 open 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

## ATS Jobs Scraper: Workday, Greenhouse, Lever & More

Read public job boards on **Workday, Greenhouse, Lever, Ashby, SmartRecruiters, Workable and Recruitee**. Get job IDs, titles, locations, descriptions, published salary evidence and application links in one dataset.

**$0.00125 per current job delivered** ($1.25 per 1,000), including descriptions when requested. Failed jobs, empty boards and change notes have no job charge. Supply the public boards you want; this Actor does not discover employers.

Smallest input: `{"boards":[{"provider":"workday","board":"example-employer/wd5/PublicJobs"}],"maxResults":1,"maxChargeUsd":0.002}`. Replace the example board with a real public careers site. The input form includes a working prefill.

### What you get

| Field | What it holds |
|---|---|
| verdict | A plain sentence describing the job or why no data was returned. |
| provider, board, jobId, resultId | Source identity and a stable delivery ID. Duplicate inputs and migrated deliveries are not charged twice. |
| company, locations, remoteType, employmentType | Published metadata only. Company is null where the feed does not name it. Workplace is Unknown unless a source workplace/location label establishes it. |
| tenant, site, shard, requisitionId, locationSummary, country, workplaceLabel, postedOn, detailStatus | Additional source fields where available. Workday keeps the original workplace label and relative posting text; it does not turn ambiguous labels into a remote-work claim. |
| descriptionText | Published description with HTML formatting removed. Contact blocks, email addresses and phone numbers are removed. |
| salaryText, salaryMin, salaryMax, salaryCurrency, salaryPeriod, salarySource | Source evidence. Missing values remain null. Multiple pay ranges stay in the text; a bare dollar sign does not prove USD. |
| publishedAt, updatedAt, jobUrl, applyUrl, sourceUrl, observedAt | Published dates where available, job/application links and observation time. Links are returned, never submitted. |
| changeType, snapshotComplete | Optional baseline/opened/updated/unchanged/reopened labels and free closure notes, only for complete comparable runs. |
| SUMMARY | Delivered and charged counts, free notes, duplicates, skipped boards, source failures, coverage and limits. |

### What people use it for

- **Job-board feeds:** Refresh vacancies for a known list of employers.
- **Recruiting-data workflows:** Normalize seven public ATS feeds without collecting people or applicant data.
- **Vacancy monitoring:** Compare sequential complete snapshots with the same namespace and filter.

### How to use it

1. Add board objects with provider and board, or supported board URLs. Replace the placeholder in the examples with your chosen public board.
2. Choose a total result cap and spending limit. The lower of maxChargeUsd and Apify Maximum cost per run applies.
3. Run the Actor and open Jobs and notes. Check SUMMARY for coverage before using the data as a complete feed.

### Input

| Field | Example | Notes |
|---|---|---|
| boards | \[{"provider":"workday","board":"example-employer/wd5/PublicJobs"}] | Up to 20 boards. Workday accepts tenant/wdN/site or a myworkdayjobs careers-site URL, with or without a locale. Other providers accept their public board slug or supported URL. Provider values: workday, greenhouse, lever, ashby, smartrecruiters, workable, recruitee. Lever EU uses region: eu. |
| urls | \["https://careers.example.test/PublicJobs"] | Use a real supported careers-site URL. Workday locale paths are accepted. Individual jobs, employer homepages and login URLs are rejected. url and startUrls are aliases. |
| query | engineer | One case-insensitive literal filter over titles and fetched descriptions. Workday, SmartRecruiters and Workable filter titles only when descriptions are off. Selective filters can reach source limits. |
| maxResults | 10 | Global maximum, 1–10,000. A truncated result set cannot establish closures. |
| maxJobsPerBoard | 10000 | Optional per-board maximum. Use a small cap to sample every input board; a truncated board cannot establish closures. |
| includeDescriptions | true | Workday, SmartRecruiters and Workable fetch one detail per job when true. False skips those requests; detail-only fields and description-derived pay are unavailable. Other feeds include descriptions in the list response. |
| snapshotKey | example-monitor | Optional namespace in a private named key-value store in your account. Use the same value for sequential runs. |
| maxChargeUsd | 0.02 | Zero makes no source requests. Repeated jobs in a new run are paid current rows, including unchanged jobs. |

### Output

Each result is one row in the run's dataset (download it as JSON, CSV, Excel, XML or HTML, or read it through the API). An example row (values shortened):

```json
{
  "verdict": "Open vacancy: Mechanical Engineer.",
  "recordType": "job",
  "resultId": "job-example-id",
  "provider": "workday",
  "board": "example-employer/wd5/PublicJobs",
  "region": "global",
  "tenant": "example-employer",
  "site": "PublicJobs",
  "shard": "wd5",
  "jobId": "JR2027384",
  "requisitionId": "JR2027384",
  "title": "Mechanical Engineer",
  "company": "Example Employer",
  "locations": [
    "Israel, Yokneam"
  ],
  "locationSummary": "Israel, Yokneam",
  "country": "Israel",
  "remoteType": "Unknown",
  "workplaceLabel": null,
  "employmentType": "Full time",
  "descriptionText": null,
  "salaryText": null,
  "salaryMin": null,
  "salaryMax": null,
  "salaryCurrency": null,
  "salaryPeriod": null,
  "salarySource": null,
  "publishedAt": "2026-10-25T00:00:00",
  "updatedAt": null,
  "postedOn": "Posted Today",
  "jobUrl": "https://careers.example.test/jobs/example-job",
  "applyUrl": "https://careers.example.test/jobs/example-job",
  "detailSourceUrl": "https://careers.example.test/api/jobs/example-job",
  "detailStatus": "fetched",
  "changeType": null,
  "snapshotComplete": false,
  "status": "open",
  "sourceUrl": "https://careers.example.test/api/jobs",
  "observedAt": "2026-10-25T15:39:54Z"
}
```

This example is based on a real 20-job Workday run on 25 October 2026. Employer names, board/site identifiers and site addresses are replaced. The description is omitted from this shortened example; the run returned it. SUMMARY records delivered and charged jobs, free notes, failures, source totals, facet coverage and limits. The first output column is verdict.

### Pricing

- **$0.00125 per delivered current job** (Open job), with or without descriptions.
- The same price on every Apify plan. No board, salary or change-report fee. Failed jobs, input notes, source-failure notes and closures have no job charge.
- Apify adds its standard $0.00005 start charge per run at the default memory setting. It counts towards the buyer’s Maximum cost per run.
- A new run charges for current jobs again, including unchanged jobs. When Apify restarts the same run, jobs already in its dataset are skipped and are not charged again.

What typical jobs cost at the list price:

| Job | Cost |
|---|---|
| 1 job | $0.00125 |
| 20 jobs | $0.025 |
| 1,000 jobs | $1.25 |
| Failed or empty board | $0 in job charges |

The lower of maxChargeUsd and Apify Maximum cost per run applies. Apify’s $0.00005 start charge counts towards Maximum cost per run; allow it in addition to the jobs you want. Free notes remain available after the limit. Source work stops when there is no room for another paid job.

### Tips

- For complete comparisons, allow enough results and budget for every matching vacancy across every board.
- Reuse snapshotKey with unchanged boards, provider regions, filter and description setting. A changed scope creates a new baseline without reporting closures.
- Schedule each snapshot namespace sequentially. Concurrent runs with the same namespace are unsupported because the snapshot store is not a transactional lock.

### Use it through the API

**Python**

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("zaiq/ats-jobs-scraper").call(run_input={"boards": [{"provider": "workday", "board": "example-employer/wd5/PublicJobs"}], "maxResults": 20, "maxChargeUsd": 0.03, "includeDescriptions": true})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row)
```

**JavaScript**

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('zaiq/ats-jobs-scraper').call({"boards": [{"provider": "workday", "board": "example-employer/wd5/PublicJobs"}], "maxResults": 20, "maxChargeUsd": 0.03, "includeDescriptions": true});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

**HTTP (one call, results back)**

```bash
curl -X POST 'https://api.apify.com/v2/acts/zaiq~ats-jobs-scraper/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>' \
  -H 'Content-Type: application/json' \
  -d '{"boards": [{"provider": "workday", "board": "example-employer/wd5/PublicJobs"}], "maxResults": 20, "maxChargeUsd": 0.03, "includeDescriptions": true}'
```

### Integrations and schedules

- **Schedules:** run it every hour, day or week from the Apify Console (Schedules) and keep a history of results.
- **Make, Zapier, n8n, Google Sheets, Slack, Airtable:** connect it through Apify's integrations, or send each finished run to a **webhook**.
- **Datasets:** results stay in the run's dataset; export them or read them by API whenever you need them.

### Use it with AI agents (MCP)

Add Apify's MCP server (`https://mcp.apify.com`) to Claude, ChatGPT, Cursor, VS Code or any MCP client. The agent can find this Actor and run it for you: ask it for "read these public ATS boards and return their current vacancies". AI agents can also pay for runs directly through Apify's agentic payments.

### Limits

- Reads signed-out public postings only. No applications, applicant records, account access, private jobs or employer discovery. Supply supported ATS-hosted board URLs; custom employer domains are not discovered automatically.
- Workday sites can clip a result set at exactly 2,000. Larger scans split using the site’s own returned facets, including nested facets where necessary. A run that encounters the cap remains incomplete for snapshot purposes even if all returned facet buckets are read. Missing or changing facets, repeated pages and source limits cannot prove a closure.
- Up to 20 boards, 10,000 delivered jobs, and 10,000 jobs per board. Responses are limited to 24 MB of decoded JSON. Original Greenhouse, Lever and Ashby feeds retain a 24 MB per-board limit, 60 seconds per board and 240 seconds of source time. The newer providers allow up to 720 seconds of source time and 20,000 requests per run; a Workday board allows up to 600 seconds. Requests time out after 30 seconds. Large runs may need more than one run; SUMMARY states what was covered.
- Workday, SmartRecruiters, Workable and Recruitee try direct access, then an Apify datacenter address and up to two fresh residential sessions for address refusals. Original providers retain direct/datacenter routing. No challenge is solved and no login or redirect is followed. HTTP 429 stops that host for the rest of the run.
- For residential fallback, Workday needs room for at least 3 jobs from that board and Recruitee needs room for at least 10. Query-filtered runs use direct/datacenter access only. If those routes are refused, a smaller or filtered request returns a free note; raise the job and spending caps or remove the filter to allow the fallback.
- A requested detail that fails is a free failure note, not a charged metadata substitute. Repeated detail failures stop that board. With descriptions off, published dates, employment type, company and salary can be null when available only on detail pages.
- snapshotComplete means every requested board was read successfully and all matching jobs fit the limits. Capped lists, failed details, repeated pages, changed totals and run limits keep the previous baseline and produce no closure notes. A changed input scope starts a new baseline.
- Source responses are reused within a restarted run, so a restart continues that run’s observed data. A fresh run reads current postings again. Stable Workday IDs combine provider, tenant, site and posting ID; moving shards or changing the title slug does not change the delivery ID.
- Company and employment fields can be null where the source omits them. Remote work is not inferred from benefits text. Ambiguous workplace labels are preserved separately. Salary figures are not guessed, converted or annualised.
- Descriptions have HTML and recognizable contact blocks, emails and phone numbers removed. Complex salary ranges remain source text. Snapshot data stays in a private named store in your account; schedule each namespace sequentially. The most recent 50,000 closed IDs support reopen recognition.

### FAQ

**Can I search for every employer hiring a role?**

No. Supply public boards or their supported URLs. query filters those boards only.

**Does a missing job always mean closed?**

No. A closure note requires two complete successful snapshots with the same scope. Partial or failed runs keep the baseline.

**Do unchanged jobs cost money on the next scheduled run?**

Yes. Every new run delivers current jobs at the per-job price. Change notes have no additional charge.

**Why is company or salary null?**

The feed did not supply enough explicit evidence. Board slugs are not presented as verified company names, and missing pay is not inferred.

**Does it apply for jobs?**

No. It returns published links and never sends an application or reads applicant data.

**Why does Workday show 2,000 jobs?**

Some sites cap their reported list. This Actor reads available facet partitions for larger scans and explicitly marks the run incomplete for closure comparisons. It never equates a capped total with the complete board.

**Can I paste a localized careers URL?**

Yes. Workday careers-site URLs with a locale path are accepted. The API may return its default language; translated output is not requested or guaranteed.

**Why can an employer have several IDs for a requisition?**

A requisition can have several public postings. Each source posting is kept separately; requisitionId lets you group them without discarding locations or posting variants.

### Related Actors from Zaiq

- [workday-jobs-scraper](https://apify.com/zaiq/workday-jobs-scraper): Workday defaults and a lower metadata-only price than its description mode

### Support

Questions, a site that changed, or a field you need? Open an issue on this Actor's **Issues** tab: we answer there, usually within a day.

***

Also found as: ATS jobs scraper, workday jobs, workday scraper, myworkdayjobs, greenhouse jobs scraper, lever jobs scraper, ashby jobs scraper, smartrecruiters jobs, workable jobs, recruitee jobs, job board API, job opening tracker.

# Actor input Schema

## `boards` (type: `array`):

Up to 20 public boards. provider: workday, greenhouse, lever, ashby, smartrecruiters, workable or recruitee. Workday board: tenant/wdN/site or a careers-site URL with optional locale. Other boards: public slug or supported URL; Lever EU uses region: eu.

## `urls` (type: `array`):

Alternative public board URLs. Employer homepages and individual vacancy URLs are not supported.

## `query` (type: `string`):

One literal case-insensitive filter on title and fetched description. With includeDescriptions false, Workday, SmartRecruiters and Workable filter titles only. Selective filters may reach source limits before finding results.

## `maxResults` (type: `integer`):

Total current jobs per run, across all boards, including rows already delivered before a restart. A cap can prevent closure comparison.

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

Include the published description after removing contact blocks, emails and phone numbers. Workday, SmartRecruiters and Workable skip detail requests when false; description-derived salary and detail-only fields are then unavailable. Other feeds may include salary with metadata.

## `snapshotKey` (type: `string`):

Optional private namespace saved in a named key-value store in your account. Reuse for sequential comparisons. Only complete comparable runs update it. Different inputs reset the baseline; never run the same namespace concurrently.

## `maxChargeUsd` (type: `number`):

Stop at the lower of this limit and Apify Maximum cost per run. Zero performs no source requests. Current rows in each new run are charged, including unchanged jobs.

## `url` (type: `string`):

Alternative to urls; one supported public board URL.

## `startUrls` (type: `array`):

Alternative to urls. URL strings or objects containing url are accepted.

## `queries` (type: `array`):

Alternative to query. At most one nonempty literal filter is supported; conflicting filters produce a free input note.

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

Alternative to query. At most one nonempty literal filter is supported; conflicting filters produce a free input note.

## `maxJobsPerBoard` (type: `integer`):

Optional per-board cap, 1–10,000. Use a small value to sample every supplied board. Truncated boards cannot establish closures.

## Actor input object example

```json
{
  "boards": [
    {
      "provider": "greenhouse",
      "board": "figma"
    }
  ],
  "urls": [],
  "query": "",
  "maxResults": 10,
  "includeDescriptions": true,
  "snapshotKey": "",
  "maxChargeUsd": 0.02,
  "maxJobsPerBoard": 10000
}
```

# Actor output Schema

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

Dataset: the results.

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

What each input returned, and notes.

# 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 = {
    "boards": [
        {
            "provider": "greenhouse",
            "board": "figma"
        }
    ],
    "maxResults": 10,
    "includeDescriptions": true,
    "maxChargeUsd": 0.02
};

// Run the Actor and wait for it to finish
const run = await client.actor("zaiq/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 = {
    "boards": [{
            "provider": "greenhouse",
            "board": "figma",
        }],
    "maxResults": 10,
    "includeDescriptions": True,
    "maxChargeUsd": 0.02,
}

# Run the Actor and wait for it to finish
run = client.actor("zaiq/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 '{
  "boards": [
    {
      "provider": "greenhouse",
      "board": "figma"
    }
  ],
  "maxResults": 10,
  "includeDescriptions": true,
  "maxChargeUsd": 0.02
}' |
apify call zaiq/ats-jobs-scraper --silent --output-dataset

```

## MCP server setup

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