# Workday Jobs Scraper & API - Whole Career Site (`automation_craft/workday-jobs-scraper`) Actor

Read every open role on any Workday career site, including the boards past Workday's 2,000 result limit where other scrapers quietly repeat page one. Each row carries the title, requisition ID, location, remote type, posting date and a working apply link. JSON, CSV, Excel or API.

- **URL**: https://apify.com/automation\_craft/workday-jobs-scraper.md
- **Developed by:** [Automation Craft](https://apify.com/automation_craft) (community)
- **Categories:** Jobs, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.80 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

### Workday Jobs Scraper & API - Whole Career Site

This **Workday jobs scraper** reads every open role on any `myworkdayjobs.com` career site through
Workday's own public jobs API. No login, no API key, no cookies and no browser. Paste one career site
URL or fifty, get a clean row per job with a working apply link, and pay once per job: a role a run has
already delivered is never billed again.

It is built around the one thing Workday does that quietly breaks other scrapers. Workday stops
paginating at 2,000 results and then serves page one again, with HTTP 200 and no error, so a large
career site comes back truncated and repeated. This Actor splits a big board by job category, country
and job type until every slice fits under that limit. On NVIDIA's career site, which reports
`total: 2000`, one run delivered **2,675 unique jobs**.

### Quick start

1. Open the company careers page and copy the address. It looks like
   `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`. The `/en-US/` form, a link to a
   single job, a `wd1.myworkdaysite.com/recruiting/...` link and even the bare tenant name `nvidia` all
   work.
2. Paste it into **Workday career site URLs**. Add more sites to the list to read them in one run.
3. Set **Maximum jobs in the run**. Leave **Fetch the full job description** off for a cheap list-only
   run, turn it on when you need the posting text.
4. Click **Start**. Results arrive as JSON, CSV or Excel, or through the Apify API.
5. To turn it into a monitor, put a name in **Dedup memory name** and schedule it. Every later run
   delivers and charges only the roles added since.

### What you get

One row per job. Fill rates below are measured on live runs: 2,675 NVIDIA jobs and 155 Red Hat jobs on
2026-09-16.

| Field | What it is | Filled |
|---|---|---|
| `title` | Job title as Workday publishes it | 100% |
| `company` | Workday tenant the career site belongs to | 100% |
| `tenant`, `site`, `host` | Where the row came from | 100% |
| `jobReqId` | The employer's own requisition number, for example `JR2025406` | 100% |
| `url` | Direct link to the posting, built from the path the API returned this run | 100% |
| `locationsText` | Location as the board shows it | 100% |
| `locations` | The location parsed out, empty when the board says "3 Locations" | 62% list only, 100% with descriptions |
| `multipleLocations`, `locationCount` | Set when a posting spans several offices | 100% |
| `postedText` | Workday's own wording, for example `Posted 4 Days Ago` | 100% |
| `postedAt` | Calendar date derived from that label | 43% list only, 100% with descriptions |
| `postedAgeDays`, `postedIsMinimum` | Age in days, and whether it is a lower bound | 100% |
| `remoteType` | Workday's remote label | 0 to 100%, the employer decides |
| `timeType` | Full time or part time | with descriptions only, 100% |
| `startDate` | Workday's exact posting date | with descriptions only, 100% |
| `country`, `countryCode` | Country of the requisition | with descriptions only, 100% |
| `descriptionHtml`, `descriptionText` | The full posting body | with descriptions only, 100% |
| `applyUrl`, `canApply` | Workday's own apply link and whether it still accepts applications | with descriptions only, 100% |
| `changeType`, `isKnown`, `firstSeenAt`, `lastSeenAt` | Memory verdict: NEW, UPDATED or DUPLICATE | 100% |
| `coverage` | `complete`, `capped` or `partial` for the career site this row came from | 100% |
| `bulletFields`, `timeLeftToApply`, `endDateText` | Extra labels a board may publish | varies |

Every run also pushes one `run-summary` row with per-site counters: how many jobs the board holds, how
many slices were needed, how many rows were delivered, how many were already known and free, the
requests used and the measured fill rates. A finished run is therefore never an empty dataset.

#### Honest notes on Workday's own data

- `remoteType` is optional for the employer. NVIDIA fills it on none of its 2,675 postings, Red Hat on
  all 155. We report what is there and never guess.
- Workday buckets everything older than 30 days as `Posted 30+ Days Ago`. We return `postedAt: null`
  with `postedAgeDays: 30` and `postedIsMinimum: true` rather than invent a date. Switch descriptions on
  to get the exact date from Workday.
- A posting that covers several offices shows as "3 Locations" on the board. We set
  `multipleLocations` and leave `locations` empty instead of storing "3 Locations" as a place; the
  description fetch fills in the real ones. A location filter keeps those rows rather than dropping
  them, because the board has not said where they are yet.
- `coverage` is measured against Workday's own counters. `complete` means every job the career site's
  facet counts account for was delivered; when a board cannot be split far enough, the rows say
  `partial` and the run summary says why, instead of quietly returning a smaller number.

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

Pay per result, so a run that finds nothing costs nothing.

| Event | Free and Bronze | Silver | Gold and above |
|---|---|---|---|
| **Job** (primary) | $0.001 | $0.0009 | $0.0008 |
| **Job description** | $0.0005 | $0.00045 | $0.0004 |
| **Actor start** | $0.00005 per run | $0.00005 | $0.00005 |

That is **$1.00 per 1,000 jobs**, or **$1.50 per 1,000** with the full description attached. Silver
users get 10 percent off the result events and Gold and above 20 percent; the Actor start event carries
no discount.

Worked example: reading NVIDIA's whole career site, 2,675 jobs without descriptions, costs
2,675 x $0.001 plus one start event, about **$2.68**. The same board with descriptions costs about
**$4.02**. Run it again next week under the same memory name and you pay only for the roles added since.

**Never charged:** jobs your filters removed, jobs a dedup memory already delivered (whether skipped or
re-sent), career sites that cannot be read, invalid input, the run summary and status rows, and a
description that was requested but did not come back. Rows are written to the dataset first and billed
afterwards, so a job you were never sent is never on your bill. The Actor start event is the one charge
that applies to every run, including a run that finds nothing.

### Input

| Field | Type | What it does |
|---|---|---|
| `careerSiteUrls` | array | The career sites to read. Any Workday address shape, or a bare tenant name. |
| `searchText` | string | Keyword handed to Workday's own search box. |
| `locations` | array | Keep only jobs whose location contains one of these words. |
| `remoteOnly` | boolean | Keep only remote roles, reading both the remote field and the location text. |
| `includeKeywords`, `excludeKeywords` | array | Keep or drop by words in the title, location or requisition ID. |
| `postedWithinDays` | integer | Keep only jobs posted in the last N days. |
| `facets` | object | Advanced: Workday facet ids passed straight through. |
| `includeDescription` | boolean | Fetch the full posting for every new job. Paid, off by default. |
| `maxJobsPerSite`, `maxTotal` | integer | Caps. The run budget is shared fairly across the sites you listed. |
| `completeLargeSites` | boolean | Split boards bigger than Workday's 2,000 result limit. On by default. |
| `memoryName` | string | Name a cross-run memory so later runs deliver and charge only new jobs. |
| `reemitKnownJobs` | boolean | Send the known jobs again, flagged and free. |
| `resetMemory` | boolean | Forget a memory and start a fresh baseline. |
| `proxyConfiguration` | object | Optional. Workday answers directly, so this is off. |

Minimal input:

```json
{
  "careerSiteUrls": ["https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"],
  "maxTotal": 20
}
```

### FAQ

#### Do I need a Workday API key or a login to scrape a career site?

No. Every career site serves its own jobs through a public JSON endpoint that the career page itself
calls, and this Actor uses that endpoint. There is no key, no login, no cookie and no browser involved.

#### Why do other Workday scrapers stop at 2,000 jobs?

Because Workday stops paginating there. Ask for a page past result 2,000 and it returns page one again
with HTTP 200, so a scraper that simply keeps paging collects duplicates and never notices. This Actor
splits a large board by job category, country and job type until every slice fits under the limit, and
sets `coverage` on every row so you can see whether a career site was read whole.

#### Can I scrape several company career sites in one run?

Yes. `careerSiteUrls` takes a list, every site is read in the same run, and `maxTotal` is shared fairly
across them: each site gets an equal share first, then any unused budget goes to the sites that still
have jobs.

#### Where do I find a company's Workday career site URL?

On the company careers page. The address contains `myworkdayjobs.com` or `myworkdaysite.com`, for
example `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`. You can also enter just the
tenant name, such as `nvidia`, and the Actor resolves the career site from the tenant's `robots.txt`.
Some companies run several career sites on one tenant, such as an external site and a campus site; each
one is a separate entry.

#### How do I get the full job description and the apply link?

Turn on **Fetch the full job description**. Each delivered job then carries `descriptionHtml` and
`descriptionText`, plus Workday's exact posting date, the time type, the country, Workday's own
`applyUrl` and the `canApply` flag. It costs one extra request per job and is charged as a Job
description event.

#### How do I see only the Workday jobs posted since last week?

Two ways, and they combine. Set `postedWithinDays` to 7 for a date window, or give the run a
`memoryName` and schedule it: every later run under that name delivers only the roles that appeared
since, and bills nothing for the ones you already have.

#### Why does this Actor run with limited permissions?

Least privilege. It reads and writes only its own storages and never touches anything else in your
Apify account. The cross-run memory is a named key-value store the Actor creates itself on its first run
and reads back on later ones.

### What this Actor does NOT do

- It does not discover companies. You give it career sites; it does not search for which employers use
  Workday.
- It does not read internal or login-only career sites, and it does not apply to jobs.
- It does not return salary. Workday career sites do not publish a structured salary field; where a pay
  range exists it sits inside the description text, which you get when descriptions are on.
- It does not scrape any other applicant tracking system. Greenhouse, Lever, Ashby, Workable and
  SmartRecruiters have their own Actors, linked below.
- It does not invent a posting date for the roles Workday files under "30+ Days Ago".
- It does not coordinate two runs that share one memory name at the same moment. Schedule a memory's
  runs one after another, not in parallel, or they can both treat the same role as new.

### Use it from the API

```bash
curl -X POST "https://api.apify.com/v2/acts/automation_craft~workday-jobs-scraper/runs?token=<YOUR_TOKEN>" \
  -H 'Content-Type: application/json' \
  -d '{"careerSiteUrls":["https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"],"maxTotal":50}'
```

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

const client = new ApifyClient({ token: '<YOUR_TOKEN>' });
const run = await client.actor('automation_craft/workday-jobs-scraper').call({
    careerSiteUrls: ['https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite'],
    includeDescription: true,
    maxTotal: 50,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_TOKEN>")
run = client.actor("automation_craft/workday-jobs-scraper").call(run_input={
    "careerSiteUrls": ["https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"],
    "memoryName": "weekly-nvidia",
    "maxTotal": 500,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item.get("title"), item.get("url"))
```

### Changelog

#### 1.0

First release. Whole career sites past Workday's 2,000 result limit, every Workday address shape
including bare tenant names, cross-run dedup memory, optional full descriptions with exact posting
dates, and a run summary with per-site coverage and measured fill rates.

### More data tools by Automation Craft

- [Greenhouse Jobs Scraper & API - Pay Once Per Job](https://apify.com/automation_craft/greenhouse-jobs-scraper)
- [Lever Jobs Scraper & API - Pay Once Per Job](https://apify.com/automation_craft/lever-jobs-scraper)
- [Ashby Jobs Scraper & API - Pay Ranges Included](https://apify.com/automation_craft/ashby-jobs-scraper)
- [Workable Jobs Scraper & API - Any Careers Page](https://apify.com/automation_craft/workable-jobs-scraper)
- [SmartRecruiters Jobs Scraper & API - Every Role](https://apify.com/automation_craft/smartrecruiters-jobs-scraper)
- [ATS Jobs Scraper & API - Greenhouse, Lever, Ashby](https://apify.com/automation_craft/ats-jobs-scraper)
- [LinkedIn Jobs Scraper - No Login, Real Dates](https://apify.com/automation_craft/linkedin-jobs-scraper)

# Changelog

This Actor's version history is a separate document: https://apify.com/automation\_craft/workday-jobs-scraper/changelog.md

# Actor input Schema

## `careerSiteUrls` (type: `array`):

One or more Workday career sites. Paste the address from the company careers page, for example https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite. The localized form with /en-US/, links to a single job, wd1.myworkdaysite.com/recruiting/... links and a bare tenant name such as nvidia all work. Every site is scraped in the same run.

## `searchText` (type: `string`):

Keyword sent to Workday's own search box, for example "software engineer". Leave empty to read the whole board. Workday matches this against the job title and the posting text.

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

Keep only jobs whose location contains one of these words, for example "Santa Clara" or "United Kingdom". Matching is case insensitive and partial. A posting that Workday shows as "3 Locations" hides its places until the description is fetched, so it is kept rather than dropped. Leave empty for every location.

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

Keep only jobs that Workday marks as remote or that say remote in the location. Whether the remote field is filled in at all is up to the employer, so this filter reads the location text too, and it ignores labels such as "Not Remote".

## `includeKeywords` (type: `array`):

Keep a job only when one of these words appears in its title, location or requisition id. Leave empty to keep everything.

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

Drop a job when one of these words appears in its title, location or requisition id, for example "intern" or "contract".

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

Keep only jobs posted in the last N days. Workday buckets everything older than 30 days as "Posted 30+ Days Ago", so values above 30 keep those rows as well. Leave empty for every posting date.

## `facets` (type: `object`):

Advanced. Facet ids passed straight to Workday, for example {"jobFamilyGroup": \["0c40f6bd1d8f10ae43ffaefd46dc7e78"]}. Every run summary lists the facets a career site exposes, so you can copy the ids from there.

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

Fetch the full posting for every new job and add descriptionHtml, descriptionText, the exact posting date, the country and the apply link Workday itself publishes. One extra request per job, charged as a Job description event, so it is off unless you turn it on. Jobs a memory already knows are re-sent free and without a fresh description fetch.

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

Upper bound on the jobs delivered from one career site. Use it to keep a run over many companies balanced and predictable.

## `maxTotal` (type: `integer`):

Upper bound on the whole run. The budget is shared fairly across the career sites you listed, and any unused share is handed to the sites that still have jobs.

## `completeLargeSites` (type: `boolean`):

Workday stops paginating at 2,000 results per query and then silently serves page one again. With this on, a larger board is split by job category, country and job type until every slice fits, so you get the whole board instead of the first 2,000 jobs. It costs a few extra requests and only runs when a board is actually bigger than the cap.

## `memoryName` (type: `string`):

Give this run a memory name, for example "weekly-nvidia", and every job it delivers is remembered. Later runs with the same name deliver and charge only jobs that are new, which is what makes a daily or weekly monitor cheap. Leave empty to deduplicate inside this run only.

## `reemitKnownJobs` (type: `boolean`):

Deliver the already-known jobs again, flagged with changeType DUPLICATE or UPDATED, free of charge. Useful when you want a full snapshot every run and still pay only for new roles. Free rows do not carry a freshly fetched description.

## `resetMemory` (type: `boolean`):

Forget everything stored under the memory name and start again. Every job then counts as new and is charged again, so use it only when you want a fresh baseline.

## `proxyConfiguration` (type: `object`):

Optional. Workday's public career API answers our datacenter requests directly, so a proxy is not needed and is left off. Turn it on only if your own network blocks Workday.

## Actor input object example

```json
{
  "careerSiteUrls": [
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
  ],
  "searchText": "software engineer",
  "locations": [
    "Santa Clara",
    "Remote"
  ],
  "remoteOnly": false,
  "includeKeywords": [
    "engineer",
    "data"
  ],
  "excludeKeywords": [
    "intern"
  ],
  "postedWithinDays": 7,
  "includeDescription": false,
  "maxJobsPerSite": 20,
  "maxTotal": 20,
  "completeLargeSites": true,
  "memoryName": "weekly-nvidia",
  "reemitKnownJobs": false,
  "resetMemory": false,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `items` (type: `string`):

No description

# 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 = {
    "careerSiteUrls": [
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
    ],
    "includeDescription": false,
    "maxJobsPerSite": 20,
    "maxTotal": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("automation_craft/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 = {
    "careerSiteUrls": ["https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"],
    "includeDescription": False,
    "maxJobsPerSite": 20,
    "maxTotal": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("automation_craft/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 '{
  "careerSiteUrls": [
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
  ],
  "includeDescription": false,
  "maxJobsPerSite": 20,
  "maxTotal": 20
}' |
apify call automation_craft/workday-jobs-scraper --silent --output-dataset

```

## MCP server setup

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