# Workday Jobs Scraper - 2,600+ Employer Career Sites (`starbright_overlap/workday-jobs-scraper`) Actor

Scrape live job postings from any Workday career site, or search 2,600+ known Workday employers in one run. Paste the careers URL - titles, locations, departments, apply links.

- **URL**: https://apify.com/starbright\_overlap/workday-jobs-scraper.md
- **Developed by:** [Sulle H](https://apify.com/starbright_overlap) (community)
- **Categories:** Jobs, 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 results

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Workday Jobs Scraper — 2,658 Employer Sites, 595,820 Open Roles

Workday runs the careers site of a large share of the Fortune 500, and every one of those sites
answers the same JSON endpoint. This Actor reads it — for a company you name, or across a bundled
registry of **2,658 live Workday career sites** carrying **595,820 open postings**.

```json
{ "keywords": ["nurse"], "locations": ["texas"], "maxJobs": 200 }
```

Or point it at specific employers. Paste the careers URL; the slug format is worked out for you:

```json
{ "companies": ["https://cvshealth.wd1.myworkdayjobs.com/cvs_health_careers"] }
```

No login, no cookies, no proxies, no API key.

### Why Workday is harder than it looks

Three things break naive Workday scrapers, and this Actor handles all three.

**It is a POST API, not a GET.** The endpoint wants a JSON body with an offset, applied facets and a
search string. A crawler that only issues GETs sees nothing at all.

**It returns 20 rows at a time.** Not 100, not "all" — 20, whatever you ask for. Any cap expressed
in pages rather than postings silently truncates Workday five times harder than the other platforms.

**Its `total` field lies above 2,000.** Every large employer reports exactly 2000, so ranking boards
by that number puts a 93,000-posting employer level with a 2,000-posting one. The response's own
facet counts carry the real figure, and that is what the registry stores — which is why Accenture
sits at the top of this registry with 93,353 postings rather than tied at the ceiling.

### Board addresses, and how to get one

A Workday board is identified by three parts, not one: the data-centre cluster, the tenant, and the
site name. They are all in the careers URL:

```
https://cvshealth.wd1.myworkdayjobs.com/cvs_health_careers
        └ tenant ┘ └cluster┘                └ site name ┘
```

Pass the whole URL and the Actor extracts `wd1:cvshealth:cvs_health_careers` itself. A locale
segment such as `/en-US/` in the middle is handled too. If you already have the three-part key, pass
that instead.

Or skip the question entirely — run without `companies` and read the `companySlug` column.

### What you get

| Field | Description |
|---|---|
| `provider` | Always `workday` — the same schema the multi-ATS Actors below emit |
| `company` / `companySlug` | Workday tenant name, and the full three-part board key |
| `jobId` | Workday requisition id — stable, so it works as a dedupe key across runs |
| `title` | Job title |
| `location` | Location as published |
| `employmentType` | Full time or part time, as the employer classified it |
| `postedAt` | ISO date, derived from Workday's posting age — see below |
| `applyUrl` | Direct link to the posting on the employer's own site |
| `scrapedAt` | When this row was read |

### The date field deserves an explanation

Workday does not publish a posting date. It publishes prose: `Posted Today`, `Posted 5 Days Ago`,
`Posted 30+ Days Ago`. This Actor converts the ones that carry a definite offset into real ISO
timestamps.

`30+ Days Ago` is genuinely unknown — it could be 31 days or three years — so `postedAt` stays
`null` rather than being invented. A wrong date is worse than a missing one, particularly if you are
filtering on freshness.

Because of that, **`postedWithinDays` keeps rows with no date rather than dropping them.** Otherwise
a freshness filter would throw away most of Workday.

### What Workday does not publish

`descriptionText` and `department` are `null` on every row. Both live behind a separate
per-posting detail request — one HTTP call per job — which the listing endpoint does not expose and
a per-result price cannot carry. Salary is not a structured field at all.

If body text is the requirement, Greenhouse, Workable, Ashby and Breezy do publish it — the
[Career Site Job Feed](https://apify.com/starbright_overlap/ats-job-feed) covers those in this same
output schema.

### Input

| Option | What it does |
|---|---|
| `keywords` | Keep only titles containing one of these. Case-insensitive. |
| `excludeKeywords` | Drop titles containing any of these, e.g. `senior`, `intern`. |
| `locations` | Keep only locations containing one of these. |
| `remoteOnly` | Keep only postings that look remote. |
| `postedWithinDays` | Freshness filter, 90 days by default. Rows with no date are kept. |
| `companies` | Careers URLs or three-part keys. Leave empty to search all 2,658. |
| `maxBoards` | How many boards to scan, largest-first. |
| `maxJobs` | Hard cap on rows, so a run costs what you expect. |
| `maxJobsPerCompany` | Stops one huge employer filling the entire run. |

That last one matters more here than anywhere else: the largest board in this registry has 93,353
postings, which is more than the whole Breezy and Ashby registries combined. Without a per-company
cap it would fill any run by itself.

### Recipes

**Enterprise hiring, one query.** Workday skews heavily corporate — healthcare systems, retail
chains, banks, manufacturers. Leave `companies` empty, set `keywords`, and you have a slice of
large-employer hiring that job aggregators charge for.

**Track a competitor's headcount.** One employer, run on a schedule, diff the `jobId` set between
runs. Departments and locations show where the money is going before any announcement does.

**Regional labour-market data.** `locations` plus a wide `maxBoards` gives you what is open in a
metro area across hundreds of large employers.

### Use it as an API

Most people who rely on this Actor never open the Apify console after the first run — they call it
from their own code and read the rows straight back. One request in, job rows out, no polling and no
dataset id to chase:

```bash
curl -X POST "https://api.apify.com/v2/acts/starbright_overlap~workday-jobs-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keywords": ["nurse"], "locations": ["texas"], "maxJobs": 200}'
```

```python
import requests

rows = requests.post(
    "https://api.apify.com/v2/acts/starbright_overlap~workday-jobs-scraper/run-sync-get-dataset-items",
    params={"token": "YOUR_TOKEN"},
    json={"keywords": ["nurse"], "locations": ["texas"], "maxJobs": 200},
    timeout=300,
).json()

for r in rows:
    print(r["company"], "—", r["title"], "—", r["applyUrl"])
```

```javascript
const rows = await (await fetch(
  'https://api.apify.com/v2/acts/starbright_overlap~workday-jobs-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN',
  { method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({"keywords": ["nurse"], "locations": ["texas"], "maxJobs": 200}) },
)).json();
```

Your token is at **Settings → Integrations** in the Apify console. Keep `maxJobs` set to what you
actually need — it is the cap that decides what the call costs.

#### Running it on a schedule instead

If you want the rows to arrive without asking, add a **Schedule** from the Actor page (Actions →
Schedule) and point a webhook at your endpoint. A daily schedule plus the `postedWithinDays` filter
is the usual setup for a job board or an alerting pipeline.

### Pricing

Pay per result — you are charged per job row delivered. A board that fails or returns nothing costs
you nothing. Platform usage is included rather than billed on top, so the per-result price is the
whole price.

### Beyond Workday

Companies move between applicant tracking systems, and most job-data projects need more than one:

- **[Career Site Job Feed](https://apify.com/starbright_overlap/ats-job-feed)** — the same engine
  across Workday, Greenhouse, SmartRecruiters, Workable, Lever, Ashby and Breezy: 27,000+ employer
  boards in one run, identical output schema.
- **[New Job Alerts](https://apify.com/starbright_overlap/new-jobs-feed)** — the same coverage, but
  each run returns only what appeared since the previous run, so you are not diffing datasets
  yourself.

Rows from all of them share one schema, so you can union the datasets without a mapping layer.

### Notes

- **A dead board never aborts the run.** Failures are collected into a `FAILED_BOARDS` record in the
  key-value store, with the reason for each.
- **`companySlug:jobId` is a stable key.** It does not change while a requisition is open, which
  makes it safe for detecting what opened and closed between runs.
- **No login, no proxies, no API key.** Workday publishes this endpoint openly. Boards behind an
  employer login are invisible to every scraper, including this one.

# Actor input Schema

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

Keep only jobs whose title contains at least one of these. Case-insensitive. Leave empty for every job.

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

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

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

Keep only jobs whose location contains one of these, e.g. `london`, `new york`, `germany`.

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

Keep only postings flagged remote by the ATS or with a remote-looking location.

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

Freshness filter. Defaults to 90 days because some employers leave postings open for years — measured across the registry, 15% of Greenhouse and about half of the largest SmartRecruiters boards are over a year old. Set a large number such as 3650 to include everything. Postings with no publication date (most Workday rows) are always kept.

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

Board slugs, e.g. `wd103:accenture:accenturecareers`. Leave empty to search all 2,658 known workday employers.

## `maxBoards` (type: `integer`):

Boards are scanned largest-first, so a capped run still returns the most jobs. Raise it to sweep all 2,658 boards.

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

Hard cap on results, so a run costs what you expect. Defaults to 500 — enough to evaluate the feed on free-tier credits. Clearing the box falls back to that, so type a large number such as 500000 to sweep everything.

## `concurrency` (type: `integer`):

Higher is faster but more likely to hit ATS rate limits.

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

Keeps one huge employer from filling the whole run. Boards are scanned largest-first, so without this the first few boards use up the entire result budget. Raise it when you want depth on a few employers rather than breadth across many.

## Actor input object example

```json
{
  "keywords": [
    "engineer"
  ],
  "remoteOnly": false,
  "postedWithinDays": 90,
  "maxBoards": 200,
  "maxJobs": 500,
  "concurrency": 8,
  "maxJobsPerCompany": 25
}
```

# Actor output Schema

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

Every posting matching your filters, normalized across all six ATS platforms.

## `failedBoards` (type: `string`):

Written only when a board was unreachable or rate-limited. The run itself is unaffected.

# 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 = {
    "keywords": [
        "engineer"
    ],
    "postedWithinDays": 90,
    "maxJobs": 500,
    "maxJobsPerCompany": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("starbright_overlap/workday-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 = {
    "keywords": ["engineer"],
    "postedWithinDays": 90,
    "maxJobs": 500,
    "maxJobsPerCompany": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("starbright_overlap/workday-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 '{
  "keywords": [
    "engineer"
  ],
  "postedWithinDays": 90,
  "maxJobs": 500,
  "maxJobsPerCompany": 25
}' |
apify call starbright_overlap/workday-jobs-scraper --silent --output-dataset

```

## MCP server setup

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