Workday Jobs Scraper — Company Job Boards
Pricing
from $1.45 / 1,000 posting saveds
Workday Jobs Scraper — Company Job Boards
Every open role on any company's Workday career site (myworkdayjobs.com) as structured data: title, every location with its country, work arrangement, time type, posted date, pay where the posting states it, full text on request. Incremental mode charges only for what changed. No personal data.
Pricing
from $1.45 / 1,000 posting saveds
Rating
0.0
(0)
Developer
Adderley Data
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
6 hours ago
Last modified
Categories
Share
What does Workday Jobs Scraper do?
Workday Jobs Scraper reads every open role on any company's public Workday career site and returns it as structured data you can load straight into a spreadsheet, a database or a model. Many large employers run their careers page on Workday; if the job list lives at an address ending in myworkdayjobs.com, this Actor reads it.
Give it the link to the company's careers page (https://acme.wd5.myworkdayjobs.com/External), and for each posting you get the title, every location it lists with the country Workday records for it, the work arrangement and time type in the company's own terms, the date the posting started, the employing organisation, the pay range where the posting's text states one, and a link to the posting — and, if you ask for it, the full text of the posting.
What makes it different:
- Workday's own JSON, not scraped pages. The Actor reads the same public JSON a company's careers page loads in any visitor's browser: the job list 20 postings per request, then each saved posting's own record for the facts the list leaves out. No HTML parsing, no browser, nothing to break when a careers page is restyled.
- Every posting, not just the first 2,000. Workday's list stops at 2,000 postings per search. When a career site is bigger than that, the Actor splits the search by one of the site's own filters (usually job category) and reads each part.
- The site's own filters. Location and job-category filters use the career site's own filter values, the same ones its careers page offers, so a posting listed as "3 Locations" is found when one of its places matches.
- Incremental mode. Put the Actor on a schedule and each run returns only postings that are new, changed, back again or gone. Unchanged postings are skipped, not charged and not read a second time.
- One stable schema. Every row has every field, every time. Unknown is
null, never a missing key, so nothing downstream breaks on a sparse posting. The schema is versioned (job.v1) and every Adderley Data jobs Actor uses it, so a Workday career site, a Greenhouse board and a job board sit in the same table. - No personal data. Workday's public records have no recruiter fields and this Actor has nowhere to put one. Contact details inside posting text are redacted by default.
- Bounded cost. You set a maximum number of results; the run stops there. It also stops at the spending limit you set on the run in Apify.
What Workday data can you extract?
| Field | What it holds |
|---|---|
id | Stable across runs: source:market:sourceJobId. Use it as your primary key. |
title | Job title as listed. |
company.name | The hiring company, where the listing names one. |
advertiser.name | The business that placed the listing — often a recruitment agency. Never a person. |
location.raw | Location text as listed. Several locations are joined with |. |
location.suburb | Suburb, when the listing states one. |
location.city | City or area, when the listing states one. |
location.region | State or region, e.g. VIC. |
location.postcode | Postcode, when the source provides it. |
location.country | ISO 3166-1 alpha-2 country code. |
workArrangement | on_site, hybrid, remote or unknown. |
employmentTypes | Normalised: full_time, part_time, contract, casual, temporary, internship, volunteer. |
salary.raw | The salary text exactly as shown, or null when the listing shows none. |
salary.min | Lower bound as a number, when the text contains one. |
salary.max | Upper bound as a number. Equal to min for a single figure. |
salary.currency | ISO 4217. Taken from the text, otherwise the market default. |
salary.period | hour, day, week, month or year; null when the text does not say. |
salary.includesSuper | true / false when the text says so ("plus super", "inc. super"); otherwise null. |
classifications | The source's category and subcategory pairs. |
teaser | The short summary shown on the results page. |
bulletPoints | Selling points shown on the results page. |
postedAt | When the listing was posted, ISO 8601 UTC. |
updatedAt | The source's own last-modified time, ISO 8601 UTC. Published by ATS and API sources; null where the site does not show one. |
expiresAt | Expiry, ISO 8601 UTC, where the source states one. |
isPromoted | true for paid placements. A listing shown both promoted and organic is returned once. |
url | Link to the listing. |
description | Null unless requested. text, optional sanitised html, and contactsRedacted. |
changeType | Incremental runs: NEW, UPDATED, REAPPEARED, EXPIRED (or UNCHANGED if you ask for those). Otherwise null. |
firstSeenAt | Incremental runs: when this monitor first saw the listing. |
contentHash | SHA-256 over the fields that define a change. Compare it to detect edits yourself. |
scrapedAt | When this row was produced, ISO 8601 UTC. |
source | Source key, e.g. seek. |
market | Market key, e.g. au, nz. |
sourceJobId | The source's own identifier for the listing. |
company.sourceCompanyId | The source's identifier for the company, when exposed. |
company.url | The company's page on the source site, when exposed. |
advertiser.sourceAdvertiserId | The source's identifier for the advertiser. |
schemaVersion | Always job.v1. Breaking changes ship as job.v2 in a new Actor version, never silently. |
How Workday's fields fill the schema:
company.nameis the employing organisation as the posting names it, often a legal entity with the company's own internal code in front (2100 Example Robotics USA). Where the posting names none, where its own record could not be read, and in rows markedEXPIRED, it is the career site's Workday tenant name, the only other name the source gives.company.sourceCompanyIdis always the tenant.location.rawlists every location the posting names, the primary one first, joined with|.location.countryis the ISO code Workday records for the posting's requisition location; where it gives none, the country the location label states, never a city's country.location.cityandlocation.regionare filled only when the label's order is clear from a country at one end (USA, CA, Pleasanton,Pune, India); location labels are each company's own, and one such asAUS.NSW.Sydneygives no city.workArrangementcomes from the company's remote-type label: labels that say remote, hybrid or on-site are read as such; a label that pairs the office with flexibility (Office - Flexible) is read as hybrid, and so isFlex(the label Workday's own career site uses for time split between office and home). Any other label, or none, falls back to a location that says "Remote"; otherwiseunknown.employmentTypescomes from Workday's time type (Full time,Part time). Where the posting gives none, a title that says "Intern" or "Contractor" is taken at its word.postedAtis the posting's start date as Workday gives it (a date without a time), at 00:00 UTC. The list's "Posted 3 Days Ago" is never turned into a date.expiresAtis the posting's end date where the career site publishes one; otherwisenull.salaryis read from the posting's text where it states a range: Workday's public records have no pay field. Where no range is stated, the fields arenull; nothing is estimated.classificationsis always empty: neither the list nor a posting's record names a department. Use the Job categories filter to select by category.updatedAtis alwaysnull: Workday does not publish when a posting was last edited.marketis alwaysglobal: a Workday career site is the company's, not a country's.
How much does it cost to scrape Workday career sites?
You pay per posting saved to your dataset — $1.75 per 1,000 postings on Apify's Starter plan — plus $0.005 each time a run starts. There is no monthly rental.
| Apify plan | Price | Per listing |
|---|---|---|
| Free | $1.75 per 1,000 listings | $0.00175 |
| Starter (Bronze) | $1.75 per 1,000 listings | $0.00175 |
| Scale (Silver) | $1.60 per 1,000 listings | $0.00160 |
| Business (Gold) | $1.45 per 1,000 listings | $0.00145 |
Plus $0.005 per run start. Compute and proxy are included in these prices.
| What you run | Cost (USD, Starter plan) |
|---|---|
| 100 listings, one run | $0.18 |
| 1,000 listings, one run | $1.75 |
| 10,000 listings, one run | $17.50 |
| 50,000 listings, one run | $87.50 |
| A daily incremental monitor finding about 150 new or changed listings a day, for a month | $8.03 |
What a run requests, so you can plan: one request per 20 postings for the list, plus one per saved posting for its facts, at the engine's polite 90 requests a minute. A career site of 1,000 postings is about 1,050 requests, which takes around twelve minutes; the first 20 postings take well under a minute. Descriptions cost nothing extra, because they come from the same request as the posting's facts. In incremental mode an unchanged posting needs no request of its own: only the list is read for it, and it is not charged.
How to scrape a Workday career site
- Find the company's careers page. On its website, follow "Careers" or "Jobs" until you reach the job list: its address looks like
https://acme.wd5.myworkdayjobs.com/External, sometimes with a language code such as/en-US/before the site name. Copy the whole address. A company name alone is not enough: the tenant (acme), the wd number (wd5) and the site name (External) all come from the link. - Open the Actor in Apify Console and go to the Input tab. Paste one link per line into Career sites. Up to 500 per run.
- Optionally filter: Search keywords, Locations, Job categories, Posted within (days).
- Set Maximum results. This is also your cost cap.
- Press Start. When the run finishes, open the Output tab and export as JSON, CSV, Excel, XML or HTML, or read the dataset through the Apify API.
A posting that matches more than one keyword or filter is returned once.
Input
| Field | Type | Default | What it does |
|---|---|---|---|
boards | array | — | One link per career site: the address of the company's Workday careers page, e.g. https://acme.wd5.myworkdayjobs.com/External. A link to one of its postings, or one with a language code such as /en-US/, works too. The tenant (acme), the wd number (wd5) and the site name (External) all come from the link, so a company name alone is not enough: open the company's careers page and copy its address. Up to 500 career sites per run. |
keywords | array | — | Each keyword is one search of each career site with Workday's own search box, which matches the title, the location, the requisition number and the posting text, so "engineer" also finds postings that mention engineering without the word in the title. Leave empty for every posting. |
locations | array | — | Keep postings in these places, using the career site's own location filter: a country ("Australia"), a city or office as the site names it ("Melbourne"), or a remote type ("Remote", "Hybrid", "On-site"). A posting in several places is kept if any of them matches. Leave empty for all locations. |
departments | array | — | Keep postings in a job category whose name contains this text, e.g. "Engineering", using the career site's own category filter. Leave empty for all categories. |
postedWithinDays | integer | — | Keep postings the career site lists as posted in the last N days, from 1 to 30. Workday states a posting's age in days only up to 30, so a posting listed as posted "30+ days ago" is left out by any value. Leave empty for any time. |
maxResults | integer | 100 | The run stops once this many postings are saved. You are charged per posting saved, so this is also your cost cap. |
includeDescription | boolean | false | On: the full posting text comes back with each row. It is read from the same request as the posting's date and country, so it costs no extra requests. Off: listing fields only. |
descriptionFormat | text, text_and_html | "text" | Plain text, or plain text plus sanitised HTML. |
redactContacts | boolean | true | On by default: email addresses and phone numbers inside description text are replaced with [redacted]. This Actor never outputs recruiter names or contact fields. |
incremental | boolean | false | Remember what earlier runs saw and save only postings that are new, changed or gone. Unchanged postings are skipped, not charged and not read a second time. Put the Actor on a schedule with this on. |
stateKey | string | — | Optional name for this monitor, e.g. "competitor-engineering". Runs with the same key share memory. Left empty, a key is derived from the career sites and filters themselves. |
emitExpired | boolean | true | Incremental mode only. When a complete run no longer finds a posting it saw before, save one row with changeType EXPIRED. |
emitUnchanged | boolean | false | Incremental mode only. Saves (and charges for) every posting, labelled UNCHANGED where nothing moved. |
proxyConfiguration | object | {"useApifyProxy":true} | Apify Proxy, automatic group, is the default and is what this Actor is tested with. |
maxConcurrency | integer | 4 | Parallel requests. The default is deliberately modest. |
maxRequestsPerMinute | integer | 90 | An upper bound on request rate across the whole run. |
A typical input:
{"boards": ["https://workday.wd5.myworkdayjobs.com/Workday"],"maxResults": 100}
How the filters work:
- Search keywords are not a title filter. Each keyword is one search of each career site with Workday's own search box, which matches the title, the location, the requisition number and the text of the posting, so it returns more than a title match would.
- Locations and Job categories are matched against the career site's own filter values. A country name picks that country, "Remote", "Hybrid" or "On-site" picks the matching remote types, and any other text picks every location whose name contains it. A category picks every job category whose name contains the text. Asking for a location or a category costs one extra request per career site and keyword, to read the site's filter values. Text the career site has no filter value for returns nothing from that site.
- Posted within (days) reads the age the list states ("Posted 7 Days Ago"). Workday states days only up to 30, so the filter runs from 1 to 30 and a posting listed as "30+ days ago" is always left out.
Output
One row per posting. This is a synthetic example in the exact shape the Actor returns:
{"schemaVersion": "job.v1","id": "workday:global:example-freight/Data-Analyst_R-0001","source": "workday","market": "global","sourceJobId": "example-freight/Data-Analyst_R-0001","url": "https://example-freight.wd3.myworkdayjobs.com/Careers/job/Australia-VIC-Melbourne/Data-Analyst_R-0001","title": "Data Analyst","company": {"name": "AU10 Example Freight Pty Ltd","sourceCompanyId": "example-freight","url": "https://example-freight.wd3.myworkdayjobs.com/Careers"},"advertiser": {"name": "AU10 Example Freight Pty Ltd","sourceAdvertiserId": "example-freight"},"location": {"raw": "Australia, VIC, Melbourne | Australia, NSW, Sydney","suburb": null,"city": "Melbourne","region": "VIC","postcode": null,"country": "AU"},"workArrangement": "hybrid","employmentTypes": ["full_time"],"salary": {"raw": "The salary range for this role is A$95,000 – A$110,000 per year plus super.","min": 95000,"max": 110000,"currency": "AUD","period": "year","includesSuper": false},"classifications": [],"teaser": null,"bulletPoints": [],"postedAt": "2026-09-20T00:00:00.000Z","updatedAt": null,"expiresAt": null,"isPromoted": false,"description": null,"changeType": "NEW","firstSeenAt": "2026-09-21T19:30:12.000Z","contentHash": "d9301f0d4cef8460be11e8fd3ef437e6032ba388525e93b476bed6fd1eb142a0","scrapedAt": "2026-09-21T19:30:12.000Z"}
The Output tab has two table views: Overview (the fields most people want, flattened) and Changes (for incremental runs).
Incremental mode: monitor new roles across companies
Turn on Incremental mode and run the same input on a schedule — hourly, daily, weekly. The Actor keeps a small record of what it has seen and every row tells you what happened:
changeType | Meaning |
|---|---|
NEW | First time this monitor has seen the posting |
UPDATED | Seen before, and its title, its location as the list shows it, its remote type or its requisition number has changed |
REAPPEARED | Was reported as expired and is back |
EXPIRED | Seen before and no longer on the career site. One row, once |
UNCHANGED | Only if you turn on Also save unchanged postings |
How it behaves, so there are no surprises:
- The first run returns everything as
NEW. From the second run you pay only for the difference. - Change detection runs on the list's title, location, arrangement and requisition id, before any posting's own record is read — that is what keeps an unchanged posting free. So an edit only to a posting's text is not reported as
UPDATED, and neither is a day passing ("Posted 3 Days Ago" becoming "Posted 4 Days Ago"). EXPIREDis only ever reported by a complete run. If a run hits your result cap or your spending limit, or a career site cannot be read, nothing is declared expired — a posting on a site the run never read is not gone.- Workday's list is read page by page, so a career site can change while it is being read. When fewer postings come back than the site reported, the Actor reads that search once more from the start; postings it already has are skipped, not charged twice.
- Runs share memory when they share a State key. Leave it empty and the key is derived from the career sites and filters themselves, so the same input always continues the same monitor. Name it (
competitor-engineering) if you want to change filters later without starting again. - A posting not seen for 45 days is forgotten.
Descriptions and contact details
Full descriptions are off by default. Turn on Include full descriptions and each row carries description.text (and sanitised description.html if you choose that format): the posting's text as Workday serves it. It comes from the request each posting's facts are read from anyway, so it costs no extra requests.
Postings sometimes contain a recruiter's email address or phone number. With Redact contact details on — the default — those are replaced with [redacted] and description.contactsRedacted is true; so is a personal profile address (linkedin.com/in/…). Neither description.text nor the optional HTML carries the address behind a link: the HTML keeps each link's words and drops its address, and drops images. The Actor never returns recruiter names or contact details as fields, under any setting: Workday's public records have no such fields, the application-form references they carry are never read, and the schema has nowhere to put one. If your use case is contacting individuals, this is the wrong tool.
What people use it for
- Competitor and market hiring signals. Which companies are opening which roles, in which countries and cities, and how often — a daily monitor across a list of career sites is one scheduled run.
- Enterprise hiring coverage. Workday career sites belong to large employers; a few links cover a large share of a sector's openings.
- Job aggregators and alert products. A clean feed of new postings from a curated list of employers, deduplicated and labelled by change.
- Sales and partnership research at company level. Growth signals from hiring, without collecting anything about the individuals involved.
- Research and teaching. A clean, repeatable dataset with a documented schema.
Using the API
Run it from code with the Apify client, using your own API token:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('adderleydata/workday-jobs-scraper').call({"boards":["https://workday.wd5.myworkdayjobs.com/Workday"],"maxResults":100});const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items.length, items[0]?.location);
import osfrom apify_client import ApifyClientclient = ApifyClient(os.environ["APIFY_TOKEN"])run = client.actor("adderleydata/workday-jobs-scraper").call(run_input={"boards":["https://workday.wd5.myworkdayjobs.com/Workday"],"maxResults":100})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["title"], item["location"]["raw"], item["postedAt"])
Schedules, webhooks and the Make, Zapier, n8n and Google Sheets integrations all work the way they do for any Apify Actor. The Actor runs with limited permissions and is priced per event, so AI agents can call it through Apify's MCP server as well.
Is it legal to scrape Workday career sites?
The Actor reads the public JSON that each company's Workday careers page loads in any visitor's browser — logged out, with no account, no key and no rate tricks — and returns facts about job postings. It does not log in, does not solve CAPTCHAs, does not work around any block, and does not collect personal information.
What you do with the data is your responsibility. Each company's postings are its own copyright — analyse them, do not republish them. If your project touches personal information, privacy law applies to you wherever you are. This is general information, not legal advice.
Questions
Where do I find a company's career-site link? On the company's website, follow "Careers" until you reach the list of jobs, then copy the address from the browser. It ends in myworkdayjobs.com/<site name>, sometimes with a language code such as /en-US/ before the site name. A link to one of the company's postings works too.
Why can't I just give the company name? Workday gives each customer a tenant on one of several numbered hosts (wd1, wd5, wd12 and others) and one or more career sites with names the company chose. Nothing public maps a company name to those three, so the link is the only reliable way in.
A career site I gave came back as "not found". Workday answers "not found" when the site name does not exist for that tenant, and refuses a tenant that is not on the wd host in the link: a company on wd5 is not found on wd1. The run log names the career site, and the others in the run are unaffected. Copy the link again from the company's careers page. A missing career site is asked for once, not retried, and a run in which every career site is missing finishes with an empty dataset and a message naming each one.
Why does a run take longer than for a Lever or Greenhouse board? Workday's list carries only the title, a location and the posting's age. The date, the country, the time type and the text come from each posting's own record, one request per posting saved, at the polite rate of 90 requests a minute.
What happens with more than 2,000 postings? Workday's list stops at 2,000 per search. The Actor splits a bigger search by one of the career site's own filters and reads each part. If a single part still holds more than 2,000 postings and no other filter divides it, the first 2,000 of that part are read.
Why is salary often null? Workday's public records have no pay field. Where a posting's text states a range — common where pay-transparency laws apply — it is read from that sentence and kept in salary.raw.
Does it need a Workday login or API key? No. The career site's JSON is public.
Can I get recruiter emails or phone numbers? No, by design.
How current is the data? It is read from Workday while your run is in progress. postedAt is the posting's start date; scrapedAt records when the row was produced.
The field I need is not there. Open an issue on the Issues tab. Fields are added to the schema without breaking existing ones.
Support
Use the Issues tab on this page. We read it every day. Include the run ID and what you expected to see.
Other Adderley Data Actors
Every Actor in a vertical returns the same fields, so adding a source needs no new code on your side.
- Ashby Jobs Scraper — Company Job Boards — same
job.v1fields - Breezy HR Jobs Scraper — Company Job Boards — same
job.v1fields - Greenhouse Jobs Scraper — Company Job Boards — same
job.v1fields - Lever Jobs Scraper — Company Job Boards — same
job.v1fields - Recruitee Jobs Scraper — Company Job Boards — same
job.v1fields - Rippling Jobs Scraper — Company Job Boards — same
job.v1fields
About
Made by Adderley Data, Melbourne — https://adderleydata.com. Not affiliated with, endorsed by or sponsored by Workday, Inc. Workday is a trade mark of its owner and is used here only to describe what this Actor reads.