# Workday Jobs Scraper & API - Paste Any Careers URL (`practical_ophthalmologist_iuq/workday-jobs-scraper`) Actor

Scrape Workday careers sites: every live job posting as JSON, CSV or Excel. Paste any Workday careers or job URL - tenant, shard and site are read out of it automatically. Returns title, location, posted date and apply link for each opening. No browser automation, no proxy.

- **URL**: https://apify.com/practical\_ophthalmologist\_iuq/workday-jobs-scraper.md
- **Developed by:** [Scrappeer](https://apify.com/practical_ophthalmologist_iuq) (community)
- **Categories:** Jobs, Lead generation, MCP servers
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 job openings

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/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 - Every Opening From Any Careers Site

**Paste any Workday careers URL. It returns every live opening.**

![Sample output table](https://raw.githubusercontent.com/udaninn/career-page-job-monitor/main/docs/output-sample.svg)

Most Fortune 500 careers sites run on Workday, and unlike Greenhouse or
Lever, their API addresses cannot be guessed: the tenant, the wd-number
shard and the site name are all set per company. This Actor reads all three
out of whatever URL you paste - the careers home page, a search page, or a
single job posting link.

No proxy. No browser. No API key.

***

### Input

```json
{
  "careersUrls": [
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://salesforce.wd12.myworkdayjobs.com/External_Career_Site"
  ],
  "searchText": "machine learning",
  "titleKeywords": ["engineer"],
  "postedWithinDays": 7
}
```

Any link from the company's Workday site works - if you can open it in a
browser, this Actor can decompose it. A compact `tenant/wd5/SiteName` triple
works too.

`searchText` is passed to Workday's own server-side search (the same box on
the careers page), so filtering happens before fetching and you are billed
only for what matches.

***

### Output

One row per opening, in the same normalised schema as the sibling ATS
Actors - so Workday rows join cleanly against Greenhouse, Lever, Ashby,
Workable, Recruitee and SmartRecruiters rows on the same columns.

| Field | Notes |
|---|---|
| `companyName`, `boardToken`, `ats` | Who, and which board it was read from |
| `title`, `location`, `isRemote` | From the listing |
| `publishedAt` | Real date derived from Workday's posted-on text |
| `postedOnText` | The original display string, kept for auditing |
| `jobUrl`, `applyUrl` | The public posting link |
| `globalId` | `workday:{tenant/shard/site}:{jobId}` - stable join key |
| `description`, `employmentType` | Only with `includeDescription` |

Export as Excel, CSV, JSON or XML, or pull it through the API.

***

### Three traps this Actor absorbs for you

These are verified behaviours of the live endpoint, not documentation
quotes - the endpoint is not documented anywhere.

**1. Asking for more than 20 jobs returns zero.** The endpoint pages at
exactly 20 records. `limit: 21` answers HTTP 200 with an empty list and
`total` still filled in - indistinguishable from a company that is not
hiring. This Actor always pages at 20 and steps `offset` until `total`.

**2. The posting date is a sentence, in your language.** `postedOn` is a
localized display string ("Posted Today", "Posted 3 Days Ago"). Every
request this Actor makes pins `Accept-Language: en-US` and derives a real
date from the English strings. Past 30 days, Workday only says
"30+ Days Ago" - turn on `includeDescription` to get each job's true start
date from the detail record instead.

**3. The listing is thin.** Description, employment type and the real
posting date live one request deeper, per job. `includeDescription` fetches
them - one extra request per job, priced accordingly by your own choice.

***

#### Monitoring: only what changed

Set **`onlyNewSinceLastRun: true`** and schedule it. The first run records a
baseline and returns everything. After that, every run returns only roles
that **opened** (`isNew: true`) or **closed** (`isClosed: true`) since the
last one. A morning with no hiring activity returns nothing and costs only
the start fee.

Changing the URLs or filters starts a fresh baseline - otherwise every job
you stopped asking about would be reported as newly closed, which is a wrong
answer rather than a noisy one.

***

### Limits

- This is the API behind the public careers page: read-only,
  unauthenticated, and **not documented as a public contract**. Workday can
  reshape it without notice; this Actor tolerates missing fields and reports
  boards it cannot read as `notFound` rows rather than dropping them.
- The list response has no department or salary. Both may appear in the
  detail record (`includeDescription`), when the company fills them in.
- Some tenants run heavily customized sites; the mainstream layout is what
  this Actor reads.
- It pages politely - one POST per 20 jobs, bounded concurrency.

***

### How this works, in full

The endpoint, the URL decomposition and all three traps are written up here,
with live examples:

**[Workday job boards have a JSON API too. It's just better hidden.](https://dev.to/udaninn/workday-job-boards-have-a-json-api-too-its-just-better-hidden-23fl)**

For one company you can build this yourself - the article gives you the
code. This Actor earns its keep at the point where that stops being true.

***

### Support

A tenant that will not resolve? Open an issue on the **Issues** tab with the
careers URL.

# Actor input Schema

## `careersUrls` (type: `array`):

Any URL from the company's Workday careers site - the careers home, a search page, or a single job posting. All three unguessable parts (tenant, wd-shard, site name) are read out of the URL. A compact tenant/wd5/SiteName triple also works.

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

Passed to Workday's own server-side search - the same box on the careers page. Narrows results before they are fetched, so you are billed only for what matches.

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

Keep only jobs whose title contains any of these.

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

Drop jobs whose title matches any of these.

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

Keep only jobs whose location matches any of these.

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

Keep only jobs whose location reads as remote.

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

0 = no limit. Uses the date derived from Workday's posted-on text (pinned to en-US). Past 30 days Workday only says '30+ Days Ago', so windows above 30 cannot exclude older postings - turn on includeDescription to use each job's real start date instead.

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

Fetches each job's detail record: full HTML description, employment type and the real posting date. One extra request per job, so a 2,000-role tenant takes noticeably longer.

## `outputMode` (type: `string`):

"jobs" is one row per opening, "companies" is one row per board, "both" is the job rows followed by the summaries.

## `onlyNewSinceLastRun` (type: `boolean`):

Return only jobs that opened since your last run (isNew) and jobs that have since closed (isClosed). The first run records a baseline and returns everything. Built for a daily schedule: a run with no changes returns nothing and costs only the start fee. Changing the URLs or filters starts a fresh baseline.

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

Cap results per board. 0 means no limit. Starts at 10 so a first trial run stays cheap - clear it to get everything.

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

Boards fetched in parallel. The endpoint pages at 20 jobs per request, so large tenants take many requests either way - this bounds politeness, not speed.

## Actor input object example

```json
{
  "careersUrls": [
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
  ],
  "searchText": "machine learning",
  "remoteOnly": false,
  "postedWithinDays": 0,
  "includeDescription": false,
  "outputMode": "jobs",
  "onlyNewSinceLastRun": false,
  "maxJobsPerBoard": 10,
  "concurrency": 3
}
```

# Actor output Schema

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

Every row the run produced.

# 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 = {
    "careersUrls": [
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
        "https://salesforce.wd12.myworkdayjobs.com/External_Career_Site"
    ],
    "maxJobsPerBoard": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("practical_ophthalmologist_iuq/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 = {
    "careersUrls": [
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
        "https://salesforce.wd12.myworkdayjobs.com/External_Career_Site",
    ],
    "maxJobsPerBoard": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("practical_ophthalmologist_iuq/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 '{
  "careersUrls": [
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://salesforce.wd12.myworkdayjobs.com/External_Career_Site"
  ],
  "maxJobsPerBoard": 10
}' |
apify call practical_ophthalmologist_iuq/workday-jobs-scraper --silent --output-dataset

```

## MCP server setup

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