# Workday Jobs Finder: all jobs by company name, past the cap (`kohnschnee/workday-jobs-finder`) Actor

Type a company name or paste a Workday careers URL: every open job in one table, including sites with more than 2,000 jobs where Workday's list stops; exact dates and pay with descriptions. Honest counts, robots.txt respected.

- **URL**: https://apify.com/kohnschnee/workday-jobs-finder.md
- **Developed by:** [Stefan Affentranger](https://apify.com/kohnschnee) (community)
- **Categories:** Jobs
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.15 / 1,000 job returneds

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 Finder

**Every open job of a company that hires through Workday, in one table, including the jobs beyond 2,000 that many Workday sites hide behind their list cap, with an honest count of anything out of reach, pay ranges parsed from the posting text, a map and a one-page hiring report. Run it once, or let it tell you only what is new each week.** $1.50 per 1,000 jobs on the Free plan.

### Quick start (no code)

1. Type the company name (`Roche`, `NVIDIA`, `CVS Health`) into **Workday careers URLs or company names**, one per line, or paste the careers URL if you have it (any page of the site works, even a single job's link). Click **Start**. A site with 2,000 jobs takes about two minutes.
2. Names are resolved against a bundled directory of public Workday career sites and each candidate is verified live before reading. The SUMMARY shows `resolvedFrom`, `matchedBy` and `alternatives` (other live sites of the same tenant, with their job counts), so a wrong pick is visible and fixable by pasting the alternative's URL. Names not in the directory come back as `not_found` with a hint.
3. On the run page: the **Output** tab is the table (tabs *Jobs*, *Details*, *Locations*, *All fields*; download as Excel, CSV or JSON). The **Storage → Key-value store** tab holds `report.html` (one-page hiring report, with a plain-words line per site: complete, incomplete and why, not usable), `results-map.html` (map) and `SUMMARY` (the same as JSON).
4. **Is the list complete?** In the SUMMARY: `status: "found"` and `jobsReturned` equal to `jobsOnSite` means yes. `status: "partial"` means Workday hid some jobs and the SUMMARY says how many and why.
5. Want it weekly? Tick **Only new since my last run**, then **Schedule** the Actor and pick a destination under **Integrations**.

The example tasks on this page are pre-filled; copy one and change the URL.

### Which Actor for which company?

| The company's jobs page URL contains … | Use |
|---|---|
| `myworkdayjobs.com` or `myworkdaysite.com` | **this Actor** (paste the URL, or just type the company name: it is looked up in a bundled directory of public Workday sites and verified live) |
| `greenhouse.io`, `lever.co`, `ashbyhq.com`, `smartrecruiters.com`, `workable.com`, `recruitee.com`, `personio.de` | [ATS Jobs Finder](https://apify.com/kohnschnee/ats-jobs-finder) (names are enough there) |
| something else | not covered yet |

Known Workday sites to try: NVIDIA `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`, Adobe `https://adobe.wd5.myworkdayjobs.com/external_experienced`, Salesforce `https://salesforce.wd12.myworkdayjobs.com/External_Career_Site`, Roche `https://roche.wd3.myworkdayjobs.com/roche-ext`, Novartis `https://novartis.wd3.myworkdayjobs.com/Novartis_Careers`, HP `https://hp.wd5.myworkdayjobs.com/ExternalCareerSite`, Target `https://target.wd5.myworkdayjobs.com/targetcareers`, CVS Health `https://cvshealth.wd1.myworkdayjobs.com/CVS_Health_Careers`, University of Pennsylvania `https://wd1.myworkdaysite.com/recruiting/upenn/careers-at-penn`.

### Fields

| Field | Notes |
|---|---|
| `company`, `tenant`, `site`, `input` | Workday tenant (e.g. `nvidia`) and site; the URL you gave |
| `jobId`, `title`, `url`, `externalPath` | always filled; `jobId` is the requisition id (e.g. `JR2019657`) |
| `location`, `timeType`, `postedText`, `postedAt` | from the list; `postedAt` is derived from `postedText` ("Posted 3 Days Ago"), exact up to 29 days, `null` for "30+ Days Ago" |
| `startDate`, `country`, `remoteType`, `additionalLocations`, `hiringOrganization`, `applyUrl`, `description` | only with **Include description and exact date** (one extra request per job) |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`, `salaryText`, `salaryRanges`, `salarySource` | only with **Include description and exact date** or **Only jobs that state pay**: the pay range stated in the posting text (US and Canadian pay-transparency postings, e.g. `168,000 USD – 264,500 USD`), parsed, with the exact quote in `salaryText`; several level tiers in one posting are merged to the lowest minimum and highest maximum with `salaryRanges` = how many. `salarySource` is always `text`; `null` when the posting states no range. Measured 2 Nov 2026: 36 of 36 US NVIDIA postings parsed, 0 invented values on the blind sample of 60 postings without pay |
| `firstSeen` | delta mode: the date you first received this posting |
| `scrapedAt` | when this run read the site |

Workday has no structured salary field; what you get is what the posting says, nothing is estimated.

Example `SUMMARY` entry (key-value store):

```json
{"input": "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite", "status": "found", "tenant": "nvidia", "site": "NVIDIAExternalCareerSite",
 "jobsOnSite": 2679, "listedTotal": 2679, "jobsMatched": 2679, "jobsReturned": 2679,
 "newJobs": 31, "closedJobs": 18, "unchangedJobs": 2648, "closed": [{"jobId": "JR…", "title": "…", "url": "…", "firstSeen": "2026-10-20", "closedOn": "2026-11-02"}],
 "snapshot": {"jobs": 2679, "remoteShare": 0.01, "newestPostedAt": "2026-11-02", "postedLast7Days": 190, "countriesTop": [["United States", 1200], ["India", 380]]}}
```

`status` is one of `found`, `partial` (`error` says how many jobs were out of reach and why), `not_found` (wrong tenant or site, or a name that resolved to nothing), `robots_disallowed` (the company's robots.txt forbids crawling the site; not read, not charged), `not_workday_url`, `duplicate_of`, `error` (Workday did not answer), `stopped_cost_limit` / `skipped_cost_limit`. A name input also carries `resolvedFrom`, `matchedBy` and `alternatives`. Every non-`found` entry carries a `hint`. Counts: `listShows` is the number Workday's own list displays (2,000 when capped), `listedTotal` the site's real size (from Workday's facets when the list is capped; `listCapped: true` then), `jobsOnSite` what was read, `jobsMatched` after your filters, `jobsReturned` what you received. Complete = `status: "found"` and `jobsReturned` equal to `jobsOnSite` equal to `listedTotal`.

### Input

- **Which Workday careers sites?** URLs, one per line, with or without language prefix or job path. The same site given twice is read once.
- **Narrow down** (optional): *Search text* (Workday's own search, applied before reading, fast), *Title keywords*, *Location keywords*, *Remote only*, *Only jobs that state pay*, *Posted within days*, *Max jobs per site* (applied after the filters).
- **Monitor**: *Only new since my last run* and *List name*.
- **Output**: *Include description and exact date*, *Map view*.

Without *Include description and exact date*: `postedAt` is empty for jobs older than 29 days, jobs listed as "2 Locations" are not placed on the map and do not match location keywords, and *Remote only* can only read the location text. Turn it on when those matter; it costs one request per job (about one per second).

### Monitor: only new since my last run

One mechanism, one switch. Tick **Only new since my last run**, schedule the Actor, choose a destination under **Integrations**. From the second run on you receive only postings that were not in your previous run, and the SUMMARY shows `newJobs`, `unchangedJobs`, `closedJobs` and the list `closed`. Only new postings are charged.

- The memory is a key-value store **in your account** named `workday-jobs-finder-state-<list name>`. Delete it to start over.
- The first run returns everything (`firstRun: true`).
- In delta mode the whole site is read each time (needed to detect closed postings); the cost cap limits only what you receive.
- *Posted within days* is a date filter without memory.

### Why it is complete where others stop

Many Workday sites stop listing at 2,000 jobs and wrap around (NVIDIA: the list says 2,000, the facets 2,662); others (CVS Health, 18,791) page to the end. This Actor detects the cap, splits the site by Workday's own facets (nested ones included), merges the slices without duplicates and checks the count against the facets; uncapped sites are read to the end; a slice that cannot be split is reported as `partial` with the number out of reach. Measured live on 2 Nov 2026: Target 14,186 of 14,186, NVIDIA 2,679 of 2,679, CVS 18,762 of 18,786 during a 97-minute read while the site grew. Dates are requested in English, so `postedText` is always parseable.

### Hiring report and map

Every run writes `report.html` (KPIs, jobs per city and country, postings per day over 28 days; printable) and, with *Map view* on, `results-map.html` (jobs by city; places resolved locally against GeoNames, CC BY 4.0). Both are in the run's key-value store.

### Pricing and duration

Pay per job returned; sites not found and filtered-out jobs are free; platform usage is included. Per 1,000 jobs by Apify plan: **Free and Bronze $1.50 · Silver $1.35 · Gold, Platinum and Diamond $1.15.**

| Run | Jobs | Cost on Free | Duration |
|---|---|---|---|
| Adobe, full list | ~280 | $0.42 | 15 s |
| NVIDIA, past the cap | ~2,680 | $4.02 | ~2.5 min |
| Target, past the cap | ~14,200 | $21.30 | ~12 min |
| 10 sites, weekly monitor | ~600 new per week | $0.90 per week | a few minutes |
| any site with *Include description and exact date* | n | same price | + about 1 s per job |

A cost cap: Console *Run options → Maximum cost per run*; API run parameter `maxTotalChargeUsd`. Without client-side filters the Actor reads only as many jobs as the cap pays for.

### For developers and AI agents

```python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("kohnschnee/workday-jobs-finder").call(
    run_input={"careerSites": ["https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"], "postedWithinDays": 7},
    max_total_charge_usd=1.0,
)
jobs = list(client.dataset(run["defaultDatasetId"]).iterate_items())
store = client.key_value_store(run["defaultKeyValueStoreId"])
summary = store.get_record("SUMMARY")["value"]
report_html = store.get_record("report.html")["value"]
```

n8n / Make / Zapier: the Apify node runs the Actor and hands over the dataset; fetch `SUMMARY`, `report.html`, `results-map.html` from the key-value store. AI agents: callable through Apify's MCP server. Key-value store keys: `SUMMARY`, `SNAPSHOT`, `report.html`, `results-map.html`. Dataset views: `overview`, `salary` (titled *Details*), `places`, `full`.

### Limits, honestly

- One list request per second per host (two sites of one company share it), detail requests on a second lane; up to six sites are read in parallel. 2,000 jobs take about 100 seconds; 40 sites of 500 jobs each take about 3 minutes. *Title keywords*, *Location keywords*, *Remote only* and *Only jobs that state pay* are applied after reading; only *Search text* narrows what Workday sends. *Posted within days* stops the read early: Workday lists newest first, so paging ends once a whole page is older than asked (Adobe, 7 days: 120 of 278 read, all 70 matching jobs found, none missed against a full read; `readStoppedEarly: true` in the SUMMARY). Not in delta mode, which must see the whole site.
- Detail requests are made only for jobs that can still match: title keywords and plain place names are decided from the list first; only "2 Locations"-style rows need the detail to decide.
- A site that changes while it is read can differ from its live count: a job or two normally, a few per thousand on very large sites (CVS Health: 24 of 18,786 over 97 minutes). `listedTotal` versus `jobsOnSite` shows it.
- Companies that put Workday behind their own domain are covered only if the `myworkdayjobs.com` URL is known.
- Pay is parsed from the posting text only; postings that state no range get `null`, and a range written in an unusual form may be missed (open an issue with the posting URL).

### Legal and fair use

The Actor reads the JSON endpoints that Workday careers sites expose to every visitor's browser, at one request per second, identified, without login, cookies or personal data. Before reading a site it fetches the tenant's robots.txt: most tenants list their career sites under `Allow:`; a few (Lowe's, Nike, Thermo Fisher on 2 Nov 2026) put them under `Disallow:` for everyone, and those sites are **not read**, by name or by URL, and come back as `robots_disallowed` with nothing charged. Names are resolved from a bundled directory built from public web indexes (618 tenants on 2 Nov 2026, growing) and from the tenant's own robots.txt; each candidate is verified with one list request. Respect the posting companies' terms when you reuse their postings.

### FAQ

**My URL comes back `not_workday_url`.** It is not on `myworkdayjobs.com` or `myworkdaysite.com`. Open the company's jobs page, click a job, copy that address.

**The Workday site says 2,000 jobs, I got more.** That is the cap; the facets carry the real counts and the Actor reads each slice.

**Why `partial`?** A slice stayed at 2,000 and no facet could split it; the SUMMARY says how many jobs were out of reach. Narrow with *Search text*.

**Why is `postedAt` empty for some jobs?** Workday only says "Posted 30+ Days Ago" for them. Turn on *Include description and exact date*.

**How do I get only new jobs each week?** *Only new since my last run* plus a schedule.

**How do I get pay ranges?** Tick *Only jobs that state pay* (or *Include description and exact date*). The range the posting states is parsed into `salaryMin`/`salaryMax`; `salaryText` holds the quote so you can check it. Postings without a stated range are `null`, never estimated.

**Where are the report and the map?** Run page → Storage → Key-value store → `report.html`, `results-map.html`.

**Something is wrong?** Open an issue on this page with the URL that failed.

### Changelog

- **1.3 (3 Nov 2026)**: company names and domains accepted (bundled directory of public Workday sites, verified live, alternatives listed), pay ranges parsed from the posting text (`salaryMin`…`salarySource`), *Only jobs that state pay*, *Posted within days* stops the read early on the newest-first list, detail requests only for jobs that can still match, URL parser no longer fails on malformed input.
- **1.2 (2 Nov 2026)**: delta mode with closed-postings list, `report.html`, *Remote only*, input form in sections with plain-language help, hints in SUMMARY, README rebuilt after an independent usability review.
- **1.1 (2 Nov 2026)**: hiring snapshot, map, four dataset views, tiered pricing; fixes from an independent code review (nested facets, uncapped tenants read to the end, `partial` with the number out of reach, duplicate URLs read once, per-site error state, `myworkdaysite.com`, English dates pinned, title filter before detail requests, exact cost-cap accounting).
- **1.0 (2 Nov 2026)**: first release.

### Tested

Offline tests on recorded real responses and synthetic tenants shaped like Target, CVS and UPenn (23 tests), a live check against nine companies' own counts and random job URLs before each build, an independent code review and an independent usability review.

# Actor input Schema

## `careerSites` (type: `array`):

One per line: a Workday careers URL (`https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`) or just the company name or domain (`Roche`, `nvidia.com`). Names are looked up in a bundled directory of public Workday sites and verified live; the SUMMARY says which site was picked and lists the alternatives. If a name is not found, open the company's jobs page, click a job and paste that address.

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

Workday's own search box, e.g. `data scientist`. Fast: it narrows the list before reading.

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

Keep jobs whose title contains any of these words, e.g. `engineer`, `nurse`. Not case-sensitive.

## `locationKeywords` (type: `array`):

Keep jobs whose place contains any of these, e.g. `Zurich`, `Germany`, `Remote`. Jobs listed as '2 Locations' need detail mode to match.

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

Keep only jobs whose location says remote, or (with 'Include description and exact date' on) whose Workday remote type says so.

## `withSalaryOnly` (type: `boolean`):

Keep only postings whose text states a pay range (US and Canadian pay-transparency postings mostly). Turns on 'Include description and exact date' because the range lives in the description; one extra request per job. The range is parsed into salaryMin/salaryMax/salaryCurrency/salaryPeriod with salarySource = text, never guessed.

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

Only jobs posted in the last N days, e.g. 7 for the last week.

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

Stop after this many jobs per site (after the filters). 0 = all.

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

Remembers what you already received, in a key-value store in your account named `workday-jobs-finder-state-<state key>`.

## `stateKey` (type: `string`):

Only change this if you monitor several different site lists. Each list has its own memory.

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

Full job text, exact posting date, country, remote type and hiring entity. One extra request per job (about one per second).

## `mapView` (type: `boolean`):

Create `results-map.html` in the key-value store: the returned jobs by city on a zoomable map.

## Actor input object example

```json
{
  "careerSites": [
    "https://adobe.wd5.myworkdayjobs.com/external_experienced",
    "Roche"
  ],
  "remoteOnly": false,
  "withSalaryOnly": false,
  "maxJobsPerSite": 0,
  "onlyNewSinceLastRun": false,
  "stateKey": "default",
  "includeDescription": false,
  "mapView": true
}
```

# Actor output Schema

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

One row per job posting (JSON; add ?format=csv for CSV).

## `summary` (type: `string`):

Per careers URL: jobs on site, listed total, matched, returned, status.

## `map` (type: `string`):

Zoomable map of the returned jobs by city.

## `snapshot` (type: `string`):

Jobs, remote share, posting dates, top places.

## `report` (type: `string`):

One-page HTML report of the returned jobs.

# 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 = {
    "careerSites": [
        "https://adobe.wd5.myworkdayjobs.com/external_experienced",
        "Roche"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kohnschnee/workday-jobs-finder").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 = { "careerSites": [
        "https://adobe.wd5.myworkdayjobs.com/external_experienced",
        "Roche",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("kohnschnee/workday-jobs-finder").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 '{
  "careerSites": [
    "https://adobe.wd5.myworkdayjobs.com/external_experienced",
    "Roche"
  ]
}' |
apify call kohnschnee/workday-jobs-finder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kohnschnee/workday-jobs-finder"
        }
    }
}
```

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/xYGN1jOememeH01vv/builds/1DSOfM51b83R56qy3/openapi.json
