# Workday Jobs Scraper & New-Job Monitor (`ikas/workday-jobs-monitor`) Actor

Cheap daily monitoring of Workday career sites (myworkdayjobs.com): after one baseline run, each scheduled run returns only NEW jobs (plus free CLOSED rows). Clean rows with full description, posted date, remote type and salary range when posted.

- **URL**: https://apify.com/ikas/workday-jobs-monitor.md
- **Developed by:** [Jacob](https://apify.com/ikas) (community)
- **Categories:** Jobs, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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 Scraper & New-Job Monitor

**Watch the Workday career sites of the companies you care about, and get only the new jobs each day.** You run one baseline, and after that every scheduled run returns, and bills, only postings that appeared since the last run. Jobs that disappeared come back as free `CLOSED` rows.

That makes it cheap to keep a daily watch on target employers for recruiting, job boards, sales signals or your own job hunt. You don't pay to re-download the same 2,000 jobs every morning.

Workday (`*.myworkdayjobs.com`) runs the career sites of many large employers, such as NVIDIA and Salesforce. The actor reads the same public job data the career page shows every visitor and returns clean rows with:

- **Full description** as clean text (HTML optional)
- **Real posting date** (ISO), not just "Posted 30+ Days Ago"
- **Remote type** as the employer publishes it. `isRemote` is true only for fully remote roles (hybrid or "Flexible" roles are false). `hasRemoteLocation` is true if any listed location is remote.
- Every location, plus country and ISO country code; the requisition ID, apply link, and full-time/part-time
- **Salary range, when the employer posts one** (`salaryMin`, `salaryMax`, `salaryCurrency`, `salaryPeriod`). In our test that was 14 of 50 jobs (28%), mostly US/Canada roles under pay-transparency laws.

### Monitor mode: after a baseline run, pay only for new jobs

Turn on **Only new jobs since my last run** and schedule it daily:

1. The first run returns today's jobs, labelled `FIRST_RUN`, as a baseline.
2. Every later run returns only the jobs that appeared since then (`NEW`), plus free `CLOSED` rows for postings that disappeared.

The baseline run costs the same as a normal scrape: every current job is billed once. After that, a daily run on a large employer usually returns only that day's new postings. A run with no new jobs costs only the $0.005 start fee. Seen-job IDs are kept in a named key-value store in *your* account (`stateStoreName`), so you can run separate monitors side by side.

Want everything with change labels instead? Turn on **Label every job NEW / SEEN**.

### Handles Workday's limits properly

- Workday serves 20 jobs per page (bigger pages fail), and the actor pages through them.
- Workday caps any one search at **2,000 results**. When a site has more, the actor tries to split the search by one of the site's own filters (location, job family and so on) that covers every job, and removes duplicates. This usually gets the full set, and `maxJobsPerSite` goes up to 10,000. If no filter covers every job, the run stops at 2,000 for that site and says so in `SUMMARY` (`capped: true`).
- Requests go one at a time, 1 s apart by default, with backoff on rate limits. **Realistic throughput** with descriptions on is about 1.5 s per job, or roughly 2,000 to 2,500 jobs per hour. List-only mode (descriptions off) is about 20 jobs per request. The default run timeout is 4 hours. If a run gets close to its timeout, it stops cleanly, saves monitor state, and notes `stoppedForTimeout` in `SUMMARY`.

### Input example

```json
{
  "careerSiteUrls": [
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://salesforce.wd12.myworkdayjobs.com/External_Career_Site"
  ],
  "searchText": "engineer",
  "maxJobsPerSite": 200,
  "postedWithinDays": 7,
  "remoteOnly": false,
  "locationContains": ["texas", "remote"],
  "onlyNewJobs": true
}
```

To find a company's Workday URL, open their careers page, click any job, and copy the address once it shows `myworkdayjobs.com`. Locale parts like `/en-US/` and direct job links are fine.

### Output example (one row, trimmed)

Real result from Oct 26, 2026. `changeStatus` and `firstSeenAt` appear when tracking or monitor mode is on.

```json
{
  "jobId": "JR354555",
  "title": "Commercial/Enterprise Account Executive, Salesforce Analytics",
  "company": "salesforce",
  "url": "https://salesforce.wd12.myworkdayjobs.com/External_Career_Site/job/Illinois---Chicago/...",
  "location": "Illinois - Chicago",
  "additionalLocations": ["New York - New York", "Georgia - Atlanta", "Texas - Dallas Metro - Remote"],
  "country": "United States of America", "countryCode": "US",
  "remoteType": "Office - Flexible", "isRemote": false, "hasRemoteLocation": true,
  "timeType": "Full time",
  "postedDate": "2026-10-25", "postedDaysAgo": 0,
  "salaryMin": 77550, "salaryMax": 189350, "salaryCurrency": "USD", "salaryPeriod": "year",
  "salaryText": "$77,550 - $189,350 annually",
  "descriptionText": "To get the best candidate experience, please consider applying ...",
  "changeStatus": "NEW"
}
```

There are two dataset views: **Jobs** (key columns) and **Jobs with descriptions** (all fields).

### Pricing

You pay per delivered job row, plus a small per-run start fee (see the Pricing tab). `CLOSED` rows are free. In monitor mode, the first (baseline) run bills every current job once. After that you only pay for new jobs, so daily runs cost very little. If you need a one-off bulk dump of every job, there are cheaper per-row scrapers; this one is built for ongoing monitoring. To cap spend, use *Max jobs per site* or a maximum charge on the run.

### Responsible by design

- **Public job postings only.** No logins, no candidate data, no applications.
- **robots.txt respected.** If a site's robots.txt disallows a career site, it's skipped and reported in the run summary.
- **No evasion.** If a site blocks us (403, a challenge page, redirect bursts, or repeated 429s), the actor stops that site and records why. It never rotates proxies or solves CAPTCHAs.
- **No personal data.** Recruiter and hiring-manager fields are never collected. Email addresses and phone numbers inside descriptions are replaced with `[email redacted]` / `[phone redacted]`.

### Run summary

The default key-value store record `SUMMARY` lists, per site: jobs listed, rows delivered, new and closed counts, requests made, and any site skipped or blocked (with the reason).

### More tools from Ikas

- [Bulk PageSpeed & Lighthouse Audit](https://apify.com/Ikas/bulk-pagespeed-lighthouse-audit): Core Web Vitals PASS/FAIL for many URLs or a sitemap, mobile + desktop, with the top fixes as columns.
- [Business Entity Lookup: NJ, KY, ND Secretary of State](https://apify.com/Ikas/sos-business-entity-lookup): official registry status, type and dates.

### Reliability

A canary checks a known career site every day, including listing, detail, required fields and redaction, so breaking changes get caught the same day.

# Actor input Schema

## `careerSiteUrls` (type: `array`):

One per line. Paste the company's Workday careers page, e.g. https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite (locale like /en-US/ and job links are fine). Also supports wdN.myworkdaysite.com/recruiting/TENANT/SITE.

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

Same as typing in the site's search box, e.g. 'data engineer'. Leave empty for all jobs.

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

Caps cost and run time. Each delivered job row is one billable result. With descriptions on, expect about 2,000-2,500 jobs per hour per run (polite 1 s pacing).

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

First run bills and returns current jobs once as a baseline (FIRST_RUN). Every later run with the same sites + filters returns and bills only jobs that appeared since, plus free CLOSED rows. Ideal for daily schedules.

## `trackChanges` (type: `boolean`):

Returns all matching jobs, each with changeStatus and firstSeenAt. Implied by 'Only new jobs'.

## `emitClosedJobs` (type: `boolean`):

When tracking, add a row for each job that disappeared since the last run. Not billed.

## `stateStoreName` (type: `string`):

Named key-value store in your account where seen-job IDs are kept. Use different names to keep separate monitors.

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

Keep only jobs posted in the last N days (uses the exact posting date when descriptions are fetched).

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

Keep fully remote jobs only (based on the employer's remote type; hybrid/'Flexible' roles are excluded). Most accurate with descriptions on.

## `locationContains` (type: `array`):

Case-insensitive match on the listing location text, e.g. 'texas', 'london', 'remote'.

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

One extra request per job. Turn off for a fast title/location/link list (same price per job).

## `includeDescriptionHtml` (type: `boolean`):

Adds the raw HTML next to the clean text (bigger dataset).

## `minDelaySecs` (type: `number`):

Politeness delay (minimum 0.5). The actor backs off on rate limits and stops a site that blocks it.

## `canaryMode` (type: `boolean`):

Runs a 2-job health check against a known site and fails the run if anything broke. Not billed.

## Actor input object example

```json
{
  "careerSiteUrls": [
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://salesforce.wd12.myworkdayjobs.com/External_Career_Site"
  ],
  "searchText": "engineer",
  "maxJobsPerSite": 10,
  "onlyNewJobs": false,
  "trackChanges": false,
  "emitClosedJobs": true,
  "stateStoreName": "workday-jobs-monitor-state",
  "remoteOnly": false,
  "includeDescription": true,
  "includeDescriptionHtml": false,
  "minDelaySecs": 1,
  "canaryMode": false
}
```

# Actor output Schema

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

Job rows (overview view).

## `fullResults` (type: `string`):

All fields.

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

Per-site counts: listed, delivered, new, closed, blocked.

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

// Run the Actor and wait for it to finish
const run = await client.actor("ikas/workday-jobs-monitor").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 = {
    "careerSiteUrls": [
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
        "https://salesforce.wd12.myworkdayjobs.com/External_Career_Site",
    ],
    "searchText": "engineer",
    "maxJobsPerSite": 10,
}

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

```

## MCP server setup

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

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/IhgxgI1gF4fcqrDrt/builds/cwIzZfzfXAUmNbXRM/openapi.json
