# 💼 Workday Jobs Scraper (`citrine_venus/workday-jobs-scraper`) Actor

Workday job scraper for any myworkdayjobs or myworkdaysite careers portal. Get titles, salaries, descriptions, normalised locations, skills, seniority and company data — 20+ filters, streamed live to your dataset.

- **URL**: https://apify.com/citrine\_venus/workday-jobs-scraper.md
- **Developed by:** [Data Minds](https://apify.com/citrine_venus) (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 $5.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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

<h1 align="center">💼 Workday Jobs Scraper</h1>

<p align="center">
  <b>Scrape any Workday careers portal into a clean, enriched job dataset — titles, salaries, locations, skills and full descriptions.</b><br/>
  <sub>Workday job scraper · myworkdayjobs scraper · ATS job data API · hiring intelligence feed</sub>
</p>

<p align="center">
  <img alt="Platform" src="https://img.shields.io/badge/Apify-Actor-97D700?style=for-the-badge&logo=apify&logoColor=black">
  <img alt="Source" src="https://img.shields.io/badge/Workday-myworkdayjobs%20%2B%20myworkdaysite-0875E1?style=for-the-badge">
  <img alt="Fields" src="https://img.shields.io/badge/Fields-75%20per%20job-8A2BE2?style=for-the-badge">
  <img alt="Views" src="https://img.shields.io/badge/Table%20views-7-FF7A00?style=for-the-badge">
  <img alt="Pricing" src="https://img.shields.io/badge/Pricing-pay%20per%20result-2ECC71?style=for-the-badge">
</p>

***

> ### ⚡ TL;DR
>
> **Paste a careers URL → press Start → get structured jobs.**
> Works on every public `*.myworkdayjobs.com` and `*.myworkdaysite.com` board. Handles pagination, opens each posting for the full description, normalises locations to `City, Region, Country` **with coordinates and timezone**, extracts **salary ranges**, skills, seniority, benefits and 40 job categories — and streams every row into your dataset **while the run is still going**.
>
> 📧 Custom fields, private builds, bespoke job-data pipelines → **<hello.dataminds@gmail.com>**

***

### 🧭 Pick your path

| I want to… | Jump to |
|---|---|
| 🚀 Get my first 10 jobs in a minute | [60-second start](#-60-second-start) |
| 📋 Copy a ready-made config | [Recipe book](#-recipe-book--copy-paste-configs) |
| 🔍 See exactly what a row looks like | [One job, one row](#-one-job-one-row) |
| 🧾 Look up a field or an input | [Field dictionary](#-field-dictionary) · [Input reference](#%EF%B8%8F-input-reference) |
| 🛡️ Understand blocking & proxies | [How it survives protected boards](#%EF%B8%8F-how-it-survives-protected-boards) |
| 💸 Keep the bill small | [Cost control](#-cost-control) |
| ❓ Ask a question | [Answers](#-answers) · [Fix-it table](#%EF%B8%8F-fix-it-table) |

***

### 🎯 The problem this Actor solves

Thousands of the world's biggest employers — food distribution giants, chip makers, banks, hospital networks, retailers — publish every opening on **Workday**, the applicant tracking system behind URLs like `company.wd5.myworkdayjobs.com/careers`.

Those boards are **JavaScript-driven, paginated and inconsistent between tenants**. Copying them by hand is unthinkable; a naive scraper gets half a page of titles and a location string like `Sysco Iowa - Ankeny - Distribution/Main Office` that no database can use.

**Workday Jobs Scraper closes that gap.**

| Raw careers board | What you get back |
|---|---|
| `Sysco Iowa - Ankeny - Distribution/Main Office` | `Ankeny` · `Polk County` · `Iowa` · `United States` · `41.72971, -93.60577` · `America/Chicago` |
| `"$27.42"` buried in paragraph 9 | `ai_salary_value: 27.42` · `ai_salary_currency: USD` · `ai_salary_unit_text: HOUR` |
| 6,000 words of HTML | Clean `description_text` + responsibilities + requirements summaries |
| "Full time" | `FULL_TIME` · `On-site` · seniority `0-2` · 40-category taxonomy · skills list · benefits list |
| Page 1 of 40 | Every page, deduplicated, streamed live to your dataset |

***

### 🚀 60-second start

1. Open the Actor in **[Apify Console](https://console.apify.com)** and hit **Try for free**.
2. Paste a careers URL into **🔗 Careers portal URLs** — for example
   `https://sysco.wd5.myworkdayjobs.com/syscocareers`
3. Set **📬 Jobs to collect** to `10`.
4. Press **▶ Start** and watch each job land in the log the second it is ready.
5. Open the **Output** tab and flip between the seven views, or export **JSON / CSV / Excel / XML**.

> 🎬 **Nothing else is required.** No proxy setup, no API key, no cookies, no browser profile. Every advanced option ships with a sensible default.

**Via API:**

```bash
curl -X POST "https://api.apify.com/v2/acts/YOUR_ACTOR_ID/runs?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "startUrls": [{ "url": "https://sysco.wd5.myworkdayjobs.com/syscocareers" }],
    "results_wanted": 10
  }'
```

**Via Python client:**

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("YOUR_ACTOR_ID").call(run_input={
    "startUrls": [{"url": "https://sysco.wd5.myworkdayjobs.com/syscocareers"}],
    "results_wanted": 100,
    "aiWorkArrangementFilter": ["Remote OK", "Remote Solely"],
    "hasSalary": True,
})

for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(job["title"], "—", job.get("locations_derived"), job.get("ai_salary_max_value"))
```

***

### 📋 Recipe book — copy-paste configs

Every recipe below is a complete input. Paste it into the **JSON** tab in Console, or send it as the API body.

<details open>
<summary><b>🥇 Recipe 1 — Whole board, everything enriched</b></summary>

```json
{
  "startUrls": [{ "url": "https://sysco.wd5.myworkdayjobs.com/syscocareers" }],
  "results_wanted": 500,
  "pagination": 0,
  "max_pages": 20,
  "details": true,
  "includeCompanyDetails": true,
  "companyProvider": "wikidata"
}
```

*The default sweep: every page, full descriptions, geo + salary + skills + free company intelligence.*

</details>

<details>
<summary><b>⚡ Recipe 2 — Fast listing-only sweep (cheapest, seconds not minutes)</b></summary>

```json
{
  "startUrls": [{ "url": "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite" }],
  "results_wanted": 1000,
  "details": false,
  "geocode": false,
  "outputFormat": "compact",
  "concurrency": 8
}
```

*Titles, links, companies, locations and posting dates only. Ideal for "what's new today?" monitoring.*

</details>

<details>
<summary><b>🏠 Recipe 3 — Remote roles that publish pay</b></summary>

```json
{
  "startUrls": [{ "url": "https://company.wd1.myworkdayjobs.com/External" }],
  "results_wanted": 200,
  "aiWorkArrangementFilter": ["Remote OK", "Remote Solely"],
  "hasSalary": true,
  "postedAt": "14 days"
}
```

*Salary benchmarking and remote job feeds in one shot.*

</details>

<details>
<summary><b>🧲 Recipe 4 — Multi-company talent watch</b></summary>

```json
{
  "startUrls": [
    { "url": "https://sysco.wd5.myworkdayjobs.com/syscocareers" },
    { "url": "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite" },
    { "url": "https://wd5.myworkdaysite.com/recruiting/company/External" }
  ],
  "results_wanted": 300,
  "titleSearch": ["engineer", "manager", "Data:*"],
  "titleExclusionSearch": ["intern"],
  "aiTaxonomiesFilter": ["Technology", "Engineering", "Product Management"],
  "postedAt": "7 days",
  "removeAgency": true
}
```

*Bulk input, prefix matching (`Data:*`), category filtering and agency removal.*

</details>

<details>
<summary><b>🔁 Recipe 5 — Daily change monitor (schedule it)</b></summary>

```json
{
  "startUrls": [{ "url": "https://company.wd5.myworkdayjobs.com/careers" }],
  "results_wanted": 1000,
  "postedAt": "3 days",
  "trackChanges": true,
  "persistCache": true
}
```

*Run it on a schedule; every row then carries `date_modified` and `modified_fields` showing exactly what the employer changed.*

</details>

<details>
<summary><b>🌍 Recipe 6 — Country-routed run with company filters</b></summary>

```json
{
  "startUrls": [{ "url": "https://company.wd3.myworkdayjobs.com/en-US/Careers" }],
  "results_wanted": 150,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] },
  "includeCompanyDetails": true,
  "companyProvider": "wikidata",
  "liOrganizationEmployeesGte": 5000,
  "locationSearch": ["United States", "Canada"]
}
```

*Pick your own route when a specific country view matters; company-size filters ride along.*

</details>

<details>
<summary><b>🎯 Recipe 7 — A single job, fully enriched</b></summary>

```json
{
  "startUrls": [{ "url": "https://company.wd5.myworkdayjobs.com/careers/job/Remote/Senior-Engineer_R12345" }],
  "results_wanted": 1,
  "outputFormat": "all"
}
```

*Paste a job link straight from an email or a candidate's application — you get the full record, the compact summary and the raw payload.*

</details>

***

### 🔗 Which URLs work?

| Paste this | Result |
|---|---|
| `https://company.wd5.myworkdayjobs.com/careers` | 🟢 Whole board |
| `https://company.wd1.myworkdayjobs.com/en-US/External` | 🟢 Locale boards |
| `https://wd3.myworkdaysite.com/en-US/recruiting/company/External` | 🟢 Hosted boards |
| `https://company.wd5.myworkdayjobs.com/careers?q=engineer` | 🟢 Your keyword search is reproduced |
| `https://company.wd5.myworkdayjobs.com/careers/job/Site/Title_R12345` | 🟢 That one job |
| A board that requires a login | 🔴 Not collectable — public pages only |

> 💡 **Pro move:** open the careers site in your browser, apply any filters you like, then copy the URL from the address bar. Whatever you searched, the Actor repeats.

***

### 🧾 One job, one row

```json
{
  "id": 4108616529,
  "date_posted": "2026-07-29T00:00:00",
  "date_created": "2026-07-29T13:43:34.894714",
  "title": "CDL A Local Delivery Truck Driver",
  "organization": "US0039 Sysco Iowa, Inc.",
  "locations_alt": ["Sysco Iowa - Ankeny - Distribution/Main Office"],
  "salary": "$27.42",
  "employment_type": ["Full time"],
  "url": "https://wd5.myworkdaysite.com/recruiting/sysco/syscocareers/job/.../CDL-A-Local-Delivery-Truck-Driver_R253735",
  "source": "workday",
  "source_domain": "sysco.wd5.myworkdayjobs.com",
  "organization_logo": "https://sysco.wd5.myworkdayjobs.com/syscocareers/assets/logo",

  "cities_derived": ["Ankeny"],
  "counties_derived": ["Polk County"],
  "regions_derived": ["Iowa"],
  "countries_derived": ["United States"],
  "locations_derived": ["Ankeny, Iowa, United States"],
  "timezones_derived": ["America/Chicago"],
  "lats_derived": [41.72971],
  "lngs_derived": [-93.60577],
  "domain_derived": "sysco.com",

  "ai_salary_currency": "USD",
  "ai_salary_value": 27.42,
  "ai_salary_unit_text": "HOUR",
  "ai_benefits": ["Paid time off", "Flexible schedule", "Tuition reimbursement", "Employee discounts"],
  "ai_experience_level": "0-2",
  "ai_work_arrangement": "On-site",
  "ai_key_skills": ["Leadership", "Training", "Sales", "Driving"],
  "ai_employment_type": ["FULL_TIME"],
  "ai_working_hours": 40,
  "ai_taxonomies_a": ["Transportation", "Supply Chain & Logistics", "Sales"],
  "ai_taxonomies_primary": "Transportation",
  "ai_core_responsibilities": "Sysco has immediate job openings for dependable local CDL A Delivery Truck Drivers…",
  "ai_requirements_summary": "21+ years of age. Valid Class A Commercial Driver License (CDL)…",

  "org_linkedin_name": "Sysco",
  "org_linkedin_industry": "wholesale",
  "org_linkedin_size": "10,001+ employees",
  "org_linkedin_headcount": 67001,
  "org_linkedin_headquarters": "Houston",
  "org_linkedin_founded_date": "1969",

  "date_modified": null,
  "modified_fields": null,
  "description_text": "Company:\nUS0039 Sysco Iowa, Inc.\n\nZip Code:\n50021\n\nJob Summary:\n…",

  "compact": { "title": "…", "company": "…", "requisition_id": "R253735", "apply_url": "…", "job_url": "…" },
  "raw": { "id": "R253735", "title": "…", "…": "untouched source payload" }
}
```

#### 🗂️ Seven views, seven tidy sections

The Output tab ships with prebuilt table views so you never scroll through 75 columns looking for one:

| View | Columns you see |
|---|---|
| ✨ **Job Overview** | Title · company · location · type · arrangement · posted · link |
| 🌍 **Locations & Geo** | Raw label · city · region · country · timezone · lat · lng |
| 💰 **Salary & Benefits** | Currency · min · max · flat pay · pay period · benefits |
| 🧠 **AI Insights** | Seniority · skills · categories · hours · sponsorship · language · education |
| 🏢 **Company & Employer** | Company · URL · domain · logo · industry · size · headcount · website |
| 📄 **Description & Summary** | Responsibilities · requirements · full description |
| 🧭 **Source & Change tracking** | IDs · portal · collected / posted / closing dates · changed fields |

#### 🎛️ Four row shapes

| `outputFormat` | Row contains | Use it when |
|---|---|---|
| `all` *(default)* | Full record **+** `compact` **+** `raw` | You want everything, once |
| `full` | 73 enriched fields | Analytics, dashboards, warehouses |
| `compact` | 12 essential columns | Sheets, Slack alerts, quick exports |
| `raw` | Untouched source payload | Your own parsing pipeline |

📊 A **run summary** — totals, per-portal counts and the live category breakdown — is saved in the key-value store as `run-summary`.

***

### 📚 Field dictionary

<details>
<summary><b>🆔 Identity & source</b></summary>

`id` · `url` · `title` · `organization` · `organization_url` · `organization_logo` · `source` · `source_type` · `source_domain` · `source_slug`

</details>

<details>
<summary><b>📅 Dates & change tracking</b></summary>

`date_posted` · `date_created` · `date_valid_through` · `date_modified` · `modified_fields`

</details>

<details>
<summary><b>🌍 Location & geo</b></summary>

`locations` · `locations_alt` · `location_type` · `location_requirements` · `cities_derived` · `counties_derived` · `regions_derived` · `countries_derived` · `locations_derived` · `timezones_derived` · `lats_derived` · `lngs_derived`

</details>

<details>
<summary><b>💰 Compensation</b></summary>

`salary` · `ai_salary_currency` · `ai_salary_value` · `ai_salary_min_value` · `ai_salary_max_value` · `ai_salary_unit_text` · `ai_benefits`

Sign-on bonuses, allowances and reimbursements are deliberately **not** treated as salary.

</details>

<details>
<summary><b>🧠 Role intelligence</b></summary>

`employment_type` · `ai_employment_type` · `ai_experience_level` · `ai_work_arrangement` · `ai_work_arrangement_office_days` · `ai_remote_location` · `ai_remote_location_derived` · `ai_key_skills` · `ai_keywords` · `ai_taxonomies_a` · `ai_taxonomies_primary` · `ai_education` · `ai_working_hours` · `ai_job_language` · `ai_visa_sponsorship` · `ai_core_responsibilities` · `ai_requirements_summary` · `ai_hiring_manager_name` · `ai_hiring_manager_email_address`

</details>

<details>
<summary><b>🏢 Company intelligence</b></summary>

`domain_derived` · `org_linkedin_name` · `org_linkedin_industry` · `org_linkedin_size` · `org_linkedin_headcount` · `org_linkedin_website` · `org_linkedin_headquarters` · `org_linkedin_founded_date` · `org_linkedin_description` · `org_linkedin_type` · `org_linkedin_recruitment_agency_derived` · `org_crunchbase_categories` · `org_logo_permalink`

Requires `includeCompanyDetails: true` **and** a `companyProvider` (`wikidata` is free and needs no key).

</details>

<details>
<summary><b>📄 Content</b></summary>

`description_text` (plain text or HTML, your choice) · `compact` · `raw`

</details>

***

### 🎚️ Input reference

<details open>
<summary><b>🚀 Start here</b></summary>

| Parameter | Type | Default | What it does |
|---|---|---|---|
| `startUrls` | array | — | **Required.** Careers portal URLs — bulk supported |
| `results_wanted` | integer | `20` | Hard stop on rows saved across all portals |
| `limit` | integer | `200` | Per-portal cap (200–5,000); the run stops at the lower of this and `results_wanted` |
| `pagination` | integer | `0` | Board pages per portal · `0` = all |
| `max_pages` | integer | `20` | Absolute page safety cap per portal |
| `details` | boolean | `true` | Open each posting for its full description |
| `stats` | boolean | `true` | Log + store the live category breakdown |

</details>

<details>
<summary><b>🔎 Search & keyword filters</b></summary>

| Parameter | Type | What it does |
|---|---|---|
| `titleSearch` / `titleExclusionSearch` | array | Keep / drop by title · `Data:*` = prefix match |
| `locationSearch` / `locationExclusionSearch` | array | Keep / drop by normalised `City, Region, Country` |
| `locations` | array | Loose *contains* match — great for site names like `Home Office` |
| `descriptionSearch` / `descriptionExclusionSearch` | array | Keyword in title **or** description |
| `organizationSearch` / `organizationExclusionSearch` | array | Hiring legal entity |
| `domainFilter` / `domainExclusionFilter` | array | Employer domain |

Filters combine with **AND**; values inside one filter are **OR**.

</details>

<details>
<summary><b>📅 Freshness window</b></summary>

| Parameter | Type | What it does |
|---|---|---|
| `postedAt` | string | **Posted on or after** — `2026-07-01` or `7 days` |
| `startAt` | string | **Posted on or before** — for backfilling a specific period |

⚠️ Use `postedAt` for "recent jobs". Setting only `startAt` to an old date keeps **older** postings and drops today's.

</details>

<details>
<summary><b>🧠 Smart job filters</b></summary>

| Parameter | Values |
|---|---|
| `aiEmploymentTypeFilter` | `FULL_TIME` `PART_TIME` `CONTRACTOR` `TEMPORARY` `INTERN` `VOLUNTEER` `PER_DIEM` `OTHER` |
| `aiWorkArrangementFilter` | `On-site` `Hybrid` `Remote OK` `Remote Solely` |
| `aiExperienceLevelFilter` | `0-2` `2-5` `5-10` `10+` |
| `aiTaxonomiesFilter` · `aiTaxonomiesPrimaryFilter` · `aiTaxonomiesExclusionFilter` | 40 categories — Technology · Software · Engineering · Data & Analytics · Healthcare · Finance & Accounting · Sales · Supply Chain & Logistics · Transportation · Construction & Trades · Hospitality & Food · Retail · and 28 more |
| `aiLanguageFilter` | Description language — `English`, `German`, … |
| `hasSalary` | Only postings that publish pay |
| `aiVisaSponsorshipFilter` | Only postings offering sponsorship |
| `removeAgency` | Drop staffing / recruitment agency posts |
| `hasNoLocation` | Only postings **without** a resolvable location |

</details>

<details>
<summary><b>🏢 Company intelligence</b></summary>

| Parameter | Type | Default | What it does |
|---|---|---|---|
| `includeCompanyDetails` | boolean | `false` | Attach the `org_*` fields |
| `companyProvider` | string | `none` | `wikidata` (free, no key) · `proxycurl` · `crustdata` |
| `companyApiKey` | secret | — | Your key for Proxycurl / Crustdata |
| `liIndustryFilter` / `liIndustryExclusionFilter` | array | — | Keep / drop by employer industry |
| `liOrganizationSizeFilter` | array | — | `1-10 employees` … `10,001+ employees` |
| `liOrganizationEmployeesGte` / `liOrganizationEmployeesLte` | integer | — | Headcount bounds |

</details>

<details>
<summary><b>📦 Output · 🌍 Network · ⚡ Speed · 🔧 Advanced</b></summary>

| Parameter | Type | Default | What it does |
|---|---|---|---|
| `descriptionType` | string | `text` | `text` (AI-friendly) or `html` (original markup) |
| `outputFormat` | string | `all` | `all` · `full` · `compact` · `raw` |
| `proxyConfiguration` | object | direct | Optional route; escalation is automatic when you leave it off |
| `browserFallback` | boolean | `true` | Rescue stubborn portals with a real browser session |
| `concurrency` | integer | `4` | Jobs collected in parallel (1–20) |
| `requestDelay` | number | `0.35` | Minimum gap between requests, seconds |
| `requestTimeout` | integer | `45` | Request timeout, seconds |
| `maxRetries` | integer | `3` | Attempts before the route escalates |
| `geocode` | boolean | `true` | City / region / country / coordinates |
| `timezoneLookup` | boolean | `true` | IANA timezone per location |
| `domainProbe` | boolean | `true` | Detect the employer's own domain |
| `trackChanges` | boolean | `true` | Fill `date_modified` / `modified_fields` |
| `persistCache` | boolean | `true` | Reuse resolved locations across runs |
| `llmEnrichment` · `llmApiKey` · `llmModel` | bool / string | `false` | Optional LLM pass with your own Anthropic key |
| `debugLog` | boolean | `false` | Verbose diagnostics |

</details>

***

### 🛡️ How it survives protected boards

Careers portals rate-limit, throttle and occasionally slam the door. The run adapts on its own:

```
🌐 direct connection  ──refused──▶  🛡️ datacenter route  ──refused──▶  🏠 residential route  ──▶  🧭 browser rescue
        ▲ fastest, free                      ▲ sticky from here on          ▲ 3 focused retries         ▲ last resort
```

- 🌐 **Direct first** — most portals never push back, so you pay nothing for proxies.
- 🚦 **Sticky escalation** — the moment a portal refuses, the run switches route and **stays there** for every remaining request. No flapping.
- 📣 **Fully logged** — every switch is printed plainly: *"Network fallback — direct connection was refused (HTTP 429) → switching to the datacenter route for every remaining request."* A route-change summary closes the run.
- ♻️ **Smart retries** — transient errors and `429`s back off exponentially and honour `Retry-After`.
- 🧭 **Browser rescue** — whatever is still refused gets one attempt inside a real browser session.
- 🐢 **Your politeness dials** — `requestDelay`, `requestTimeout`, `concurrency`.
- 💾 **Nothing is ever lost** — rows are written the instant they are ready, so even an aborted or migrated run keeps everything collected so far.

> ✅ **You do not need to configure a proxy.** Turn one on only when you want a specific country route.

***

### 💸 Cost control

Billing is **pay per result** — one `job_result` event per job row saved. Filtered-out postings do not add result charges.

| Lever | Effect |
|---|---|
| `results_wanted` | 🎯 The hard ceiling on rows saved — the single biggest lever |
| `details: false` | ⚡ Listing-only sweep: dramatically faster and cheaper |
| `pagination` / `max_pages` | 📄 Bound how much of the board is walked |
| `titleSearch` · `postedAt` · `locationSearch` | 🚫 Drop postings **before** their descriptions are ever opened |
| `geocode: false` | 🌍 Skip normalisation when raw labels are enough |
| `concurrency` | ⚙️ Higher finishes sooner (less compute) — be gentle with small portals |

***

### 🔌 Integrations & automation

- ⏰ **Schedules** — hourly or daily runs; `date_modified` + `modified_fields` reveal what changed.
- 🔔 **Webhooks** — ping your service the moment a run finishes.
- 🔄 **Make · Zapier · n8n** — push new jobs into Airtable, Sheets, Slack, HubSpot or your own ATS.
- 🗄️ **API access** — dataset items as JSON, JSONL, CSV, XLSX, XML or RSS.
- 🤖 **AI pipelines** — `description_text` + `ai_key_skills` + `ai_taxonomies_a` drop straight into embeddings, RAG stores, job-matching models and skill-extraction training sets.

```bash
curl "https://api.apify.com/v2/datasets/YOUR_DATASET_ID/items?token=YOUR_APIFY_TOKEN&format=csv"
```

***

### 👥 Built for

| Who | Why |
|---|---|
| 🧲 **Recruiters & staffing agencies** | Track competitor hiring, source live openings, build candidate-facing feeds |
| 🌐 **Job boards & aggregators** | Ingest thousands of employer postings with one consistent schema |
| 📈 **Talent intelligence & HR analytics** | Hiring velocity, location strategy, salary benchmarks, headcount plans |
| 💼 **Sales & GTM teams** | Hiring signals are buying signals — 30 new warehouse roles means a new facility |
| 🔬 **Labour-market researchers** | Longitudinal datasets of real, employer-published demand |
| 🤖 **AI & data teams** | Clean job text for matching, embeddings and skill graphs |
| 🧑‍💻 **Developers** | A dependable job data API with zero ATS paperwork |

***

### 💬 Answers

<details open>
<summary><b>What is the best way to scrape Workday job listings?</b></summary>

Paste the company's careers URL into this Actor and press Start. Pagination, per-job details, location normalisation, salary parsing and enrichment are handled for you, and clean rows land in a dataset you can export or query through the Apify API.

</details>

<details>
<summary><b>Which Workday URLs are supported?</b></summary>

Any public board on `*.myworkdayjobs.com` or `*.myworkdaysite.com` — including locale paths (`/en-US/…`), hosted recruiting boards, pre-filtered search URLs, and direct links to a single job.

</details>

<details>
<summary><b>Can I scrape several companies in one run?</b></summary>

Yes. `startUrls` takes unlimited URLs and supports bulk input; portals are processed one after another until `results_wanted` is reached.

</details>

<details>
<summary><b>How do I get only remote jobs that publish a salary?</b></summary>

Set `aiWorkArrangementFilter` to `["Remote OK", "Remote Solely"]` and `hasSalary` to `true` — see [Recipe 3](#-recipe-book--copy-paste-configs).

</details>

<details>
<summary><b>How do I get only jobs posted this week?</b></summary>

Set `postedAt` to `7 days`. Relative values (`3 days`, `2 weeks`, `1 month`) and exact dates (`2026-07-01`) both work.

</details>

<details>
<summary><b>Does it return salary data?</b></summary>

Yes, whenever the employer publishes it. Structured pay data is used when present; otherwise the range, currency and pay period are extracted from the description, with guards so sign-on bonuses, allowances and reimbursements are never mistaken for salary.

</details>

<details>
<summary><b>How accurate are the locations?</b></summary>

Raw labels stay in `locations_alt`, and normalised values fill `cities_derived`, `regions_derived`, `countries_derived`, `locations_derived`, plus coordinates and timezone. Internal site names like `Sysco Iowa - Ankeny - Distribution/Main Office` resolve to the real city wherever possible. Anything unresolvable is left `null` rather than guessed — and facility-style labels that carry no real city name (for example a bare state or campus name) can resolve to a same-named place elsewhere, so keep `locations_alt` if exactness matters.

</details>

<details>
<summary><b>Do I need a proxy?</b></summary>

No. Runs start direct and escalate automatically only when a portal pushes back. Configure a proxy only if you want a specific country route.

</details>

<details>
<summary><b>How do I monitor changes daily?</b></summary>

Schedule the Actor and keep `trackChanges` on. Every row then carries `date_modified` and `modified_fields` listing exactly what changed since the previous run — see [Recipe 5](#-recipe-book--copy-paste-configs).

</details>

<details>
<summary><b>Can I use the output for AI or job-matching models?</b></summary>

Yes — `description_text`, `ai_key_skills`, `ai_taxonomies_a` and `ai_experience_level` are shaped for embeddings, classification and matching pipelines.

</details>

<details>
<summary><b>What if I need a field that isn't here?</b></summary>

Email **<hello.dataminds@gmail.com>** — custom fields and private builds are available.

</details>

***

### 🛠️ Fix-it table

| Symptom | Fix |
|---|---|
| 🚫 **No jobs saved** | Loosen filters — strict `titleSearch` + `locationSearch` + dates can exclude everything. The run summary's **Top filters** line names the filter that dropped the most. |
| ⏳ **Only old jobs / nothing recent** | You probably set `startAt` (posted **on or before**) instead of `postedAt` (posted **on or after**). |
| 📉 **Fewer jobs than requested** | The board may hold fewer matches, or `pagination` / `max_pages` ended the sweep. Set `pagination: 0` and raise `max_pages`. |
| 🛑 **Run stops at 200** | `limit` is the per-portal cap and bounds the total too. Raise it when `results_wanted` exceeds 200. |
| 🏢 **Company fields empty** | Set `includeCompanyDetails: true` **and** choose a `companyProvider` (`wikidata` is free). |
| 📍 **`locations_derived` is null** | The label matched no real place — common for `Remote` or internal codes. Raw labels remain in `locations_alt`. |
| 🔒 **Portal did not answer** | The URL may be private, retired or region-locked. Open it in a browser first; login-walled boards cannot be collected. |
| 🐌 **Slow runs** | `details: false` for a listing sweep, raise `concurrency`, lower `requestDelay`. |

***

### ⚖️ Is scraping job listings legal?

This Actor collects **publicly available** job postings — the same pages any visitor can open without logging in. Scraping public data is generally lawful in the EU and the US, but how you use it is on you:

- 🚧 Do not collect data behind authentication or paywalls.
- 🤝 Respect the target site's terms and reasonable request rates.
- 🔐 Handle personal data (a recruiter's name in a posting, for instance) in line with **GDPR**, **CCPA** and local law.
- 🚫 Never use the data for spam or unlawful discrimination.

Background reading: Apify's guide on the [legality of web scraping](https://blog.apify.com/is-web-scraping-legal/). Not legal advice.

***

### 💌 Support & custom builds

<table>
<tr>
<td>🐛 <b>Bug or missing field</b></td>
<td>Open the Actor's <b>Issues</b> tab</td>
</tr>
<tr>
<td>📧 <b>Custom scrapers, private integrations, bulk job-data pipelines</b></td>
<td><a href="mailto:hello.dataminds@gmail.com"><b>hello.dataminds@gmail.com</b></a></td>
</tr>
<tr>
<td>⭐ <b>Enjoying it?</b></td>
<td>Leave a review — it genuinely helps</td>
</tr>
</table>

***

<sub><b>Related searches:</b> workday jobs scraper · workday job scraper · myworkdayjobs scraper · myworkdaysite scraper · workday careers scraper · workday ATS scraper · scrape workday job listings · job listings scraper · job postings API · job data API · careers page scraper · job board scraper · ATS job feed · hiring data extraction · recruitment data scraping · talent intelligence data · HR analytics dataset · salary data scraper · remote jobs scraper · job market research data · employer hiring signals · job aggregator feed · applicant tracking system scraper · job description extraction · job scraping API · workday job listings dataset · company hiring monitor · daily job feed automation</sub>

# Actor input Schema

## `startUrls` (type: `array`):

📝 **Required.** One or more Workday careers URLs. Works with every public shape:

• `https://company.wd5.myworkdayjobs.com/careers`
• `https://wd3.myworkdaysite.com/en-US/recruiting/company/External`
• a URL with a keyword already in it (`?q=engineer`) — the keyword is honoured
• a direct link to a single job

Add as many portals as you like — they are processed one after another. 🚀

## `results_wanted` (type: `integer`):

🎁 Hard stop on how many **job records** are saved across all portals. Perfect for quick samples or full harvests.

## `limit` (type: `integer`):

🛡️ Safety cap per careers portal (200–5,000). The run always stops at the **lower** of this value and *Jobs to collect*. Set the run memory to 1 GB or more when going above 2,000 jobs.

## `pagination` (type: `integer`):

📚 How many pages of the job board to walk through. **0** means *keep going until the portal runs out* (still bounded by the page safety cap below).

## `max_pages` (type: `integer`):

⛑️ Absolute maximum number of board pages opened per portal, even when pages are set to *all*. Keeps runtime and cost predictable.

## `details` (type: `boolean`):

✅ **Recommended.** Opens each posting to collect the full description, salary, requisition id, company, dates and everything the AI fields are built from. Turn **off** for a much faster, listing-only sweep.

## `stats` (type: `boolean`):

📊 Logs the live category / company / location counts published by each portal and stores them in the run summary — a great overview of what a company is hiring for right now.

## `titleSearch` (type: `array`):

✅ Keep only jobs whose **title** matches one of these terms. Example: `Software Engineer` · `Data:*` · `Nurse`.

## `titleExclusionSearch` (type: `array`):

🛑 Drop jobs whose **title** matches any of these terms. Example: `Intern` · `Senior:*` · `Driver`.

## `locationSearch` (type: `array`):

🗺️ Keep only jobs in these places, matched against the normalised `City, Region, Country` form. Use English names and full state names — `New York, New York, United States`, `London, England, United Kingdom`, or simply `Germany`. `New:*` prefix matching works too.

## `locationExclusionSearch` (type: `array`):

❌ Drop jobs in these places. Same format as above — no abbreviations (`NY`, `US`, `UK`).

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

🧲 Simple **contains** matching against every location on the job (raw and normalised). Great for site names or partial cities: `Ankeny` · `Home Office` · `Bavaria`.

## `descriptionSearch` (type: `array`):

⚠️ Powerful but heavy — be specific and combine it with a title filter. Keeps only jobs whose **title or description** mentions one of these terms. Example: `Python:*` · `forklift` · `CDL`.

## `descriptionExclusionSearch` (type: `array`):

🚮 Drop jobs whose **title or description** mentions any of these terms. Example: `commission only` · `night shift`.

## `organizationSearch` (type: `array`):

🏢 Large employers post under many legal entities. Keep only the ones you care about — `Sysco:*` · `FreshPoint`.

## `organizationExclusionSearch` (type: `array`):

🙅 Drop jobs posted by these entities.

## `domainFilter` (type: `array`):

🔗 Exact match against the company domain detected for each job — `sysco.com`.

## `domainExclusionFilter` (type: `array`):

🔗 Exact match, exclusion side.

## `postedAt` (type: `string`):

📆 Oldest posting date to keep. Example: `7 days` for the last week, or `2026-06-01`.

## `startAt` (type: `string`):

📆 Newest posting date to keep — useful for backfilling a specific period. Example: `2026-07-20` or `1 day`.

## `aiEmploymentTypeFilter` (type: `array`):

Keep only these employment types. Leave empty for all.

## `aiWorkArrangementFilter` (type: `array`):

Where the work happens. *Remote OK* = remote with an office available · *Remote Solely* = fully remote. Select both to catch every remote job.

## `aiExperienceLevelFilter` (type: `array`):

Years of experience the posting asks for.

## `aiTaxonomiesFilter` (type: `array`):

Keep jobs that fall into **any** of these categories.

## `aiTaxonomiesPrimaryFilter` (type: `array`):

Stricter than the list above — the job's **top** category must be one of these.

## `aiTaxonomiesExclusionFilter` (type: `array`):

⚠️ Broad by nature — a job touching any of these categories is dropped, even when it also matches a category you wanted.

## `aiLanguageFilter` (type: `array`):

Keep only descriptions written in these languages — `English`, `French`, `German`, `Spanish`…

## `hasSalary` (type: `boolean`):

💵 Keep only postings that publish pay — either a structured salary or one detected in the description.

## `aiVisaSponsorshipFilter` (type: `boolean`):

🌏 Keep only postings that clearly state sponsorship is available.

## `removeAgency` (type: `boolean`):

🚮 Drop postings that look like they come from a staffing or recruitment agency rather than the employer.

## `hasNoLocation` (type: `boolean`):

🧭 Niche option — returns only jobs whose location could not be normalised (often fully remote or multi-site roles). Do not combine with the location filters.

## `includeCompanyDetails` (type: `boolean`):

Adds `org_*` company fields to each job (industry, headcount, size, website, description, logo).

## `companyProvider` (type: `string`):

Where company facts come from. **Wikidata** is free and needs no key. *Proxycurl* and *Crustdata* are premium APIs and need your own key below.

## `companyApiKey` (type: `string`):

Only needed for Proxycurl or Crustdata. Stored encrypted.

## `liIndustryFilter` (type: `array`):

Exact, case-sensitive match against the company industry — needs a company data source.

## `liIndustryExclusionFilter` (type: `array`):

Exact, case-sensitive exclusion — needs a company data source.

## `liOrganizationSizeFilter` (type: `array`):

Keep only companies in these headcount buckets — needs a company data source.

## `liOrganizationEmployeesGte` (type: `integer`):

Keep only companies with at least this many employees — needs a company data source.

## `liOrganizationEmployeesLte` (type: `integer`):

Keep only companies with at most this many employees — needs a company data source.

## `descriptionType` (type: `string`):

How the job description is stored — clean readable **text** (great for AI) or the original **HTML** (great for re-publishing).

## `outputFormat` (type: `string`):

**All** = rich record + `compact` summary + `raw` source payload · **Full** = rich record only · **Compact** = 12 essential columns · **Raw** = untouched source payload.

## `proxyConfiguration` (type: `object`):

🌐 **Optional — off by default.** Fallback handling is automatic. Turn this on only when you need a specific route or country (for example to see country-specific job boards).

## `browserFallback` (type: `boolean`):

🪄 If a page still refuses every network route, open it in a real browser session instead of giving up. Slower, but rescues stubborn portals. Recommended to leave **on**.

## `concurrency` (type: `integer`):

🔄 How many job postings are collected at the same time. Higher = faster; be kind to small portals.

## `requestDelay` (type: `number`):

🌊 Minimum gap before each request, e.g. `0.35`. Use `0` to go as fast as the other limits allow.

## `requestTimeout` (type: `integer`):

⏲️ How long to wait for a slow portal before retrying.

## `maxRetries` (type: `integer`):

♻️ Total attempts before a request is escalated to the next network route (`3` = one try plus two retries, with exponential backoff). `429` responses honour `Retry-After`.

## `llmEnrichment` (type: `boolean`):

When **on** (and a key is supplied) each description is also read by the model; anything it cannot confirm falls back to the built-in extraction.

## `llmApiKey` (type: `string`):

Your own key, used only for this run. Stored encrypted. Leave empty to keep the free built-in extraction.

## `llmModel` (type: `string`):

Model id used for AI enrichment.

## `geocode` (type: `boolean`):

🗺️ Turns raw site labels (`Sysco Iowa - Ankeny - Distribution/Main Office`) into clean `city, region, country` values with coordinates and timezone. Turn off for the fastest possible run.

## `timezoneLookup` (type: `boolean`):

Adds the IANA timezone for every resolved location.

## `domainProbe` (type: `boolean`):

Works out the employer's own website domain for each portal (used by the domain filters).

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

📌 Remembers what each job looked like last time and fills `date_modified` + `modified_fields` when a posting changes. Great for daily monitoring.

## `persistCache` (type: `boolean`):

Keeps resolved locations between runs so repeat runs are faster and cheaper.

## `debugLog` (type: `boolean`):

📣 Drops the run log to DEBUG level — noisier, but useful when something looks wrong.

## Actor input object example

```json
{
  "startUrls": [
    "https://sysco.wd5.myworkdayjobs.com/syscocareers"
  ],
  "results_wanted": 20,
  "limit": 20,
  "pagination": 0,
  "max_pages": 20,
  "details": true,
  "stats": true,
  "titleSearch": [],
  "titleExclusionSearch": [],
  "locationSearch": [],
  "locationExclusionSearch": [],
  "locations": [],
  "descriptionSearch": [],
  "descriptionExclusionSearch": [],
  "organizationSearch": [],
  "organizationExclusionSearch": [],
  "domainFilter": [],
  "domainExclusionFilter": [],
  "aiLanguageFilter": [],
  "hasSalary": false,
  "aiVisaSponsorshipFilter": false,
  "removeAgency": false,
  "hasNoLocation": false,
  "includeCompanyDetails": false,
  "companyProvider": "none",
  "liIndustryFilter": [],
  "liIndustryExclusionFilter": [],
  "descriptionType": "text",
  "outputFormat": "all",
  "proxyConfiguration": {
    "useApifyProxy": false
  },
  "browserFallback": true,
  "concurrency": 4,
  "requestDelay": 0.35,
  "requestTimeout": 45,
  "maxRetries": 3,
  "llmEnrichment": false,
  "llmModel": "claude-opus-5",
  "geocode": true,
  "timezoneLookup": true,
  "domainProbe": true,
  "trackChanges": true,
  "persistCache": true,
  "debugLog": false
}
```

# Actor output Schema

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

One row per job posting, saved the moment it is ready.

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

Totals, per-portal counts and the live category breakdown for this run.

# 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 = {
    "startUrls": [
        "https://sysco.wd5.myworkdayjobs.com/syscocareers"
    ],
    "results_wanted": 20,
    "titleSearch": [],
    "titleExclusionSearch": [],
    "locationSearch": [],
    "locationExclusionSearch": [],
    "locations": [],
    "descriptionSearch": [],
    "descriptionExclusionSearch": [],
    "organizationSearch": [],
    "organizationExclusionSearch": [],
    "domainFilter": [],
    "domainExclusionFilter": [],
    "postedAt": "",
    "startAt": "",
    "aiLanguageFilter": [],
    "liIndustryFilter": [],
    "liIndustryExclusionFilter": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("citrine_venus/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 = {
    "startUrls": ["https://sysco.wd5.myworkdayjobs.com/syscocareers"],
    "results_wanted": 20,
    "titleSearch": [],
    "titleExclusionSearch": [],
    "locationSearch": [],
    "locationExclusionSearch": [],
    "locations": [],
    "descriptionSearch": [],
    "descriptionExclusionSearch": [],
    "organizationSearch": [],
    "organizationExclusionSearch": [],
    "domainFilter": [],
    "domainExclusionFilter": [],
    "postedAt": "",
    "startAt": "",
    "aiLanguageFilter": [],
    "liIndustryFilter": [],
    "liIndustryExclusionFilter": [],
}

# Run the Actor and wait for it to finish
run = client.actor("citrine_venus/workday-jobs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    "https://sysco.wd5.myworkdayjobs.com/syscocareers"
  ],
  "results_wanted": 20,
  "titleSearch": [],
  "titleExclusionSearch": [],
  "locationSearch": [],
  "locationExclusionSearch": [],
  "locations": [],
  "descriptionSearch": [],
  "descriptionExclusionSearch": [],
  "organizationSearch": [],
  "organizationExclusionSearch": [],
  "domainFilter": [],
  "domainExclusionFilter": [],
  "postedAt": "",
  "startAt": "",
  "aiLanguageFilter": [],
  "liIndustryFilter": [],
  "liIndustryExclusionFilter": []
}' |
apify call citrine_venus/workday-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=citrine_venus/workday-jobs-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/1ZhEZmpCsZlbSwi5y/builds/IzKX5Ge96kByAFcn5/openapi.json
