# Careers Page Scraper: Workday, Greenhouse, Lever & Ashby Jobs (`dtkfoundry/ats-jobs`) Actor

Careers page scraper for any company's Workday, Greenhouse, Lever or Ashby career site: every open job in one format with title, locations, remote/hybrid, department, posted date, salary where published, full description and apply link. Pay per job.

- **URL**: https://apify.com/dtkfoundry/ats-jobs.md
- **Developed by:** [DTK Foundry](https://apify.com/dtkfoundry) (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 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

## Careers Page Scraper: Workday, Greenhouse, Lever & Ashby Jobs

A careers page scraper: every open job from any company's careers page on Workday, Greenhouse, Lever or Ashby, in one format: title,
all locations, remote / hybrid / onsite, department and team, posted date, salary where the company publishes it,
the full description as text and HTML, and the apply link. Paste career-site URLs from any mix of the four
systems; filter by keyword, location and posting date.

- **Four systems, one format.** Most tech companies, banks, hospitals, universities and governments post their jobs
  on one of these four. The same fields come back whichever one a company uses, so a list of employers becomes one
  clean table.
- **Reliable.** Tested on 200 career-site runs (47 Workday sites, 34 Greenhouse, 8 Lever, 16 Ashby; with and
  without filters): 198 answered on the first build, 6,899 jobs, all but 3 with full descriptions. The two misses
  were one huge Lever board that timed out; it is now read page by page and passed 16 of 16 since.
- **Workday done right.** Workday career pages load jobs 20 at a time and show the description only on each job's
  page. This actor pages through Workday's own search and fetches each full posting, with all its locations.
- **Salary where published.** Structured salary (min, max, currency, period) from Ashby and Lever boards that
  publish it: 457 of 4,860 jobs in the unfiltered test run.
- **Pay per job.** $0.001 per job. Sites that fail, or have no matching jobs, are never charged.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `careerSites` | array | `[]` | One career site per line (see below). Empty = a free format example. |
| `search` | string | | Keywords such as `data engineer`. Workday: Workday's own search (titles and descriptions). Greenhouse, Lever, Ashby: jobs whose title, department or team contains every word. |
| `location` | string | | Keeps jobs whose location, country or workplace type contains every word (`London`, `Germany`, `remote`). |
| `postedWithinDays` | integer | `0` | Only jobs posted in the last N days. 0 = any time. |
| `maxJobsPerSite` | integer | `200` | Up to 10,000. Each job is one charged result. |
| `includeDescription` | boolean | `true` | Full description as text and HTML. Off = faster on Workday; same price. |

Career sites can be given as:

| System | Example URL | Short form |
|---|---|---|
| Workday | `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite` (any page under it works, with or without `/en-US/`) | |
| Greenhouse | `https://boards.greenhouse.io/airbnb` or `https://job-boards.greenhouse.io/airbnb` | `greenhouse:airbnb` |
| Lever | `https://jobs.lever.co/spotify` (EU boards: `jobs.eu.lever.co`) | `lever:spotify` |
| Ashby | `https://jobs.ashbyhq.com/openai` | `ashby:openai` |

```json
{
    "careerSites": [
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
        "greenhouse:airbnb",
        "https://jobs.ashbyhq.com/openai"
    ],
    "search": "engineer",
    "location": "remote",
    "postedWithinDays": 14
}
```

### Output (one record per job)

```json
{
    "status": "ok",
    "source": "ashby",
    "company": "openai",
    "careerSite": "ashby:openai",
    "jobId": "f2d8ed04-3c2f-4298-832b-dda7e1644c1b",
    "requisitionId": null,
    "title": "Enterprise Security Engineer, Vulnerability Management",
    "url": "https://jobs.ashbyhq.com/openai/f2d8ed04-3c2f-4298-832b-dda7e1644c1b",
    "applyUrl": "https://jobs.ashbyhq.com/openai/f2d8ed04-3c2f-4298-832b-dda7e1644c1b/application",
    "location": "San Francisco",
    "locations": ["San Francisco", "US - Remote"],
    "country": "United States",
    "workplaceType": "hybrid",
    "employmentType": "FullTime",
    "department": "Security",
    "team": "Security",
    "postedAt": "2026-10-15",
    "postedText": null,
    "salary": {"min": 347000, "max": 445000, "currency": "USD", "interval": "1 YEAR",
               "text": "$347K – $445K • Offers Equity"},
    "description": "About the Team\n\nEnterprise Security protects the systems, identities, devices ...",
    "descriptionHtml": "<p>...</p>",
    "scrapedAt": "2026-10-22T17:43:08+00:00"
}
```

- `source` is the system (`workday`, `greenhouse`, `lever`, `ashby`). `company` is the company name Greenhouse
  gives, otherwise the board's name in the URL (`nvidia`, `spotify`).
- `locations` lists every location of the job; `location` is the main one.
- `workplaceType` is `remote`, `hybrid` or `onsite` when the board says so (or the location says "Remote"),
  otherwise `null`.
- `postedAt` is the date the job was posted (Greenhouse: first published; Lever: created; Workday: posting start
  date). Workday also gives its own `postedText` ("Posted 3 Days Ago").
- `salary` is filled where the board publishes a structured range (Ashby, Lever); otherwise `null`. Salary text
  inside the description stays in the description.
- A board with no matching jobs gives one record with `status: "no-results"` and `jobsOnBoard`; a board that could
  not be loaded (wrong URL, board removed) gives `status: "failed"` and an `error`. Neither is charged.

### Pricing

Pay per event, no subscription.

| Event | Price | When it is charged |
|---|---|---|
| Job | **$0.001** | Once per job delivered. 200 jobs from one career site = $0.20. |

A Workday job whose full posting could not be loaded after retries is still delivered from Workday's job list
(title, location, link) with `descriptionError`, and not charged. Set a maximum charge in the run options and the
actor stops before it would go over. Apify's standard run-start event adds $0.00005 per run.

### Limits, stated plainly

- Only the four systems above. Company career pages on other systems (iCIMS, SuccessFactors, Taleo, SmartRecruiters,
  custom sites) are not read.
- Only public job boards: internal boards and jobs a company does not list publicly are not visible.
- Workday's search is Workday's own and also matches descriptions; on the other three, `search` matches titles,
  departments and teams only.
- Workday lists at most 10,000 jobs per search here; very large employers need a search or filter to reach the rest.
- Public data only: no login, no candidate data.

### Privacy

Only the career sites you give are requested. Results are stored only in your run's dataset. The actor runs with
limited permissions and reads nothing else in your account.

# Actor input Schema

## `careerSites` (type: `array`):

One per line: a company's career site URL on Workday (https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite), Greenhouse (https://boards.greenhouse.io/airbnb), Lever (https://jobs.lever.co/spotify) or Ashby (https://jobs.ashbyhq.com/openai). Short forms work too: greenhouse:airbnb, lever:spotify, ashby:openai. Leave empty for a free format example.

## `search` (type: `string`):

Keywords such as "data engineer". Workday sites use Workday's own search (titles and descriptions); Greenhouse, Lever and Ashby keep jobs whose title, department or team contains every word.

## `location` (type: `string`):

Keeps jobs whose location, country or workplace type contains every word: "London", "Germany", "remote".

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

Only jobs posted in the last N days. 0 = any time.

## `maxJobsPerSite` (type: `integer`):

Each job is one charged result. Large employers list thousands of jobs; use search and filters to narrow them.

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

The job description as plain text and HTML. Turning it off makes Workday sites faster (one call per 20 jobs instead of one per job); the price is the same.

## Actor input object example

```json
{
  "careerSites": [
    "https://jobs.lever.co/spotify",
    "https://boards.greenhouse.io/airbnb"
  ],
  "postedWithinDays": 0,
  "maxJobsPerSite": 200,
  "includeDescription": true
}
```

# Actor output Schema

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

Every job with title, locations, workplace type, department, posted date, salary and description.

## `resultsCsv` (type: `string`):

The same jobs as CSV.

# 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 = {
    "careerSites": [
        "https://jobs.lever.co/spotify",
        "https://boards.greenhouse.io/airbnb"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("dtkfoundry/ats-jobs").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 = { "careerSites": [
        "https://jobs.lever.co/spotify",
        "https://boards.greenhouse.io/airbnb",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("dtkfoundry/ats-jobs").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 '{
  "careerSites": [
    "https://jobs.lever.co/spotify",
    "https://boards.greenhouse.io/airbnb"
  ]
}' |
apify call dtkfoundry/ats-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,dtkfoundry/ats-jobs"
        }
    }
}
```

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/l6DWBjlFm6IrGobDr/builds/3WlilfN2BMsyLVCpz/openapi.json
