# Workday Jobs Scraper — Any myworkdayjobs.com Site | $1.50/1K (`bovi/workday-jobs-scraper`) Actor

Scrape job postings from any company's Workday-hosted career site (myworkdayjobs.com). Paste the career-site URL — talks to Workday's CXS search API, no browser, no proxy. Returns title, location, remote type, seniority, employment type, requisition id, posted date, apply URL, description.

- **URL**: https://apify.com/bovi/workday-jobs-scraper.md
- **Developed by:** [Vitalii Bondarev](https://apify.com/bovi) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.97 / 1,000 workday jobs scraper — any myworkdayjobs.com site | $1.50/1ks

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?

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 — Any myworkdayjobs.com Career Site

For talent-intelligence, sourcing, and job-aggregation teams that need job postings
from companies hosted on Workday — one of the largest ATS/HCM platforms, used by
thousands of enterprises for their careers pages (myworkdayjobs.com).

**Pay per result — from $1.50 / 1,000 jobs.** No API key, no browser, no proxy.
Uses Workday's own CXS (Candidate Experience Service) JSON API — the same API
the career-page website itself calls in the browser.

### How it works — access path

Paste a company's Workday career-site URL (the URL you see when you visit their
careers page, e.g. `https://nike.wd1.myworkdayjobs.com/NikeCareers`), and the
actor:

1. Extracts `tenant` (`nike`), `wdServer` (`wd1`), and `site` (`NikeCareers`)
   from the URL.
2. `POST`s to `https://<tenant>.<wdServer>.myworkdayjobs.com/wday/cxs/<tenant>/<site>/jobs`
   with `{"appliedFacets": {}, "limit": N, "offset": N, "searchText": ""}` and
   paginates via `offset`/`limit` until the listing is exhausted.
3. Optionally (`includeDescriptions=true`, default) `GET`s the per-job detail
   endpoint `.../wday/cxs/<tenant>/<site>/job/<externalPath>` for each posting
   to fill in the full description, requisition id, remote type, and
   employment type — fields the listing call doesn't include.

This CXS URL/JSON pattern is structurally the **same across every Workday
tenant** — it isn't a per-company scrape, it's one integration that works for
any myworkdayjobs.com site. Requests use Chrome TLS impersonation
(`curl_cffi`) since Workday fronts many tenants with bot-management that
blocks plain HTTP clients.

### Input

```json
{
  "siteUrls": ["https://workday.wd5.myworkdayjobs.com/Workday"],
  "titleKeyword": "engineer",
  "locationKeyword": "Remote",
  "remoteOnly": false,
  "includeDescriptions": true,
  "maxJobsPerSite": 50,
  "maxTotalJobs": 0
}
```

**How to find a site URL:** open the company's careers page — if it's on Workday,
the address bar will show `something.wdN.myworkdayjobs.com/SiteSlug`. Paste that
whole URL as-is.

### Output schema

| Field | Description |
|---|---|
| `company` | Workday tenant slug (e.g. `nike`) |
| `title` | Job title |
| `location` | Primary location |
| `all_locations` | Primary + additional locations, comma-joined |
| `remote_type` | `remote` / `hybrid` / `onsite` / `null` |
| `seniority` | `executive/vp/director/manager/principal/staff/lead/senior/mid/entry/intern`, inferred from title |
| `employment_type` | Workday `timeType` (e.g. "Full time") — only populated with `includeDescriptions=true` |
| `posted_at_raw` | Workday's own posted-date text, verbatim |
| `posted_at_is_exact` | `true` if `posted_at_raw` parses as a real ISO date, `false` if it's a relative string like "Posted Today" |
| `job_id` | Requisition id (`jobReqId`, e.g. `JR-0097159`) |
| `external_path` | Workday's internal per-job path (stable across runs — usable as a dedup key) |
| `url` | Public apply/posting URL |
| `apply_url` | Same as `url` on this platform |
| `description_text` / `description_html` | Full job description — only populated with `includeDescriptions=true` |
| `global_id` | `workday:<tenant>:<site>:<jobId or externalPath>` — stable dedup key |
| `parse_confidence` | 0.0–1.0 quality score |
| `warnings` | Machine-readable parse-quality flags |

### Known limitations (honest)

- **`postedOn` is often a relative human string**, not an exact date (e.g.
  "Posted Today", "Posted 30+ Days Ago"). This is a real Workday platform
  limitation, not a parsing bug — most tenants do not expose an exact ISO
  timestamp via CXS. `posted_at_raw` carries whatever Workday returns
  verbatim; `posted_at_is_exact` tells you whether it happens to be a real
  date on that tenant.
- **Salary and department are intentionally NOT in the schema.** Workday's
  CXS API does not reliably expose either across tenants — rather than ship
  a column that's almost always `null`, they're omitted.
- **Field names and exact response shape were confirmed via multiple
  independent research sources** (Workday integration write-ups, an
  open-source Workday job crawler's documented output schema, and
  third-party API-reference pages) that converge on the same `jobPostings`
  / `jobPostingInfo` structure and the `/wday/cxs/<tenant>/<site>/jobs` +
  `/job/<externalPath>` endpoint pair. This was **not verified against a
  live tenant response during development** (research/build session ran
  without hitting the live target from an unproxied connection, per this
  fleet's access policy) — the parser is written defensively (multiple
  fallback field names, graceful degradation to listing-only fields) to
  tolerate the minor per-tenant/per-CXS-version field variation these
  sources note, but a first production run should be spot-checked against
  a couple of real tenants before scaling up.
- **Some tenants require additional facet parameters** (e.g. a `locations`
  or `jobFamilyGroup` applied facet) to see certain job families in the
  default unfiltered search — this actor sends an empty `appliedFacets`
  object, which returns the full unfiltered job list on the tenants this
  was researched against, but an unusual tenant configuration could in
  principle require a facet to be pre-selected. Not observed in research,
  flagged for completeness.
- Very large tenants (thousands of open reqs) with `includeDescriptions=true`
  make one extra HTTP request per job — expect proportionally longer/costlier
  runs; set `includeDescriptions=false` for a fast metadata-only pass.

### Wedge vs. generic Workday scrapers

Most Workday scrapers on the market return raw CXS fields as-is. This actor
adds: canonical `seniority` and `remote_type` classification, a stable
`global_id` dedup key independent of Workday's own (sometimes-recycled)
`externalPath`, an honest `posted_at_is_exact` flag instead of silently
mis-parsing relative date text, and single-URL-paste onboarding (no need to
separately supply tenant/site/wdServer).

# Actor input Schema

## `siteUrls` (type: `array`):

Full career-site URLs on myworkdayjobs.com. Copy the URL straight from the company's careers page, e.g. https://nike.wd1.myworkdayjobs.com/NikeCareers. The actor extracts the tenant, data-center (wd1/wd3/wd5/...) and site slug from the URL itself.

## `titleKeyword` (type: `string`):

Case-insensitive substring to filter job titles. Example: 'engineer' returns only jobs whose title contains 'engineer'. Leave blank to return all jobs.

## `locationKeyword` (type: `string`):

Case-insensitive substring to filter job locations. Example: 'London' or 'Remote'. Leave blank to return all locations.

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

If enabled, only jobs with remote\_type='remote' are returned.

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

If enabled, fetches the per-job detail page for description\_text/html, requisition id, remote type and employment type. Costs one extra HTTP call per posting, so runs are slower and use more compute units. If disabled, only the fast listing fields (title, location, apply URL) are returned.

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

Maximum number of jobs to return per Workday site URL. 0 = no limit.

## `maxTotalJobs` (type: `integer`):

Maximum total jobs across all site URLs. 0 = no limit.

## Actor input object example

```json
{
  "siteUrls": [
    "https://workday.wd5.myworkdayjobs.com/Workday"
  ],
  "remoteOnly": false,
  "includeDescriptions": true,
  "maxJobsPerSite": 50,
  "maxTotalJobs": 0
}
```

# Actor output Schema

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

Dataset containing Workday job records (company, title, location, remote\_type, seniority, employment\_type, posted\_at\_raw, job\_id, url, description\_text/html, parse\_confidence).

# 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 = {
    "siteUrls": [
        "https://workday.wd5.myworkdayjobs.com/Workday"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("bovi/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 = { "siteUrls": ["https://workday.wd5.myworkdayjobs.com/Workday"] }

# Run the Actor and wait for it to finish
run = client.actor("bovi/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 '{
  "siteUrls": [
    "https://workday.wd5.myworkdayjobs.com/Workday"
  ]
}' |
apify call bovi/workday-jobs-scraper --silent --output-dataset

```

## MCP server setup

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