Workday career sites as JSON: the /wday/cxs endpoint behind myworkdayjobs.com, tested on NVIDIA, Salesforce and Adobe, with a Python example and a free sample
How the public JSON behind Workday career sites works, how to build the URL from a careers link, the list and detail calls, the 20-per-page and 2,000-result traps we hit, why it is not a documented API and what that means, and when an Actor saves you the code.
The short version
- Workday career sites (
https://<tenant>.wd<N>.myworkdayjobs.com/<site>) are single-page apps. The page loads its jobs from a JSON endpoint under/wday/cxs/<tenant>/<site>/: a POST for the list and a GET per job for the description. No login or key was needed on any tenant that answered. - This is not a documented API. Workday does not publish it for third parties; it is what the career site itself calls. It can change, throttle or disappear without notice, and some tenants refuse it (we got
HTTP 422from two). Do not build anything you cannot afford to repair. - Traps we measured:
limitabove 20 is rejected,totalis reported on the first page only, the list stops at 2,000 jobs (NVIDIA has more), and the list carries no description and no real date, so those cost one extra request per job. - This page gives the calls with curl, a Python script (about 60 lines, tested today) and a free 20-job sample in the normalized schema of our job Actors.
1. From a careers URL to the endpoint
Take the URL of the career site. Three parts matter: the tenant (first label of the host), the data-center number wdN (second label), and the site (first path segment, after an optional locale such as en-US):
https://nvidia.wd5.myworkdayjobs.com/en-US/NVIDIAExternalCareerSite
^tenant ^wd5 ^locale ^site
list: POST https://nvidia.wd5.myworkdayjobs.com/wday/cxs/nvidia/NVIDIAExternalCareerSite/jobs
detail: GET https://nvidia.wd5.myworkdayjobs.com/wday/cxs/nvidia/NVIDIAExternalCareerSite{externalPath}
Tenants and sites we confirmed on 6 November 2026 (company-chosen names, so copy them from the real URL, do not guess):
| Company | Tenant / data center | Site | Jobs reported on the first page |
|---|---|---|---|
| NVIDIA | nvidia.wd5 | NVIDIAExternalCareerSite | 2,000 (see section 3) |
| Salesforce | salesforce.wd12 | External_Career_Site | 1,554 |
| Cisco | cisco.wd5 | Cisco_Careers | 1,457 |
| Adobe | adobe.wd5 | external_experienced | 281 |
Workday itself (workday.wd5/Workday) also worked when read through the Actor. A wrong site name on a real tenant gave HTTP 404; a tenant host that does not exist gave HTTP 422. The same 422 came back for the two other big names we tried, Walmart and Dell, with the site names we guessed; we did not find out whether the sites or the request were wrong, so treat 422 as "not usable this way".
2. The calls, with curl
# list: 20 jobs per page; must send Content-Type: application/json
curl -s -X POST "https://nvidia.wd5.myworkdayjobs.com/wday/cxs/nvidia/NVIDIAExternalCareerSite/jobs" \
-H "Content-Type: application/json" \
-d '{"appliedFacets":{},"limit":20,"offset":0,"searchText":""}'
# full text search: searchText
curl -s -X POST "https://nvidia.wd5.myworkdayjobs.com/wday/cxs/nvidia/NVIDIAExternalCareerSite/jobs" \
-H "Content-Type: application/json" \
-d '{"appliedFacets":{},"limit":20,"offset":0,"searchText":"data scientist"}'
# one job: the externalPath taken from the list
curl -s "https://nvidia.wd5.myworkdayjobs.com/wday/cxs/nvidia/NVIDIAExternalCareerSite/job/US-CA-Remote/Senior-Account-Manager--Hyperscale-and-AI-Native_JR2023413-1"
What the list returns
{
"total": 2000,
"jobPostings": [
{
"title": "Senior Account Manager, Hyperscale and AI Native",
"externalPath": "/job/US-CA-Remote/Senior-Account-Manager--Hyperscale-and-AI-Native_JR2023413-1",
"locationsText": "US, CA, Remote",
"postedOn": "Posted Today",
"bulletFields": ["JR2023413"]
}, ...
],
"facets": [ {"facetParameter": "jobFamilyGroup", "descriptor": "Job Category", "values": [...]}, ... ],
"userAuthenticated": false
}
- No description and no date.
postedOnis a label ("Posted Today", "Posted Yesterday", "Posted 7 Days Ago", "Posted 30+ Days Ago").locationsTextbecomes "2 Locations" as soon as a job has more than one.bulletFields[0]is the requisition id. searchTextworks as a title and text search: "data scientist" gave 98 NVIDIA jobs.- Order is roughly newest first, not guaranteed: at offset 400 the labels were "7 Days Ago", at offset 1000 "6 Days Ago".
What the detail returns
The detail is {"jobPostingInfo": {...}, "hiringOrganization": {...}, "similarJobs": [...]}. In jobPostingInfo we found, among others: title, jobDescription (HTML), location, additionalLocations[], startDate (a date such as 2026-11-06: the real posting date), timeType ("Full time"), jobReqId, country.descriptor, jobRequisitionLocation.country.alpha2Code, externalUrl (the public job page) and, on some tenants, remoteType (Cisco returned "Onsite Only" for one job it listed). There is no salary field; if a company publishes pay it is inside the description text. There is no department field either.
3. Limits and traps (measured)
- 20 per request.
"limit": 21returnedHTTP 400with{"errorCode":"HTTP_400",...}. Page withoffsetin steps of 20. - Content-Type matters. The same POST without the JSON content type returned
HTTP 500. totalonly on page one. On later pages it was0. Read it once, then page until a page returns fewer than 20 jobs.- The list stops at 2,000. NVIDIA's first page said
total: 2000, yet one facet alone (Engineering,jobFamilyGroup) counted 1,780 and the job categories added up to 2,804. Asking foroffset: 2000(or 2020, 2100) did not return an empty page: it returned the first page again. If you page blindly you collect duplicates. Split by facets instead: take a valueidfromfacets[].values[]and send it back, e.g."appliedFacets": {"jobFamilyGroup": ["<id>"]}(the Engineering id returnedtotal: 1780). - Details cost a request per job. 2,000 jobs with descriptions means about 2,100 requests. Filter on title and location from the list first, and only then fetch details.
- Be gentle. We spaced requests 0.3 s apart, one at a time per host. We found no published rate limit, which is not the same as there being none.
- robots.txt. NVIDIA's host lists
Disallow: /refreshFacet/and a few site paths (/talentcommunity/,/NVIDIAExternalCareerSiteResearch/,/NVIDIARecruitAds/), withAllow: /NVIDIAExternalCareerSite/;/wday/is not disallowed. Read the file of each host you use: Mastercard's, for instance, has extra disallowed paths.
4. DIY: one Workday site to CSV in Python
This script parses a careers URL, searches for a phrase, pages through the list, fetches each job's detail and writes workday_jobs.csv. Run on 6 November 2026 with searchText "data scientist" on NVIDIA it reported total 98 and wrote 40 rows (the script's max_jobs limit). It needs pip install requests. Put a real contact in the User-Agent.
import csv
import re
import time
from urllib.parse import urlparse
import requests
HEADERS = {"User-Agent": "my-script/1.0 (contact: you@example.com)"}
CAREERS_URL = "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
def parse(url):
"""https://<tenant>.<wdN>.myworkdayjobs.com/[locale/]<site>/... -> (host, tenant, site)"""
u = urlparse(url)
m = re.match(r"^([a-z0-9-]+)\.wd\d+\.myworkdayjobs\.com$", u.hostname or "")
parts = [p for p in u.path.split("/") if p]
if parts and re.match(r"^[a-z]{2}-[A-Z]{2}$", parts[0]): # optional locale, e.g. en-US
parts = parts[1:]
if not m or not parts:
raise ValueError("not a myworkdayjobs.com career site URL")
return u.hostname, m.group(1), parts[0]
def search(base, text="", max_jobs=40):
"""Yield list entries; the server accepts limit <= 20 and returns `total` on the first page only."""
offset = 0
while offset < max_jobs:
r = requests.post(f"{base}/jobs", headers=HEADERS, timeout=60,
json={"appliedFacets": {}, "limit": 20, "offset": offset, "searchText": text})
r.raise_for_status()
data = r.json()
if offset == 0:
print("total reported:", data["total"])
postings = data.get("jobPostings") or []
yield from postings
if len(postings) < 20:
break
offset += 20
time.sleep(0.3)
def detail(base, external_path):
r = requests.get(base + external_path, headers=HEADERS, timeout=60)
r.raise_for_status()
return r.json()["jobPostingInfo"]
host, tenant, site = parse(CAREERS_URL)
base = f"https://{host}/wday/cxs/{tenant}/{site}"
rows = []
for p in search(base, text="data scientist", max_jobs=40):
d = detail(base, p["externalPath"])
rows.append({"id": d["jobReqId"], "title": d["title"], "location": d.get("location"),
"country": (d.get("country") or {}).get("descriptor"),
"time_type": d.get("timeType"), "start_date": d.get("startDate"),
"url": d["externalUrl"]})
time.sleep(0.3)
with open("workday_jobs.csv", "w", newline="", encoding="utf-8-sig") as f:
w = csv.DictWriter(f, fieldnames=list(rows[0]))
w.writeheader()
w.writerows(rows)
print(len(rows), "jobs written")
It does not split by facets, so on a site with more than 2,000 jobs run it per facet or per search term, and it does not retry on errors. If a tenant answers 422, stop: the endpoint is not usable there.
Remember what you are using: an undocumented endpoint of someone else's product, used by their own career site. Keep volumes small, identify yourself, and expect to fix the script when Workday changes it. The postings are the employers' content: do not use them to contact individuals.
5. When the Actor saves time
For one site and a one-off export, the script above is free and enough. The Apify Actor Workday Jobs Scraper (by Nexus Scrapers) does the same calls with the housekeeping built in, and the multi-system Career Site Jobs Scraper adds Greenhouse, Lever, Ashby, Workable, Recruitee and Personio in the same schema (see the Greenhouse, Lever and Ashby guide).
- Input as the URL form:
nvidia.wd5/NVIDIAExternalCareerSiteor the career URL, several sites per run. - The normalized schema: title, locations, country and ISO code, employment type, remote type when the site gives one,
postedAt, description text and HTML, apply link, requisition id inextras. Missing values arenull. - Filters as plain inputs: title keywords, excluded keywords, location text, remote only, posted within N days. Details are fetched only for the jobs that pass your title and location filters, at most
maxItemsper company, so a narrow search costs a few requests instead of thousands. - Politeness: honest User-Agent,
robots.txtchecked per host before every request, one request at a time per Workday host, 0.3 s apart, retries with backoff, no proxies, no browser. A site that answers 422 or 404 is skipped with a warning in the log; the run succeeds if at least one site was read. - Scheduling, webhooks, JSON/CSV/Excel export and API/MCP access come with Apify.
Pricing is pay per result; the Actor page shows the current price.
Limits, from the code and our own run: it reads at most the first 1,000 listings of each site, so it will not walk a 2,000-job list to the end (use keywords or several runs). With includeDescription off there is no detail call and postedAt is approximate, derived from the "Posted N days ago" label (extras.postedAtApprox says so). Salary is always empty for Workday, because the endpoint has none. A remoteType value such as Cisco's "Onsite Only" was not mapped in our sample (workplaceType and isRemote are null for those jobs). It relies on an undocumented endpoint, so it can break the day Workday changes it.
6. Real sample: 20 normalized jobs
These 20 jobs were produced on 6 November 2026 by running the Actor's own code locally on the live endpoints (no Apify platform involved): the first postings of five public Workday sites, NVIDIA 5, Salesforce 4, Adobe 4, Cisco 4 and Workday 3, in the list's order (roughly newest first), not hand-picked. postedAt is the date-only startDate of each job. Description text is cut at 1,500 characters and the HTML description is left out to keep the files small. A scrub pass for e-mail addresses ran over every text field and none remain in the files.
Download: sample.json (full records, UTF-8) and sample.csv (flattened, lists joined with | , opens in Excel).
| Company | Title | Location | Req. id | Remote | Posted |
|---|---|---|---|---|---|
| Nvidia | Senior Account Manager, Hyperscale and AI Native | US, CA, Remote | JR2023413 | yes | 2026-11-06 |
| Nvidia | Industry Development Specialist, AI Natives | US, CA, Santa Clara | JR2028623 | - | 2026-11-06 |
| Nvidia | Industry Development Specialist, NCP | US, CA, Santa Clara | JR2023422 | - | 2026-11-06 |
| Nvidia | System Application Engineer | Taiwan, Taipei | JR2029384 | - | 2026-11-06 |
| Nvidia | Senior System Software Engineer - GPU Server | Taiwan, Taipei | JR2029291 | - | 2026-11-06 |
| Salesforce | Recruiting Coordinator (Contract) | Canada - Toronto | JR367536 | - | 2026-11-06 |
| Salesforce | Software Engineering AMTS | India - Hyderabad | JR356580 | - | 2026-11-06 |
| Salesforce | Product Accessibility Engineer | India - Bangalore | JR367595 | - | 2026-11-06 |
| Salesforce | Regional Vice President, Sales Enterprise | South Africa - Johannesburg | JR367434 | - | 2026-11-06 |
| Adobe | Director Cybersecurity and Fraud Public Policy | Washington D.C. | R173038 | - | 2026-11-06 |
| Adobe | Regional Security and Safety Manager for Wester… | Paris | R173261 | - | 2026-11-05 |
| Adobe | Senior Manager, Labor Relations EMEA (Location:… | London | R173441 | - | 2026-11-05 |
| Adobe | Group Manager, Global Customer Operations & Gro… | Noida | R171692 | - | 2026-11-05 |
| Cisco | Embedded Software Engineer | San Jose, California, US | 2029178 | - | 2026-11-05 |
| Cisco | Senior FPGA Engineer | San Jose, California, US | 2027589 | - | 2026-11-05 |
| Cisco | Lead Performance & AI Tools Engineer, Hybrid | Milpitas, California, US | 2030105 | no | 2026-11-05 |
| Cisco | Product Designer - Collaboration Devices & Work… | Lysaker, Norway | 2028374 | no | 2026-11-05 |
| Workday | Incident Manager | New Zealand, Auckland | JR-0111310 | - | 2026-11-06 |
| Workday | Principal, Product Manager - Financial Reportin… | USA, CA, Pleasanton | JR-0110096 | - | 2026-11-05 |
| Workday | Sr Solution Architect | USA, CA, Pleasanton | JR-0111082 | - | 2026-11-05 |
"Remote" is the normalized isRemote: yes, no, or - when the site does not say. Note that sourceUrl in the records is the /wday/cxs/ detail URL and jobUrl the public page.
7. Terms
There is no public API documentation or developer terms for this endpoint to cite; it is the interface of the employers' career sites. That is why we keep volumes small, honor robots.txt and say plainly that it is unsupported. The postings belong to the employers; read the terms that apply to your use. This is not legal advice.
Links
- Workday Jobs Scraper on Apify
- Career Site Jobs Scraper on Apify (Greenhouse, Lever, Ashby, Workable, Recruitee, Personio, Workday)
- The companion guide: Greenhouse, Lever and Ashby
- The same kind of guide for EU tenders (TED)