Dayforce Jobs API
Query jobs from Dayforce boards through one API with ?source=dayforce — plus how Dayforce's CSRF handshake and its 25-row page size really behave.
Dayforce is Ceridian's HCM platform, and its career sites are the enterprise
end of this pool: hospitals, media groups, manufacturers, the kind of employer
that runs one Dayforce tenant across a dozen brands. Every board lives under
jobs.dayforcehcm.com/<clientNamespace>/<jobBoardCode>, a Next.js front end over
an ASP.NET Core API. There are 28,727 Dayforce postings in this dataset — a number
that moves hourly, so read it from /v1/meta rather
than trusting this sentence.
Try it#
curl -G https://api.hyperjobs.io/v1/jobs \
-H "Authorization: Bearer $HYPERJOBS_KEY" \
--data-urlencode "source=dayforce" \
--data-urlencode "time_frame=7d" \
--data-urlencode "limit=2"{
"data": [
{
"id": "dayforce:northgate_10482",
"title": "Multimedia Journalist",
"company": {
"slug": "northgate-media",
"name": "Northgate Media Group",
"domain": "northgatemedia.com",
"website": "https://northgatemedia.com",
"logo": "https://media.licdn.com/dms/image/…",
"industry": "Broadcast Media",
"employee_count": 3100
},
"location": {
"remote": false,
"countries": ["United States"],
"cities": ["Des Moines"],
"regions": ["Iowa"],
"raw": ["Des Moines, IA, US"]
},
"employment_type": ["FULL_TIME"],
"work_arrangement": null,
"seniority": null,
"salary": {
"min": 24.5,
"max": 31.5,
"currency": "USD",
"unit": "HOUR",
"annual_min": 50960,
"annual_max": 65520,
"summary": "$24.50 – $31.50 an hour"
},
"skills": ["video editing", "adobe premiere"],
"taxonomies": ["Media"],
"apply_url": "https://jobs.dayforcehcm.com/en-US/northgate/CANDIDATEPORTAL/jobs/10482",
"url": "https://jobs.dayforcehcm.com/en-US/northgate/CANDIDATEPORTAL/jobs/10482",
"source": "dayforce",
"posted_at": "2026-07-09T13:04:00.000Z",
"expires_at": "2026-08-08T00:00:00.000Z",
"updated_at": "2026-07-14T02:19:07.000Z"
}
],
"total": 2,
"limit": 2,
"offset": 0
}Note the id: northgate_10482, not 10482. That prefix is load-bearing and the
reason is below.
How Dayforce boards actually work#
Three endpoints, and only the middle one is guarded:
# 1. CSRF — a token AND a Set-Cookie you have to keep
curl "https://jobs.dayforcehcm.com/api/auth/csrf"
# 2. List — POST, 25 rows, needs both halves of the handshake
curl -X POST "https://jobs.dayforcehcm.com/api/geo/<ns>/jobposting/search" \
-H "X-CSRF-TOKEN: <token>" \
-H "Cookie: __Host-next-auth.csrf-token=<cookie>" \
-d '{"clientNamespace":"<ns>","jobBoardCode":"<board>","cultureCode":"en-US","paginationStart":0}'
# 3. Detail — GET, no auth at all
curl "https://jobs.dayforcehcm.com/api/geo/<ns>/jobposting/<ns>/en-US/<boardId>/<id>"The CSRF wall is a double-submit#
The site's axios interceptor injects X-CSRF-TOKEN from next-auth on every POST,
and the API checks the header and the matching __Host-next-auth.csrf-token
cookie. Both, together, or nothing:
| Sent | Result |
|---|---|
| No token, no cookie | 403 |
| Header token only | 403 |
| Cookie only | 403 |
| Bogus token + real cookie | 403 |
| Header + its matching cookie | 200 |
So GET /api/auth/csrf first, keep the cookie it sets, and send both. That one
cookie is enough — the __cf_bm and callback-url cookies it also hands you are
not checked. Most HTTP clients have no cookie jar by default, which is the whole
of the difficulty: this is a two-line problem that reads as an auth wall.
The detail endpoint, and the sitecontext endpoint that tells you which languages
a board publishes in, take no auth at all.
The page size is 25 and cannot be raised#
The paging field is paginationStart, a row offset rather than a page index — the
site computes it as (page - 1) * 25. Arbitrary values work, and it's honest:
0/25/50/…/150 on one board returned 159 unique ids with zero overlap, exactly the
maxCount it reported.
What you cannot do is ask for more rows. Roughly 45 override names were sent —
count, take, pageSize, limit, maxCount, paginationCount,
paginationEnd and the rest, in the body, nested under paging / pagination /
searchCriteria / filter, and as query strings — and every one was silently
ignored at HTTP 200, with the response echoing count: 25 regardless. One
request per 25 jobs is the floor on this platform.
Finding the field that does work is harder than it sounds, because
System.Text.Json drops unknown properties silently: a wrong name is
indistinguishable from a right one that "doesn't work". offset: "XX", a string
in an int field, does not type-error, which is the tell that the field isn't
in the model at all.
Board codes are not guessable#
boardToken is <clientNamespace>/<jobBoardCode>, and the second half is not
derivable. The obvious guesses, Careers and JobOpenings, both 404. The real
ones observed are CANDIDATEPORTAL, alljobs and jobboards. There's no index
and no pattern, so discovery is the only way to learn a board exists. This is the
"finding the board is harder than fetching it" problem again, and on Dayforce it's
the entire cost of coverage.
What bites you#
`jobPostingId` is a per-client counter, not a global id
The ranges overlap hard across tenants — one client's postings run 9,637–11,915
while another's run 9,749–12,513. A key of dayforce:<jobPostingId> collides
and silently merges two employers' jobs into one row.
The key has to be scoped, and scoped to the client, not the board: the id
is drawn from one sequence shared across all of a client's boards, so scoping by
namespace also correctly dedups a posting listed on two of them. Ours is
dayforce:<clientNamespace>_<jobPostingId>, which is why the ids on this source
look like dayforce:northgate_10482.
cultureCode filters the board, it does not translate it. A job is visible
only in the culture it was posted in. One board returns 154 jobs on en-US, 35 on
fr-CA, and 2 on de-DE — so the wrong culture hands you a partial board at
HTTP 200, invisibly, and a genuinely multilingual employer quietly yields only
its English postings. We default to en-US and probe sitecontext for the real
list only when a board comes back empty, rather than writing it off as dead.
coordinates is a centroid of whatever the address names. Every
"United States" row on the platform carries the same lat: 38.7945952 — the
dead centre of the country, in rural Colorado. "Canada" gets 56.13,-106.35;
counties and states get their own. Emit them and every vague or remote posting
pins to one fake point on a map. cityName is the only signal that the
coordinates refer to a real place, and a comma in the address is not one:
states and counties have commas too.
jobPostingAttributes[].value is not uniformly a string. The type: "string"
rows (JobFamily, PayType, EducationLevel) carry strings; the
type: "currency" rows — HiringMinRate, HiringMaxRate, HiringRate, which is
to say every salary — carry a JSON number. A typeof v === "string" guard
therefore drops the entire salary map while the string attributes sail through,
and the board then reports department at 100% and salary at 0% and looks
completely healthy. That bug shipped here for one run and took a per-field value
check across five tenants to see.
PayType is the only period signal, and it occasionally lies. One posting in
161 was marked "Salary" on a 28.20–32.30 CAD range that is plainly an hourly
wage. Trust the label and you write a $28-per-year salary. We honour it only when
the magnitude agrees and fall back to inferring from the amount when they
contradict each other.
postingLocations can be null, not merely empty — a third of the rows on one
tenant. And the list's jobDescription is the full body but HTML-stripped and
capped at 12,000 characters, while jobDescriptionHeader/Footer are
per-board boilerplate: the same 6.5k EEO block repeated on every job. Concatenate
them and you add 8k of noise to every row.
Or use ours#
Dayforce is cheap per board and expensive per posting, and none of the cost is where you'd guess. It isn't the CSRF handshake, which is two lines once you know the cookie matters. It's that the page size is pinned at 25, the board code can't be derived, the culture parameter silently truncates, and the salary type isn't what its neighbours' types are.
You get the descriptions uncapped rather than truncated at 12,000 characters, the
boilerplate header and footer left out, the client-scoped ids that stop one
employer's 10482 from clobbering another's, salary read as a number where it's
stored as a number and refused where the label contradicts the magnitude,
centroids withheld so a remote job doesn't land in rural Colorado, and the
company join.
Refresh cadence varies by source, and one request per 25 postings puts this one at the expensive end. See coverage, and don't assume uniform latency.
Query it with ?source=dayforce.
Quickstart · Pricing
Every source above normalises onto the same schema, with the hiring company already joined onto each posting.
Get an API key