All platforms

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.

?source=dayforcesource reference

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#

BASH
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"
Illustrative — the shape, not a live response
{
  "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:

BASH
# 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:

SentResult
No token, no cookie403
Header token only403
Cookie only403
Bogus token + real cookie403
Header + its matching cookie200

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