Microsoft Jobs API
Query Microsoft's postings through one API with ?source=microsoft — plus the list field that swears every job is on-site, and a page size hard-capped at ten.
Microsoft is not a platform. It's one employer with a bespoke careers site, and
this adapter reaches Microsoft and nothing else. Write Workday once
and you reach every Workday tenant; write this one and you reach Microsoft.
company is ignored — there is one.
Its jobs are worth having, and the only way to have them is a dedicated adapter
maintained forever. We run it: query it with ?source=microsoft.
/v1/meta is the authoritative live vocabulary, with
the current count per source.
How Microsoft Careers actually works#
Microsoft moved to a platform it calls pcsx (apply.careers.microsoft.com);
the old gcsservices API is dead. It's the same Eightfold-family platform our
Eightfold adapter already reads — same workLocationOption enum.
No browser, no auth: a plain browser User-Agent gets a 200.
GET /api/pcsx/search?domain=microsoft.com&query=&start=N&num=10 → positions[], count
GET /api/pcsx/position_details?position_id=<id>&domain=microsoft.com → full HTML bodyThe list is structured but ships no description, so the detail call is an N+1
you can't skip — run only for the day's new reqs. Page 0 carries count (~1,600
jobs); the rest fan out in parallel.
`num` is capped at ten, server-side
Ask for num=100 and you get ten. The page size is fixed at ten no matter
what you send, so a ~1,600-job board is ~160 list pages, then ~1,600 detail
fetches on a cold run. Paginate sequentially and you blow the time budget; the
adapter fans the pages out concurrently instead.
The list swears every job is on-site#
workLocationOption on the list record reads "onsite" for every job —
measured 10 out of 10, including fully-remote roles with zero days in office. It is
a broken field. The real arrangement lives on the detail payload, in
efcustomTextWorkSite — "0 days / week in-office – remote", "3 days / week in-office", "Fully on-site" — which the adapter parses into Remote / Hybrid /
On-site, falling back to the broken list field only when the detail is missing.
Take workLocationOption at face value and you'd label a remote-first employer
100% on-site, with a well-formed field and nothing to alert you.
What bites you#
A 403 here is transient, not a ban
The pcsx edge is Akamai, and it's reputation-gated. From a clean IP a plain
request 200s and the whole board scrapes. Hammer it — a handful of full-board
runs inside fifteen minutes — and it trips a temporary IP-wide block that 403s
even curl, then decays on its own. So a 403 is a momentary soft-block, not a
wall: it's retried, and a persistently-blocked run fails safe (returns
nothing, expires nothing) rather than wrongly shrinking the board.
Locations come two ways, and one is backwards. standardizedLocations is
city-first ("Redmond, WA, US") — the shape a parser wants; locations is
country-first ("United States, Washington, Redmond"). The adapter prefers the
former and only falls back to the latter.
The cold start is the expensive one. ~1,600 detail fetches behind a ten-per-page list is real work; warm re-scrapes detail only the day's new reqs, and a detail that fails leaves the job with its structured list fields rather than dropping it.
Or use ours#
A ten-capped page size fanned out, a mandatory detail N+1 for descriptions, a
work-arrangement field that lies on the list and has to be recovered from the
detail, and an Akamai soft-block handled as transient — with Microsoft's rows in
the same Job shape as every other
source, dedup against the same role on
LinkedIn, and the adapter's upkeep being ours instead of yours.
Same deal as Amazon, Apple, Uber,
TikTok, Google and IBM.
Every source above normalises onto the same schema, with the hiring company already joined onto each posting.
Get an API key