Workday Jobs API
Query jobs from Workday tenants through one API — and what the Workday CXS endpoint really costs if you build the scraper yourself.
Workday is the HR platform most large enterprises run on, which makes it the ATS
behind a very large share of the jobs you actually want and the least pleasant one
in this pool to read. Every customer is a tenant with its own host and its own
career site: nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite. There is no
global index, no shared API host, and no single endpoint that serves more than one
company. Our queue tracks 7,586 Workday boards, and every one of them was found
individually.
Try it#
curl -G https://api.hyperjobs.io/v1/jobs \
-H "Authorization: Bearer $HYPERJOBS_KEY" \
--data-urlencode "source=workday" \
--data-urlencode "time_frame=7d" \
--data-urlencode "limit=2"{
"data": [
{
"id": "workday:globex_R-007754",
"title": "Principal Firmware Engineer",
"company": {
"slug": "globex",
"name": "Globex Corporation",
"domain": "globex.com",
"website": "https://globex.com",
"logo": "https://globex.wd5.myworkdayjobs.com/wday/cxs/…",
"industry": "Semiconductors",
"employee_count": 24000
},
"location": {
"remote": false,
"countries": ["United States"],
"cities": ["Maryville"],
"regions": ["Tennessee"],
"raw": ["Maryville, TN 37801, United States of America"]
},
"employment_type": ["FULL_TIME"],
"work_arrangement": "On-site",
"seniority": null,
"salary": null,
"skills": ["c", "rtos", "embedded"],
"taxonomies": ["Software"],
"apply_url": "https://globex.wd5.myworkdayjobs.com/en-US/GlobexCareers/job/Maryville/Principal-Firmware-Engineer_R-007754",
"url": "https://globex.wd5.myworkdayjobs.com/en-US/GlobexCareers/job/Maryville/Principal-Firmware-Engineer_R-007754",
"source": "workday",
"posted_at": "2026-07-06T00:00:00.000Z",
"expires_at": "2026-08-30T00:00:00.000Z",
"updated_at": "2026-07-12T04:41:52.000Z"
}
],
"total": 2,
"limit": 2,
"offset": 0
}salary being null there is not an omission — see below. Workday genuinely does
not publish one.
How Workday boards actually work#
Workday's career sites are backed by a JSON API called CXS. It takes two steps, and the first one is a POST.
# 1. List — POST, paginated, no descriptions
curl -X POST "https://<host>/wday/cxs/<tenant>/<site>/jobs" \
-H "content-type: application/json" \
-d '{"limit":20,"offset":0,"searchText":""}'
# 2. Detail — GET, one per posting, this is where the description is
curl "https://<host>/wday/cxs/<tenant>/<site><externalPath>" \
-H "accept: application/json"Three identifiers, not one#
You cannot derive a Workday board from a company name. You need three things, and all three come out of the careers URL:
From https://nvidia.wd5.myworkdayjobs.com/en-US/NVIDIAExternalCareerSite | |
|---|---|
| tenant | nvidia |
| host | nvidia.wd5.myworkdayjobs.com — note the wd5, it varies per customer |
| site | NVIDIAExternalCareerSite — arbitrary, employer-chosen |
The wd5 is a Workday instance number and differs between tenants; there's no
rule that gets you from a company to its instance. The site name is whatever the
employer typed. An optional locale segment (en-US) sits between them and has to
be skipped. Miss any of the three and you have nothing — this is the "finding the
board is harder than fetching it" problem in its purest form.
The list has no descriptions#
Every posting needs its own detail call. jobPostingInfo.jobDescription holds the
full HTML, and it exists nowhere else. There is no content=true equivalent, no
bulk mode, no feed. A tenant with 6,000 postings costs 6,000 requests plus 300
list pages, and the page size is 20.
What we do about the N+1
Two things. The list call reports the complete live key set first, so board diffing and expiry work off the cheap call. Then only postings we've never seen get a detail fetch. A re-scrape of an unchanged 6,000-job tenant costs the list pages and nothing else.
Department and workplace type only exist as facets#
This is the strangest part of the platform. Workday's job detail exposes no
department field. But the list response carries facets[], and each job
belongs to exactly one value of each facet. So you recover per-job department by
re-querying the list once per facet value with appliedFacets and tagging every
returned job's req id. Every job appears in exactly one partition, so this fetches
the same job set again, partitioned — not more jobs, just more requests.
The department facet's parameter name is tenant-configurable. jobFamilyGroup
is the common one, but tenants ship jobCategory, a custom field with
Department in the name, or a bare jobFamily instead. Look for only the common
one and those tenants come back with zero departments and no error.
The workplace-type facet (remoteType / isRemoteType / workplaceType) is
exposed by a minority of tenants — roughly one in five — but where it exists it
tags every job, which matters because the per-detail remoteType is carried by
only about 30% of postings. Measured on one university tenant: On-Site 1,290,
Hybrid 138, Remote 53.
What bites you#
Req ids are only unique within a tenant
Two different companies will both have an R-12345. A key of
workday:<reqId> collides across tenants and silently overwrites one company's
job with another's. The key has to be scoped — ours is
workday:<tenant>_<reqId>, which is why the ids on this source look like
workday:globex_R-007754.
Pagination that stops at 200 will silently truncate. The page size is 20 and
the loop must run to end-of-list — a short page, or offset + limit >= total. A
cap set for safety becomes a data bug: a tenant with ~6,000 postings quietly
yielding 200 of them, with a 200 on every request.
hiringOrganization.name is a legal entity, not a brand. Retail, staffing and
grocery tenants put a per-store or per-subsidiary name there — L001 Loblaws Inc., TJMaxx of CA, LLC, one of four different PwC LLPs. Take it at face value
and one employer splinters into forty. We vote across the whole tenant: adopt a
proper name only when a single non-fragmenting name dominates the tenant's
postings, otherwise keep the stable slug. Lowe's Home Centers, LLC survives;
TJX keeps its slug.
The req id in the URL isn't the clean one. The path-derived id carries a
posting-variant suffix — R-007754-2. The detail's own jobReqId is the real
one, R-007754.
Locations drop their country. The display string is Maryville, TN 37801.
Workday carries the country separately in country.descriptor as
United States of America. We append it — but only to the primary location, since
additionalLocations[] may be in other countries entirely and appending would
produce London, United States of America.
There is no structured salary. At all. Workday CXS exposes nothing. Pay
appears only as free text inside the description, if it appears. We deliberately
don't parse it during the scrape — that's a guess, not ground truth, and this
phase only stores what the platform actually declared. So salary is null on
Workday postings far more often than on Ashby or
Lever, and that's the platform, not us.
A minimal request body returns zero jobs rather than an error. Get the POST body wrong and you get a valid, empty, 200-status response.
Or use ours#
Workday is the clearest case on this site for not building it yourself, and the reason isn't the parsing — it's that the work is per-tenant and never ends.
Every Workday customer is a separate discovery problem, a separate host, a
separate site name, and a separate N+1. We track 7,586 of them. You get the
descriptions without paying a request per posting, the tenant-scoped ids that stop
Globex's R-12345 from clobbering Initech's, the org-name vote that keeps one
employer as one employer, and the country appended from Workday's own structured
field rather than guessed from a zip code.
Workday sits in the heavy refresh tier — attempted every third cycle rather than every hour, because the N+1 makes it expensive. A new Workday posting can take several hours to appear, and a removed one takes as long to reap. Coverage is explicit about that; don't build anything that assumes uniform latency across sources.
Every source above normalises onto the same schema, with the hiring company already joined onto each posting.
Get an API key