Workday Jobs Scraper — Pay Ranges by Company Name
Pricing
from $2.00 / 1,000 results
Workday Jobs Scraper — Pay Ranges by Company Name
Scrape open roles from any company's Workday career site by typing the company name — no tenant ID, pod or site name needed. Extracts disclosed pay ranges (min, max, currency, hourly vs annual), location, remote type, requisition ID and posting date. For comp benchmarking and hiring intelligence.
Pricing
from $2.00 / 1,000 results
Rating
0.0
(0)
Developer
mochi
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
Get every open role from any company's Workday career site by typing the company name. No tenant ID, no pod number, no career-site name — and no URL hunting.
Built for compensation benchmarking and hiring intelligence: alongside the usual job fields it extracts the disclosed pay range — minimum, maximum, currency, and whether the figure is hourly or annual — from the posting text, including the multi-band ranges that large US employers now publish under state pay-transparency laws.
Why this is harder than it looks
A Workday career site lives at https://<tenant>.wd<N>.myworkdayjobs.com/<CareerSite>. To fetch one company you need all three parts:
| Part | Example | Where you find it |
|---|---|---|
| tenant | cvshealth | usually the company slug, but not always |
| pod | wd1 / wd5 / wd12 / wd103 | which Workday data centre hosts them — no pattern, no registry |
| career site | CVS_Health_Careers | freely chosen by the employer |
There is no public directory mapping companies to those values. This actor resolves all three from a plain company name by reading the tenant's own robots.txt, which publishes a Sitemap: directive for each career site it operates.
You type CVS Health — it finds cvshealth.wd1 / CVS_Health_Careers.
You can also paste a career-site URL directly if you already have one.
Pay-range coverage — measured, not claimed
Disclosure is entirely up to the employer, and it varies enormously. These are real numbers from a sweep of 325 live postings across 12 companies:
| Company | Postings sampled | With a pay range |
|---|---|---|
| CVS Health | 30 | 100% |
| Target | 30 | 93% |
| Workday | 30 | 73% |
| Cisco | 30 | 63% |
| Adobe | 30 | 53% |
| NVIDIA | 30 | 43% |
| AstraZeneca | 30 | 17% |
| Pfizer | 30 | 13% |
| Shell | 30 | 10% |
| RBC | 24 | 8% |
| Salesforce | 30 | 7% |
| Unilever | 1 | 0% |
| Overall | 325 | 44% |
Currencies seen: USD, EUR, CAD. Periods: annual and hourly. Roughly 1.65 pay bands per disclosing posting — because many postings quote more than one (see below).
If you only want rows that carry pay, set onlyWithPayRange. US-filtered runs land far above the average — a countryCodes: ["US"] run on these companies returns close to 100% disclosure, because the disclosure laws are American.
Multi-band ranges — the part most scrapers flatten
A single posting frequently contains several ranges, and they mean different things. Real examples from the captures in fixtures/:
Cisco publishes a base range plus state-specific ranges in the same posting:
starting salary range ................ $145,200.00 to $184,100.00New York City Metro Area ............. $145,200.00 - $212,100.00Non-Metro New York state & Washington $129,100.00 - $188,300.00
NVIDIA publishes one range per level:
base salary range is 60,000 USD - 101,200 USD for Level 3,and 72,000 USD - 120,750 USD for Level 4
Target publishes a starting range and a full range:
The starting pay range for this position per hour is $21.00 - $23.00The full pay range for this position per hour is $21.00 - $35.70
The primary band is reported in the flat payMin / payMax columns, and every band is kept in payBands, each with its own scope (starting / full), level (e.g. Level 3) and an includesVariablePay flag for ranges labelled OTE or "cash compensation".
Hourly vs annual
Workday postings do not label the period in a machine-readable field, and the wording is often ambiguous. CVS Health, for example, prints this next to every range:
"This pay range represents the base hourly rate or base annual full-time salary…"
That sentence cannot decide the question, so:
- If the surrounding text gives exactly one unambiguous period phrase (
per hour,per year,annually…), it is used andpayPeriodInferredisfalse. - If the text is silent or self-contradictory, the period is inferred from the magnitude, and
payPeriodInferredistrueso you can tell the difference. - Between roughly 400 and 12,000 in a major currency, an amount could be monthly, weekly or a daily rate. The period is left
nullrather than guessed. A benchmark with gaps is more useful than one with invented numbers.
Magnitude inference is only applied to currencies with USD-like magnitudes. A JPY or INR figure never gets a guessed period.
What the parser refuses to do
Every one of these was a real false positive caught against live postings during the build:
Estimated Weekly Hours 40— a bare integer is never money, and "Weekly" here is not a pay period.500-5,000 FTE,BGP 1-7— a trailing capitalised acronym is only a currency if it is a real ISO-4217 code.$231,000 - $275,000 OTE— that is a real USD range;OTEis recorded as a variable-pay flag, not mistaken for the currency.a $500 sign-on bonus— a lone amount needs both an explicit period phrase and pay context before it counts as pay.$2,500.00 per hour of transaction volume— has a period phrase but no pay context, so it is ignored.- A decoy number earlier in the description (
a portfolio of 200 to 300 accounts) does not hide the real range further down.
Output fields
| Field | Notes |
|---|---|
company, legalEntity, tenant, pod, careerSite, requestedAs | who, and how it was resolved |
jobId, requisitionId, jobPostingId, title, jobUrl | the requisition |
location, requisitionLocation, country, countryCode, additionalLocations, locationCount | ISO alpha-2 country code included |
workplaceType, isRemote, timeType | as Workday reports them; often unset |
postedOnText, postedDate, daysSincePosted, applicationDeadline, timeLeftToApply, isOpen | freshness and deadlines |
hasPayRange, payMin, payMax, payCurrency, payCurrencySymbol | the primary band |
payPeriod, payPeriodInferred, payIsSingleValue | hour / year / month / null |
payIncludesVariablePay, payScope, payLevel | OTE flag, starting-vs-full, level label |
payRangeRaw | the exact source text, so you can audit any number |
payBandCount, payBands | every band found, not just the first |
descriptionText | optional (includeDescription) |
source, scrapedAt | provenance |
Pay columns are always present, set to null when no range was found, so a CSV export keeps a stable shape.
Input
| Option | Default | What it does |
|---|---|---|
companies | 5 sample employers | Company names or career-site URLs |
maxResults | 20 | Allocated round-robin, so every company is represented |
searchText | "" | Passed to Workday's own search per company |
onlyWithPayRange | false | Keep only postings with a disclosed range |
titleKeywords | [] | Substring match on the title |
locationKeywords | [] | Matches location, requisition location, country, additional locations |
countryCodes | [] | Exact ISO alpha-2 match — US does not also select AUS |
postedWithinDays | 0 | Freshness filter; 0 disables |
remoteOnly | false | Many employers leave the field unset; those are excluded |
includeDescription | false | Adds the full plain-text description |
includeSecondarySites | false | Early-careers / campaign sites, which mostly duplicate the main one |
includeAllSites | false | Also sites marked internal / private / contractor |
Round-robin matters. Filling results company-by-company means the first company consumes the whole quota and every company after it silently returns nothing. Results here are interleaved as they are fetched, so a five-company run at maxResults: 20 returns four from each.
How it reports problems
Silence is the failure mode that costs you money, so the run log distinguishes:
- unresolved — no Workday tenant found for that name (it is never guessed at);
- empty — the career site resolved fine but genuinely lists 0 open postings (Dell's Workday instance is a real example);
- fetch failure — a request that actually failed.
The counts are also written to the RUN_SUMMARY key in the default key-value store.
Source, limits and compliance
- Source: public Workday career sites — the same postings any visitor sees, read through the career site's own JSON endpoint.
- robots.txt is honoured. The resolver reads each tenant's
robots.txtat run time, uses itsSitemap:directives for discovery, and will not request a path that tenant disallows. If a tenant ever names an AI crawler inrobots.txt, that company is refused outright rather than fetched. - Non-public career sites are skipped by default, including ones named internal, private, contractor or conversion-only, even where they are technically reachable.
- PDPA / GDPR: organisation-level data only. Employer, requisition, location and pay. No candidate or recruiter data is collected.
- Coverage limits: the actor only reaches employers who use Workday, and only sites listed in that tenant's
robots.txt. Pay extraction depends on the employer disclosing in the posting text — 44% of postings in our sample, and 0% at some employers. - Rate: requests are issued one at a time per company with retries and backoff.