# Workday Jobs Monitor — Change Tracking + Hiring Signal Score (`goldenrod_serrano/workday-hiring-signals`) Actor

Pull every open job from any Workday career site (\*.myworkdayjobs.com), filtered by country or city, with a new-jobs-since-last-run monitor and a per-company hiring-signal summary. Optional change tracking and an explainable 0–100 signal score.

- **URL**: https://apify.com/goldenrod_serrano/workday-hiring-signals.md
- **Developed by:** [Paopao Studio](https://apify.com/goldenrod_serrano) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 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.

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 Monitor for Recruiter BD — Country Filter + Hiring Signals

Pull **every open job from any Workday career site** (`*.myworkdayjobs.com`, `*.myworkdaysite.com`), filtered **server-side by country or city**, with a **new-jobs-since-last-run monitor** and a **per-company hiring-signal summary**.

Thousands of large employers (NVIDIA, Salesforce, Mastercard, Accenture, banks, airlines, governments, universities) post their jobs only on Workday. LinkedIn and job boards show a delayed, incomplete copy. This Actor reads the employer's own Workday site directly.

**No login, no cookies, no proxy.** It calls the same public JSON endpoint the careers page calls in your browser, so runs are fast and cheap.

### What's new: change tracking and signal scoring

Two optional switches (both **off by default**):

1. **Change tracking** (`changeTracking`) + **State key** (`stateKey`) — schedule the Actor and get only what changed since the last run with the same key: new jobs, changed fields (title, location, …) and free closed-job notices. First run is a baseline.
2. **Signal scoring** (`signalScoring`) — adds an explainable 0–100 `signal_score`, a hot/warm/cool/cold tier and plain-English `score_reasons` on each company summary.

**Baseline tip.** With change tracking on, *Max jobs to output per site* only limits how many rows a run outputs. Every scanned job is saved as seen, so capped-out jobs are **not** sent or charged on a later run. If you need every matching job as the baseline, set that cap to **0** (no limit) on the first run. Only rows stopped by your max charge per run are carried over.

Details: [Change tracking](#change-tracking-new-changed-and-closed-jobs-optional) · [Signal score](#explainable-signal-score-optional).

### Why this one

- **Country/city filter that Workday applies for you.** Type `UAE`, `Saudi Arabia`, `India`, `Japan`, `Singapore`, `Brazil`, or a city. The Actor maps it to the site's own location filter, so you get every job in that market, **even on sites with more than 2,000 jobs** (Workday's list view stops at 2,000). Shortcuts: `GCC`, `SEA`, `LATAM`, `UAE`, `KSA`, `UK`, `US`.
- **Monitor mode with closed jobs.** Schedule daily and get, and pay for, only the jobs that opened since the last run. The company summary also reports `closed_jobs_since_last_run`, `closed_job_urls` and `net_change_since_last_run`, so you can see roles being filled or pulled as well as roles opening.
- **Seniority and work model on every row.** Each job gets `seniority` (intern, entry, mid, senior, lead, manager, director, executive) from its title, and `work_model` (remote / hybrid / onsite, or null when Workday doesn't say). Filter with *Seniority levels*, for example only `director` and `executive` for decision-maker roles. The company summary adds `seniority_mix`.
- **Hiring signals.** One row per company: open jobs, jobs posted in the last 7 and 30 days, top locations and countries, remote share, and a 0–100 score. Use it to rank target accounts.
- **Optional details.** Full description, country plus ISO code, exact posting date, time type, and the **hiring legal entity** (for example `India: Mastercard Technology Private Limited`).
- Paste any Workday URL: a job link, a search link, or a link with a locale (`/en-US/`) all work. A company careers page that links to Workday works too.

### Who uses this

- **Recruiters and staffing-agency BD (primary).** Watch Workday employers in one market (GCC, India, APAC…) without LinkedIn lag. Deduped rows, long-open / repost flags, "JD names an internal recruiter", and `company_domain` for outreach. Example: GCC firms hiring under Emiratisation / Saudisation quotas.
- **B2B sales.** A company opening 20 roles in Riyadh this month is expanding there. Sort by `posted_last_30_days` or `hiring_signal_score`.
- **Job boards and aggregators.** Normalized rows from any Workday tenant.
- **Job seekers.** Filter by country, keyword, recency or remote.

### Input

| Field | Example |
|---|---|
| Workday career sites | `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite` |
| Search text (server-side) | `sales` |
| Countries / cities | `UAE`, `GCC`, `Japan`, `Bangalore` |
| Keywords / exclude (title) | `manager` / `intern` |
| Posted within N days | `7` |
| Seniority levels | `director`, `executive` |
| Monitor mode + state key | on / `gcc-weekly` |
| Include details | on (adds description, country, legal entity) |
| Change tracking / Signal scoring | on / on (optional, off by default) |
| Score functions | `sales`, `customer_success` |

Run it with no input and it runs a free 5-job sample.

### Output

**Job rows** (`type: "job"`): `company, company_domain, site, job_id, title, location, country, country_code, seniority, remote, work_model, time_type, posted_on_text, posted_days_ago, days_open, is_long_open, is_repost, fingerprint, names_internal_recruiter, recruiter_contact, duplicate_count, duplicate_urls, posted_date, hiring_entity, url, description (optional), is_new`

**Company summary rows** (`type: "company_summary"`): `company_domain, careers_url, total_open_jobs, total_capped_by_workday, jobs_scanned, matched_jobs, new_jobs_since_last_run, closed_jobs_since_last_run, closed_job_urls, net_change_since_last_run, long_open_jobs, repost_jobs_in_output, jobs_naming_recruiter, posted_last_7_days, posted_last_30_days, seniority_mix, remote_share, top_locations, top_countries, sample_matched_titles, hiring_signal_score, location_facets_applied`

**Error rows** (`type: "error"`, never charged): an input that is not a readable Workday site.

Example (real run, Oct 29 2026, `locations: ["GCC"]`):

```json
{"type":"company_summary","company":"mastercard","total_open_jobs":25,"posted_last_7_days":8,"location_facets_applied":["Dubai, United Arab Emirates","Riyadh, Saudi Arabia","Vendor - Kuwait City, Kuwait (Mercans)"]}
{"type":"job","company":"mastercard","title":"Director, Finance Business Partner","country":"Saudi Arabia","country_code":"SA","posted_on_text":"Posted Yesterday","hiring_entity":"...","url":"https://mastercard.wd1.myworkdayjobs.com/CorporateCareers/job/..."}
```

### Change tracking: new, changed and closed jobs (optional)

Turn on **Change tracking** (`changeTracking: true`) and schedule the Actor. The first run is a baseline: every matching job is output as `change_type: "new"`. Later runs output only **new** jobs, **changed** jobs (title, location, work model, remote, time type or country changed; `changed_fields` and `previous_values` show what changed) and **closed** jobs (`type: "job_closed"`, free). New and changed rows are charged as normal job rows. State is kept per site in the named key-value store `workday-hiring-signals-state`. If a scan is incomplete (Workday reports 2,000+ or *Max scan per company* cut it short), the results are merged into the saved state and nothing is reported as closed. **Every job scanned is saved as seen, including jobs cut by *Max jobs per company*.** The cap only limits how many rows a run outputs; capped-out jobs are not sent or charged on a later run, and closures are judged against the full list. If you need every matching job, set the cap to 0 (no limit) on the baseline run. The only rows carried to the next run are rows your *max charge per run* stopped, so no row is charged twice. (The older *Only new jobs since last run* mode keeps its own behavior.) Off by default.

### Explainable signal score (optional)

Turn on **Signal scoring** (`signalScoring: true`) to add `signal_score` (0-100), `signal_tier` (hot ≥70, warm ≥45, cool ≥20, cold) and `score_reasons` to each summary. Job rows get `job_function`, `company_signal_score`, `company_signal_tier` and `score_reasons`. The components are new demand (25), velocity (20: growth of open roles once tracking spans 7+ days, before that this week's posting pace), seniority (15), department fit to your `scoreFunctions` (15), hard-to-fill share open 30+ days (10), volume (10) and remote share (5).

Example from a real run (Oct 30 2026, NVIDIA, `searchText: "sales"`, `scoreFunctions: ["sales"]`, second tracked run with no changes):

```json
{"type":"company_summary","company":"nvidia","total_open_jobs":331,"new_jobs_since_last_run":0,"changed_jobs_since_last_run":0,"closed_jobs_since_last_run":0,"is_baseline_run":false,"scan_complete":true,"signal_score":93,"signal_tier":"hot",
 "score_reasons":["+25 new demand: 131 roles posted in the last 30 days (59 in the last 7); 0 new since the last run",
  "+19 velocity: 59 postings this week vs 30.5/week 30-day average (1.9x pace)",
  "+15 seniority: 87 manager/director/executive roles (26%), 79% senior-or-above",
  "+15 department: 89 open roles in target functions (sales)",
  "+9 hard-to-fill: 200 roles open 30+ days (60%)","+9 volume: 331 open roles","+1 remote: 11% of roles are remote"]}
```

### For investors and equity research (alternative data)

Job postings are one of the most-used alternative datasets on the buy side: in Exabel's 2026 buy-side survey, 57% of fund managers named employment data as the category most likely to give an informational edge.
This Actor gives you the raw, first-party version of that signal at pay-per-event prices:

- **`ticker`, `cik`, `sec_title`** on every company summary (and `ticker` on every job), matched against SEC's public `company_tickers.json`. Matching is conservative: exact normalized name only; ambiguous or private companies stay `null`. Use `tickerMap` to set your own mapping.
- **Monitor mode** gives new jobs, closed jobs and net change per run - schedule it daily to build your own point-in-time hiring series for a watchlist.
- Typical uses: hiring momentum vs. peers before earnings, expansion into new countries (e.g. GCC hiring), AI/engineering hiring mix, hiring freezes (net change turning negative).

Data comes only from employers' own public career sites. No personal data about candidates is collected. Not investment advice.

### Built for recruiter BD

Same BD fields as the sister [ATS Jobs Monitor](https://apify.com/goldenrod_serrano/ats-hiring-signals): `fingerprint` + optional merge of duplicate title+location rows, `is_long_open` / `is_repost` (monitor mode), `names_internal_recruiter` when *Include details* is on, and `company_domain` from a non-Workday careers URL or `companyDomainMap` (never invented from the tenant name). Demand evidence: recruitment-agency BD threads on duplicate/repost/recruiter-in-JD pain, plus GTM engineers asking for daily company + job + domain feeds.

### Pricing (pay per event)

- **Job row:** charged per job pushed.
- **Company summary:** charged per site.
- Error rows and the sample run are free. Set *Max charge per run* in Apify to cap spend. The Actor stops cleanly at the cap.

### Notes and limits

- `posted_date` comes from Workday's "Posted N Days Ago" (30 means 30+) unless *Include details* is on, which gives the exact start date.
- `total_open_jobs` is Workday's own count. `total_capped_by_workday: true` means Workday reports 2,000+. Add countries or search text to get everything.
- Polite by design: 20 jobs per request, short pauses, retries on 429/5xx, at most 10 sites in parallel.
- `seniority` is a rule on the title. It is free and fast, but it is not AI. Titles with no level word come out as `mid`. `work_model` comes from Workday's remote type when the site sets it, otherwise from the location and title text.
- Closed jobs are reported from the second monitor run on, and only when the run saw the whole list: not cut by *Max scan per company* and under Workday's 2,000 cap. Otherwise the field is `null`, never a guess. It counts postings seen on a previous run that are gone now. Keep the same search and countries for each state key.
- Only public job postings are read. No candidate or personal data.

### Integrations

Also tracking Greenhouse, Lever, Ashby, Workable or SmartRecruiters boards? Use the sister Actor [ATS Jobs & Hiring Signals](https://apify.com/goldenrod_serrano/ats-hiring-signals). It has the same `seniority`, `work_model` and summary fields, so the outputs join cleanly.

Schedule the Actor and send new jobs to Google Sheets, Slack, n8n, Make, Zapier or a webhook through Apify integrations, or call it from the Apify API.

# Actor input Schema

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

One per line. Any Workday career-site URL: https://<company>.wdN.myworkdayjobs.com/<Site> (a job or search URL works too), https://wdN.myworkdaysite.com/recruiting/<company>/<Site>, 'company/wdN/Site', or a company careers page that links to its Workday site.

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

Passed to Workday's own search box, e.g. 'sales' or 'data engineer'. Fastest way to narrow big sites. Empty = all jobs.

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

Matched to the site's own location filters, so Workday filters server-side (works past the 2000-job cap). Country or city names, plus shortcuts: UAE, KSA, GCC, SEA, LATAM, UK, US. Falls back to text match on location when the site has no location filter.

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

Keep jobs whose title contains any of these. Case-insensitive. Use Search text for Workday full-text search.

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

Drop jobs whose title contains any of these (e.g. intern).

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

Keep jobs posted in the last N days (from Workday's 'Posted N Days Ago'; 30 means 30+). 0/empty = no limit.

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

Keep only jobs whose title/location says remote.

## `seniorityLevels` (type: `array`):

Keep only these levels, inferred from the job title: intern, entry, mid, senior, lead, manager, director, executive. Empty = all. Every job row has a 'seniority' field either way.

## `includeDetails` (type: `boolean`):

One extra request per kept job. Adds full plain-text description, country + ISO code, exact posting date, time type and hiring legal entity.

## `longOpenDays` (type: `integer`):

Mark is_long_open when posted_days_ago is at least this many days. Long-open / reposted roles are the classic recruiter BD entry point.

## `dedupeJobs` (type: `boolean`):

Collapse rows that share a fingerprint within the run. The kept row gets duplicate_count and duplicate_urls.

## `companyDomainMap` (type: `object`):

Map Workday tenant or input URL to a website domain, e.g. {"nvidia":"nvidia.com"}. Workday hosts are never used as the company domain.

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

Remembers job URLs per site in a named key-value store, so scheduled runs output (and charge) only newly opened jobs. First run outputs everything that matches. From the second run on, the company summary also reports closed_jobs_since_last_run and net_change_since_last_run (keep the same filters per state key).

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

Name for the saved state. Use different keys for separate watch lists (e.g. 'gcc-weekly', 'sales-roles'). Required for change tracking and for the older monitor mode.

## `changeTracking` (type: `boolean`):

Keeps one small state record per company in the named key-value store and outputs only what changed since the last run with the same Monitor state key: change_type='new' (charged as a normal job row), change_type='changed' (a tracked field - title, location, country, time type and work model - changed; charged as a job row, with changed_fields and previous_values) and free type='job_closed' rows for jobs that disappeared. First run = baseline (everything matching is 'new'). If a listing could be truncated, no closures are reported and the old state is merged, never overwritten. Every scanned job is saved as seen: maxJobsPerCompany only limits output, and capped-out jobs are never sent or charged later. Only rows stopped by your max charge are carried to the next run. Replaces 'Only new jobs since last run' when both are on.

## `signalScoring` (type: `boolean`):

Adds signal_score, signal_tier (hot/warm/cool/cold), score_reasons (one plain-English line per component, with points) and score_components to each company summary, and company_signal_score + score_reasons to each job row. Components: new demand 25, growth velocity 20 (real open-role trend when change tracking has history, else posting pace), seniority 15, department focus 15, hard-to-fill/long-open roles 10, volume 10, remote share 5. The legacy hiring_signal_score is unchanged.

## `scoreFunctions` (type: `array`):

Optional. Functions that matter to you for the 'department focus' part of the score: sales, customer_success, marketing, data_ai, engineering, product_design, finance_legal, people_ops. Empty = reward the company's biggest build-out.

## `outputJobs` (type: `boolean`):

One row per matching job (charged per job). Turn off to get only company summaries.

## `outputCompanySummary` (type: `boolean`):

One row per site: open jobs, posted in last 7/30 days, remote share, top locations/countries and a 0-100 hiring-signal score.

## `maxScanPerCompany` (type: `integer`):

Stop listing after N jobs per site (newest first). 0 = all (Workday shows at most ~2000 per query; use Countries or Search text to go deeper).

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

0 = no cap. With change tracking on, every scanned job is saved as seen: this cap only limits how many rows this run outputs. Jobs cut by the cap are not sent or charged later. For a full baseline (every matching job delivered once), set this to 0 on the first run.

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

How many sites to fetch in parallel.

## `addTickers` (type: `boolean`):

Adds ticker, cik and sec_title to each company summary (and ticker to each job) using SEC's public company_tickers.json. Conservative exact-name match: unmatched or ambiguous names stay null - no guessing. Free, no extra charge.

## `tickerMap` (type: `object`):

Optional. Overrides automatic matching, e.g. {"amazon": "AMZN", "meta": "META"}. Keys are the company name or slug as it appears in the output.

## Actor input object example

```json
{
  "careerSites": [
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://mastercard.wd1.myworkdayjobs.com/CorporateCareers"
  ],
  "locations": [
    "GCC"
  ],
  "remoteOnly": false,
  "includeDetails": false,
  "longOpenDays": 30,
  "dedupeJobs": true,
  "onlyNewSinceLastRun": false,
  "stateKey": "default",
  "changeTracking": false,
  "signalScoring": false,
  "outputJobs": true,
  "outputCompanySummary": true,
  "maxScanPerCompany": 0,
  "maxJobsPerCompany": 0,
  "maxConcurrency": 4,
  "addTickers": true,
  "tickerMap": {}
}
```

# Actor output Schema

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

Matched Workday job postings (dataset view 'jobs').

## `hiringSignals` (type: `string`):

One row per Workday site with counts and a 0-100 hiring-signal score (dataset view 'signals').

## `runSummary` (type: `string`):

Counts of sites, sites read, jobs pushed and errors.

## `changes` (type: `string`):

With change tracking on: new and changed job rows plus free job_closed rows (dataset view 'changes').

# 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://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
        "https://mastercard.wd1.myworkdayjobs.com/CorporateCareers"
    ],
    "locations": [
        "GCC"
    ],
    "tickerMap": {}
};

// Run the Actor and wait for it to finish
const run = await client.actor("goldenrod_serrano/workday-hiring-signals").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://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
        "https://mastercard.wd1.myworkdayjobs.com/CorporateCareers",
    ],
    "locations": ["GCC"],
    "tickerMap": {},
}

# Run the Actor and wait for it to finish
run = client.actor("goldenrod_serrano/workday-hiring-signals").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://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://mastercard.wd1.myworkdayjobs.com/CorporateCareers"
  ],
  "locations": [
    "GCC"
  ],
  "tickerMap": {}
}' |
apify call goldenrod_serrano/workday-hiring-signals --silent --output-dataset

```

## MCP server setup

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

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/NWaa9XOlecGzVlqxA/builds/QfUEZUcd0anfcoUJL/openapi.json
