# Workday Jobs Scraper (`humble-echidna/workday-jobs`) Actor

Unofficial, not affiliated with Workday. Paste Workday career-site URLs and get every open job those companies list: full descriptions, exact dates, salary where the text states it, one clean format. Filter by keyword, place, type or date; get only new, changed or closed jobs since your last run.

- **URL**: https://apify.com/humble-echidna/workday-jobs.md
- **Developed by:** [Michael Costa](https://apify.com/humble-echidna) (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 $1.40 / 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

### What does Workday Jobs Scraper do?

You tell it **which companies you care about**. It returns **every open job those companies post on Workday**, straight from each company's own Workday career site, in **one clean, consistent format**.

This is an **unofficial** tool: it isn't made, endorsed or supported by Workday, and isn't affiliated with Workday in any way. Workday is a trademark of its owner, named here only to say which career sites the actor reads.

It is **not** a job-board search: it doesn't search Indeed, LinkedIn, or all of Workday. Workday doesn't publish a directory of the companies that use it, so you list the companies, and you get every job each of them has open, the moment it's listed.

**Paste each company's Workday career-site address**, one per line:

| You paste | Example |
|---|---|
| **Workday URL** | `https://sonos.wd1.myworkdayjobs.com/Sonos`, a localized or single-job link like `https://sonos.wd1.myworkdayjobs.com/en-US/Sonos/job/…`, or `https://wd3.myworkdaysite.com/recruiting/<company>/<site>` |
| **Careers page** | `https://example.com/careers`, when it links to the company's Workday site. Many big-company pages load jobs with JavaScript; then paste the Workday URL instead. |

**How to find it:** open any job on the company's careers site. If the address contains `myworkdayjobs.com`, that's it: paste it as is.

Company names can't be looked up on Workday: its addresses carry a server number (`wd1`, `wd5`…) and a site name that nobody can guess.

Each run's log and the `RUN_STATS` record show what every URL or page was matched to, and anything that couldn't be used, with the reason. A company on another hiring platform is skipped with a clear message and the rest of the run continues; for Greenhouse, Lever, Ashby, Recruitee, Personio and Teamtailor, use [Company Career Page Jobs Scraper](https://apify.com/humble-echidna/ats-jobs), which has the same output format.

**Try it in one click:** the input comes pre-filled with 1 example company: Sonos. That's about 50 jobs, or roughly $0.10 (50 × $0.002 per job, plus $0.00005 for the run start). **Then replace it with the companies you actually want.**

It reads each career site's own public job data, the same data the company's careers page shows, without logging in. No browser, no proxies, so runs are fast and cheap.

### Monitor new, changed and closed Workday jobs

Turn on **Only jobs new since my last run** (`onlyNewJobs: true`), save the input as a task and schedule it (Console → Schedules, e.g. daily): the first run returns every matching job, later runs only jobs posted since. For a full job-change feed, also turn on **Also report closed and changed jobs** (`includeClosedAndChanged: true`): each row then has a `changeType` of `new`, `changed` (title, locations or employment type changed as the job list shows them; `changedFields` shows old and new) or `closed` (taken off the company's Workday career site; the row keeps its last known title, location and link, with `closedDetectedAt`). Send the results on from the task's **Integrations** tab (Slack, email, a webhook, or Make, Zapier or n8n). A run with nothing new, changed or closed returns 0 jobs and costs only the start fee.

### Why this one?

- **Full descriptions and exact dates.** Workday's job list only says "Posted 3 days ago"; each job's own record gives the exact posting date, the full description, every location and the country, and those are filled in for every job you get back (fetched only for those, after filters). The employment type and workplace type (remote, hybrid, onsite) are filled in when the company sets them in Workday, otherwise they're `null`.
- **Fast keyword runs on big boards.** Title keywords are sent to Workday's own search, so a keyword run on a 2,000-job board only downloads the matches. Workday lists at most 2,000 jobs per search, and each keyword is its own search, so keywords also reach jobs on the biggest boards that a full list can't (the run log says if a keyword still matched more than 2,000).
- **Any Workday address.** `…myworkdayjobs.com/…`, localized links (`/en-US/…`), single-job links, and `myworkdaysite.com/recruiting/…` all work.
- **Reliable.** A broken or unreachable company never affects the others in your run. A malformed job record is skipped instead of failing its company. Rate limits are handled automatically. The run log and the `RUN_STATS` record say exactly which company had a problem and why.
- **Clean, consistent fields.** Employment type is one of `FULL_TIME`, `PART_TIME`, `CONTRACT`, `TEMPORARY`, `INTERNSHIP`, `VOLUNTEER` and workplace type one of `remote`, `hybrid` or `onsite` (`null` when the job doesn't say); dates are ISO 8601 UTC. The format is identical to our other job scrapers, so results from several platforms can go in one table.
- **Salary where it's published.** Workday has no salary field, but when the company puts the pay range in the job text, it's extracted from there as numbers with currency and period ("$150,000 - $200,000 USD", "$28.50/hr"), and `salarySource` says so. Extraction is conservative: bonuses, stipends and funding amounts are ignored.

### Use cases

- **Job boards and aggregators:** keep listings fresh straight from the source.
- **Recruiters and sales teams:** track which Workday companies are hiring for what (hiring signals).
- **Job alerts:** turn on **Only jobs new since my last run** and schedule it daily.
- **Salary and market research:** pay ranges as numbers, with currency and period.

### How much does it cost?

You pay per job returned: **$2.00 per 1,000 jobs**, plus $0.00005 each time a run starts. Filters are applied before you're charged, so you only pay for jobs you keep. Closed and changed jobs are rows like any job, $0.002 each, and only when you turn that option on.

**It's cheaper on paid Apify plans:** $1.80 per 1,000 jobs on Starter, $1.60 on Scale and $1.40 on Business. The prices on this page are the Free-plan price, so on a paid plan you pay less than the examples show.

Two ways to cap a run, and it stops cleanly at whichever comes first:

- **Max jobs per run** in the input.
- **Maximum cost per run** in the run options. It stops fetching as soon as the limit is covered, so a capped run is also a fast one.

### Input

| Field | What it does |
|---|---|
| **Companies** | One per line: a Workday career-site URL (…myworkdayjobs.com/… or …myworkdaysite.com/recruiting/…), or a careers page that links to it. |
| Title keywords / Exclude keywords | Keep or drop jobs by words in the title (case-insensitive). Also sent to Workday's own search, so keyword runs on big boards are fast. |
| Locations | Keep jobs whose location or country contains any of these. Add `remote` to include remote jobs. |
| Remote jobs only | Only jobs marked remote. |
| Posted within (days) | Only recently published jobs. |
| Departments | Workday career sites state no department, so leave it empty. |
| Employment types | Only jobs of these types (`FULL_TIME`, `PART_TIME`, ...), from Workday's time type. |
| Minimum salary, currency, per | Only jobs whose pay reaches this amount at the top of their range, in this currency (default USD) and period (default a year). Currencies are never converted. |
| Keep jobs with no comparable salary | With a minimum salary: keep (default) or drop jobs whose pay isn't published or can't be compared. |
| Only jobs new since my last run | First run returns everything that matches; later runs return only jobs posted since. Remembered in your own Apify storage, per company and per filter combination. |
| Also report closed and changed jobs | Later runs also return jobs you were given before that have since closed or changed, each marked with `changeType`. Implies the option above. |
| Include full job description | Plain-text description (on by default). On Workday this also gives the exact posting date, every location and the country (one extra request per job). Turn it off for much faster runs. |
| Max jobs per run | Cap the total number of jobs across all companies. |
| Max jobs per company | Cap results per company. |

```json
{
  "companies": [
    "https://sonos.wd1.myworkdayjobs.com/Sonos",
    "https://zendesk.wd1.myworkdayjobs.com/zendesk"
  ],
  "keywords": [
    "engineer"
  ],
  "locations": [
    "Dublin",
    "remote"
  ],
  "postedWithinDays": 30
}
```

### Output

Every job has the same fields. Fields Workday doesn't publish for a job are `null`.

```json
{
  "id": "workday:sonos/sonos:R2848",
  "platform": "workday",
  "company": "sonos/sonos",
  "companyName": null,
  "title": "Senior Manager, Knowledge Engineering & AI Agent Enablement",
  "department": null,
  "team": null,
  "location": "Boston, MA",
  "locations": [
    "Boston, MA",
    "Seattle, WA",
    "Goleta"
  ],
  "country": "US",
  "remote": null,
  "workplaceType": null,
  "employmentType": "FULL_TIME",
  "postedAt": "2026-10-09T00:00:00Z",
  "updatedAt": null,
  "url": "https://sonos.wd1.myworkdayjobs.com/Sonos/job/Boston-MA/Senior-Manager--Knowledge-Engineering---AI-Agent-Enablement_R2848",
  "applyUrl": "https://sonos.wd1.myworkdayjobs.com/Sonos/job/Boston-MA/Senior-Manager--Knowledge-Engineering---AI-Agent-Enablement_R2848/apply",
  "salaryMin": null,
  "salaryMax": null,
  "salaryCurrency": null,
  "salaryPeriod": null,
  "salaryText": null,
  "salarySource": null,
  "description": "At Sonos we want to create the ultimate listening experience for our customers and know…",
  "scrapedAt": "2026-10-10T18:02:11.519204Z"
}
```

`id` is stable across runs (`platform:company:jobId`), so you can use it to deduplicate.

### FAQ

**Can I get every job on Workday, from every company?** No. This actor fetches the companies you list. Workday doesn't publish a directory of its customers, so no scraper can list "all Workday jobs" from Workday itself. Use this one when you know which companies you want to track and need their complete, fresh listings.

**Why can't I just type the company name?** Workday addresses contain a server number and a site name chosen by each company (`sonos.wd1.myworkdayjobs.com/Sonos`, `nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`), and Workday offers no way to look them up. Open one of the company's jobs and paste its address.

**A company wasn't found.** The run log and `RUN_STATS` (`unresolved`) say why, and the rest of the run continues. Common reasons: the careers page loads its jobs with JavaScript or links to another platform. Paste the Workday URL from one of the company's jobs instead.

**How does "Only jobs new since my last run" know about my last run?** It stores the IDs of the jobs it returned in a named key-value store (`workday-jobs-seen`) in **your** Apify account, which persists between runs. Each company and filter combination has its own memory, so a daily "engineer" alert and a weekly "sales" alert on the same company don't interfere. Changing the filters starts a fresh search, and so does deleting that store. Jobs held back by **Max jobs per company** are not marked as seen, so they come through on a later run.

**How are closed jobs detected?** With **Also report closed and changed jobs** on, the same memory also keeps each returned job's title, locations, employment type and link as the company's job list shows them. On the next run, a job that's no longer on the company's Workday career site comes back once as `changeType: "closed"`, with those last known fields and `closedDetectedAt`; a job whose tracked fields differ comes back once as `changeType: "changed"`, with `changedFields` (old and new). Changes inside a job's description aren't tracked on Workday. A job only counts as closed when its career site was read cleanly: if it fails, is blocked, times out or has records that don't parse, or suddenly lists nothing or far fewer jobs, its closed-job check is skipped for that run and the log and `RUN_STATS` (`closureChecksHeld`) say why.

**How do the location, employment type and salary filters work on Workday?** The job list often shows only "3 Locations" and never the pay, so with **Locations**, **Remote jobs only**, **Employment types** or **Minimum salary** set, each job's own record is read first (for the jobs your title keywords leave), and the filters judge that. All of them run before you're charged, so a job they leave out costs nothing. A job with no stated employment type is left out while that filter is set; a job with no comparable pay is kept unless you turn off **Keep jobs with no comparable salary**. Workday states no department, so leave **Departments** empty.

**Why is `companyName` empty?** Workday only publishes a legal-entity name per job (like "2100 NVIDIA USA"), not the company's display name, so it's left `null` rather than wrong. `company` holds the Workday tenant and site (e.g. `sonos/sonos`).

**Workday is slower than other platforms.** Each career site gets at most about 7 requests a second, and descriptions need one request per job, so ~2,000 jobs with descriptions take about 5 minutes. Turn off descriptions or add title keywords for fast runs. Workday also lists at most 2,000 jobs per list, so for very large employers use title keywords to reach everything (the run log tells you when a list was capped).

**A company that used to work now fails or returns fewer jobs.** Workday can change its career sites without notice, so a site change is the most likely cause. The run log names the company and what didn't parse, and every other company in the run is unaffected. Please open an issue with the company so it can be looked at.

**Some of my companies use another platform.** A job-board URL from another platform is skipped with a message saying which platform it is; the rest of the run continues. [Company Career Page Jobs Scraper](https://apify.com/humble-echidna/ats-jobs) reads Greenhouse, Lever, Ashby, Recruitee, Personio and Teamtailor in one run, with this exact output format.

**Is this legal?** It reads only what each company's Workday career site shows any visitor without logging in: its open job postings, through the same public data the careers page itself loads. It collects job postings only: no candidate data, no recruiter names, emails, or other personal data. It's unofficial and not affiliated with Workday. It identifies itself honestly (User-Agent `HumbleEchidnaApify`) and reads every host's robots.txt before anything else: anything a site disallows is never requested, and the company is reported with the reason instead of being skipped silently; crawl delays are respected. Workday career sites' robots.txt allows the career site and doesn't disallow the job data it reads (checked on 2026-10-10). Requests are paced (at most about 7 a second per career site) and back off when a site asks. If you run a Workday career site and want it left out, open an issue on this actor and it will be excluded promptly.

**Which addresses does it request?** Only public web addresses on the standard web ports (80 and 443). A careers
page on another port, or on a domain that points at a private network, is refused with the reason, and a careers
domain that doesn't exist is reported as such. Each site's robots.txt is read once per run; a site that asks for
more than 30 seconds between requests is reported rather than fetched slowly.

### Versions

Current version: **1.1**. See the Changelog tab for what changed in each version.

# Changelog

This Actor's version history is a separate document: https://apify.com/humble-echidna/workday-jobs/changelog.md

# Actor input Schema

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

One company per line: its Workday career-site URL (e.g. https://sonos.wd1.myworkdayjobs.com/Sonos; localized or single-job links and myworkdaysite.com/recruiting/... addresses work too), or its careers page if that page links to its Workday site. Company names can't be looked up on Workday: open any of the company's jobs and paste that address. The list starts with 1 example; replace it with your own.

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

Keep only jobs whose title contains any of these words (case-insensitive). Leave empty for all jobs. They're also sent to Workday's own search, so a keyword run on a big board downloads only the matches; Workday lists at most 2,000 jobs per search, and each keyword is its own search.

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

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

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

Keep only jobs whose location or country contains any of these, e.g. London, Germany, US. Add "remote" to also keep remote jobs.

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

Keep only jobs Workday marks as remote (the company's remote type, or a location that says remote).

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

Keep only jobs published in the last N days. Leave empty for all.

## `departments` (type: `array`):

Workday's career sites don't state a department or team for their jobs, so every job's department is null and this filter would leave out every job: leave it empty. It's offered so the same input works in every one of our ATS actors.

## `employmentTypes` (type: `array`):

Keep only jobs of these types: `FULL_TIME`, `PART_TIME`, `CONTRACT`, `TEMPORARY`, `INTERNSHIP` or `VOLUNTEER`, mapped from Workday's time type (full time / part time); jobs whose type isn't stated are left out while this is set. Leave empty for all types.

## `minSalary` (type: `integer`):

Keep only jobs whose pay reaches this amount (the top of the job's range), in the currency and period below. Pay comes from the ATS's own salary field, or else from pay-transparency text in the description, which is then read even if "Include full job description" is off. Most jobs publish no pay: see "Keep jobs with no comparable salary". Leave empty for no salary filter.

## `minSalaryCurrency` (type: `string`):

Three-letter currency code of the minimum salary, e.g. USD, EUR, GBP, CAD. Amounts are never converted between currencies: a job paid in another currency counts as having no comparable salary.

## `minSalaryPeriod` (type: `string`):

The period of `minSalary`: `YEAR` (default), `MONTH`, `WEEK`, `DAY` or `HOUR`. Pay stated per another period is compared as a full-time year of 2,080 hours, 260 days, 52 weeks or 12 months; a job whose pay has no period counts as having no comparable salary.

## `keepJobsWithoutSalary` (type: `boolean`):

Only with a minimum salary. On: jobs that publish no pay, or pay in another currency or with no period, are kept (their salary fields show what is known). Off: only jobs known to pay at least the minimum are returned.

## `onlyNewJobs` (type: `boolean`):

The first run returns all matching jobs; later runs return only jobs posted since. The actor remembers what it already returned in a storage in your own Apify account, separately for each company and each combination of filters, so different searches never hide each other's jobs. Changing the filters starts a fresh search. Use it with a schedule for job alerts or hiring-signal monitoring.

## `includeClosedAndChanged` (type: `boolean`):

Turns this into a job-change feed: besides new jobs, each run also returns jobs returned before that have since closed (changeType `closed`, last known title, location and link) or changed title, location, department, employment type or salary (changeType `changed`, changedFields old -> new). Each such row is charged like a job. Implies `onlyNewJobs`. A board that fails, is blocked or suddenly shrinks isn't checked for closures that run.

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

Adds the plain-text description and, from each job's own record, the exact posting date, every location, the country and the employment type (one extra request per job). Turn off for much faster runs; dates are then approximate ("Posted 3 Days Ago").

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

Stop after this many jobs in total, across all companies. Leave empty for no limit. The run also stops cleanly at the maximum cost per run you set in the run options, whichever comes first.

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

Stop after this many matching jobs per company. Leave empty for no limit.

## `maxConcurrency` (type: `integer`):

How many companies are fetched at the same time, 1 to 20 (default 5). Each career site gets at most about 7 requests a second; lower this if a career site starts rate-limiting.

## Actor input object example

```json
{
  "companies": [
    "https://sonos.wd1.myworkdayjobs.com/Sonos"
  ],
  "remoteOnly": false,
  "minSalaryCurrency": "USD",
  "minSalaryPeriod": "YEAR",
  "keepJobsWithoutSalary": true,
  "onlyNewJobs": false,
  "includeClosedAndChanged": false,
  "includeDescription": true,
  "maxConcurrency": 5
}
```

# Actor output Schema

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

No description

## `runStats` (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 = {
    "companies": [
        "https://sonos.wd1.myworkdayjobs.com/Sonos"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("humble-echidna/workday-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 = { "companies": ["https://sonos.wd1.myworkdayjobs.com/Sonos"] }

# Run the Actor and wait for it to finish
run = client.actor("humble-echidna/workday-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 '{
  "companies": [
    "https://sonos.wd1.myworkdayjobs.com/Sonos"
  ]
}' |
apify call humble-echidna/workday-jobs --silent --output-dataset

```

## MCP server setup

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