Workday career site JSON: the jobs endpoint, facets, the 2,000-row cap and big boards
Every Workday career site loads its jobs from a JSON endpoint, POST https://{tenant}.{wdN}.myworkdayjobs.com/wday/cxs/{tenant}/{site}/jobs, which returns 20 rows per call and silently wraps back to page one after 2,000 rows. Source: Arcadium Data, data as of .
Workday is the most common system among large employers: it is behind the career sites of banks, chipmakers, retailers and hospitals. It has no published public jobs API, but the sites you see at company.wd5.myworkdayjobs.com are single-page apps that call the same JSON service you can call. That service is stable enough to build on, and has exactly the quirks that make Workday the costliest ATS to crawl.
Among the boards we crawl, Workday has around 3,800 employers (17% of the live directory) and takes most of the crawl cost. This guide covers the request, facets, the cap and a way around it, the detail call, and what to do about pace.
The official route: the career site's own JSON service
Find the three parts of the address
A board needs a tenant (the company's id on Workday), a data centre (wd1, wd3, wd5, wd12 and others) and a site (the career site name; one tenant can have several, such as an external site and an internal one). All three are in the career-site URL: https://nvidia.wd5.myworkdayjobs.com/en-US/NVIDIAExternalCareerSite has tenant nvidia, data centre wd5 and site NVIDIAExternalCareerSite; the en-US is a language and is not part of the address. You cannot guess these from a company name. Find them in the careers page of the company, or in lists of ATS board URLs (see companies using an ATS).
The list call
curl
curl -s -X POST "https://nvidia.wd5.myworkdayjobs.com/wday/cxs/nvidia/NVIDIAExternalCareerSite/jobs" \
-H "content-type: application/json" -H "accept: application/json" \
-d '{"appliedFacets":{},"limit":20,"offset":0,"searchText":""}'
The answer has total, jobPostings (20 rows with title, externalPath, locationsText, postedOn and bulletFields, which usually holds the requisition id) and facets. The page size is 20; larger limits are refused or clipped. postedOn is relative ("Posted 3 Days Ago", "Posted 30+ Days Ago"), so the list gives day-level dates at best.
The cap and how to get past it
One query returns at most 2,000 rows. Past that, paging does not end: the list silently restarts at page one, and a naive loop collects the same 20 jobs forever or stops with a wrong total. Detect it by stopping when a page contains only ids you have already seen. To read a board of more than 2,000 jobs, split the query with facets.
The facets array lists filters with value ids and counts: job category, location, location country, worker type, time type. Add a value to appliedFacets as {"jobFamilyGroup": ["<id>"]}, using the facet's facetParameter as the key, and each slice is a fresh query with its own 2,000 rows. Choosing the facet matters:
- Prefer a facet whose counts add up to about the board total (job family, worker type). A facet where many jobs have no value is unreachable for those jobs.
- A multi-valued facet such as location counts a job once per location, so slices overlap; deduplicate by id.
- Prefer slices that all stay under 2,000, then the fewest slices. If a slice is still over the cap, split again with a second facet, but limit the depth to avoid loops.
Python
import requests
BASE = "https://nvidia.wd5.myworkdayjobs.com/wday/cxs/nvidia/NVIDIAExternalCareerSite"
CAP = 2000
def query(applied, offset):
body = {"appliedFacets": applied, "limit": 20, "offset": offset, "searchText": ""}
return requests.post(BASE + "/jobs", json=body, timeout=60).json()
def crawl(applied, seen):
first = query(applied, 0)
if first["total"] >= CAP: # over the cap: split by a facet
facet = next(f for f in first["facets"] if f.get("facetParameter") and f.get("values"))
for v in facet["values"]:
if v.get("id"):
crawl({**applied, facet["facetParameter"]: [v["id"]]}, seen)
return
for offset in range(0, first["total"], 20):
page = first if offset == 0 else query(applied, offset)
for p in page["jobPostings"]:
seen[p["externalPath"]] = p
seen: dict = {}
crawl({}, seen)
print(len(seen), "jobs")
The detail call
GET https://{tenant}.{wdN}.myworkdayjobs.com/wday/cxs/{tenant}/{site}{externalPath} returns jobPostingInfo: the description HTML, an exact startDate, location and additionalLocations, jobReqId, timeType, remoteType, the country code and the public externalUrl. It is one request per job, which is why Workday costs more than any other system: a first read of a board with 1,400 jobs is about 70 list calls plus 1,400 detail calls. Fetch details for new ids only. Pay ranges are written into the description text, not a field.
Pace and politeness
Workday returns 429 when you hit many tenants quickly. In our crawl the budget looked like a per-IP limit across tenants, not per host: several tenants in parallel at a few requests per second ran into 429s, and two crawlers on one IP add up. Keep a few requests per second in total, back off with a growing pause on 429, run one crawler per IP and use the delta approach (details for new ids only). The employer name inside a posting (hiringOrganization) is a legal entity or site ("IN03 NVIDIA Graphics, Hyderabad"), so take the brand from the tenant, not from the posting. Boards also change address when a company migrates data centres, so keep the full host and site in your key, not the tenant alone.
The fast route: counts and postings per company, no tenant needed
Arcadium Data crawls Workday boards in deltas, handles the facet split, keeps tenant, data centre and site in the board key, and serves each company by domain. The company call is open without a key:
Calls on this page work without a key from one IP (10 requests a minute, 100 a day, short answers, point lookups). A free key lifts the limits: sign up. Routes, parameters and fields: OpenAPI.
curl
curl -s "https://api.arcadiumdata.io/v1/companies/nvidia.com"
Python
import requests
data = requests.get("https://api.arcadiumdata.io/v1/companies/nvidia.com", timeout=30).json()
h = data["hiring"]
print(h["ats"], h["boardUrl"], h["openJobs"], h["jobsPostedLast30Days"])
print(data["signals"]["openings7d"], data["signals"]["closures7d"])
TypeScript
const res = await fetch('https://api.arcadiumdata.io/v1/companies/nvidia.com');
const data = (await res.json()) as { hiring: { ats: string; boardUrl: string; openJobs: number; jobsPostedLast30Days: number }; signals: { openings7d: number; closures7d: number } };
console.log(data.hiring.ats, data.hiring.openJobs, data.hiring.jobsPostedLast30Days);
console.log(data.signals.openings7d, data.signals.closures7d);
Response (abridged)
{
"domain": "nvidia.com", "name": "NVIDIA",
"hiring": {
"ats": "workday",
"boardUrl": "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
"openJobs": 2742, "jobsPostedLast30Days": 1062, "remoteShare": 0.184,
"topCountries": [{ "name": "US", "share": 0.497 }, { "name": "IL", "share": 0.15 }],
"matchConfidence": "high"
},
"signals": { "openings7d": 384, "closures7d": 18, "countries": 32 }
}
That one call replaces finding the three address parts, the facet split (this board is over the cap) and a detail call per job. With a free key, GET /v1/jobs with ats=workday lists the postings across companies (filters country, q, postedSince). The market view, with Workday's share and its switches in and out, is GET /v1/ats/market and GET /v1/ats/switches.
Comparison
| Workday JSON service directly | Arcadium Data | |
|---|---|---|
| Key | None | None for company lookups; free key for job search |
| Page size | 20 | Up to 100 with a cursor |
| Over 2,000 jobs | Silent wrap; split by facets | Handled |
| Dates | Relative in the list; exact in the detail | Exact where the detail was read |
| Request cost | List pages plus one detail per job | One call per company |
| Finding the board | Tenant, data centre and site needed | By domain |
| Pay | Parse the description text | Normalised where stated |
Get the data without the plumbing
Arcadium Data serves these datasets from one REST API and one MCP server, with sources, licences and dates on every answer. No key is needed to try the routes in this guide.
Related
- Companies using an ATS
- Which ATS does a company use
- Posted salary data from job ads
- SmartRecruiters and Personio endpoints
- ATS vendors
Sources
Last updated 2026-10-25. This guide is ours and independent of the organisations it describes. Found a mistake? Tell us and we correct it.
Questions
Does Workday have a public jobs API?
Not as a documented product, but every career site calls a JSON service at /wday/cxs/{tenant}/{site}/jobs. It is the same one the page uses, needs no key and is stable enough to build on.
How many jobs can one Workday query return?
At most 2,000. Past that the list silently restarts at page one. Split the board with facets such as job category or location and deduplicate by requisition id.
How do I find the tenant and site of a company?
Read them from the career-site URL: tenant.wdN.myworkdayjobs.com/[language/]Site. A company name alone is not enough. Our company lookup returns the board URL when we have it.
Why is Workday slower to crawl than other systems?
The list has 20 rows per call and no description, so each job needs a detail call. Other systems return a whole board in one request.
Does the Workday posting include a salary?
Usually only in the description text. Parse the range with its currency and period, and flag contradictions such as an hourly label on yearly amounts.
Cite as: Arcadium Data, "Workday career site JSON: the jobs endpoint, facets, the 2,000-row cap and big boards", https://arcadiumdata.io/guides/workday-career-site-json-api/, data as of 2026-10-25.