PageUp Jobs API
Query jobs from PageUp boards through one API with ?source=pageup — plus how PageUp's listing endpoint and its #job-details wrapper really behave.
PageUp is an enterprise talent management platform with a heavy footprint in
higher education and in Australia and New Zealand — universities, health
services, state agencies, the kind of employer with a thousand open roles and a
procurement process. Boards live at
careers.pageuppeople.com/<orgId>/<code>/<lang>/, and there are 8,644 PageUp
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=pageup" \
--data-urlencode "time_frame=7d" \
--data-urlencode "limit=2"{
"data": [
{
"id": "pageup:533_caw_en_694218",
"title": "Lecturer, Environmental Science",
"company": {
"slug": "kelvin-university",
"name": "Kelvin University",
"domain": "kelvin.edu.au",
"website": "https://kelvin.edu.au",
"logo": "https://media.licdn.com/dms/image/…",
"industry": "Higher Education",
"employee_count": 4200
},
"location": {
"remote": false,
"countries": ["Australia"],
"cities": ["Melbourne"],
"regions": ["Victoria"],
"raw": ["Bundoora Campus, Melbourne, VIC"]
},
"employment_type": ["FULL_TIME"],
"work_arrangement": null,
"seniority": null,
"salary": null,
"skills": ["teaching", "research"],
"taxonomies": ["Education"],
"apply_url": "https://careers.pageuppeople.com/533/caw/en/job/694218",
"url": "https://careers.pageuppeople.com/533/caw/en/job/694218",
"source": "pageup",
"posted_at": "2026-07-08T00:00:00.000Z",
"expires_at": "2026-08-05T13:59:00.000Z",
"updated_at": "2026-07-14T05:33:18.000Z"
}
],
"total": 2,
"limit": 2,
"offset": 0
}salary is null there and it is not an omission: PageUp exposes no structured pay
field. expires_at being real, on the other hand, is unusual — most ATS publish
no deadline at all.
How PageUp boards actually work#
No JSON API, no JSON-LD, HTML at both levels. The list is the easy half:
# 1. List — a plain GET, 100 per page, 1-indexed
curl "https://careers.pageuppeople.com/<orgId>/<code>/<lang>/listing/?page=1&page-items=100"
# 2. Detail — one per posting; the body only exists here
curl "https://careers.pageuppeople.com/<orgId>/<code>/<lang>/job/<jobId>"That's genuinely all the list takes. No POST, no token, no handshake, and
page-items=100 works — which makes PageUp one of the cheapest listings in this
pool to enumerate. The cost is the second call: the listing's <tr class="summary">
is a teaser that looks like a description, so every posting needs its own fetch.
Three things you can't derive#
boardToken is the <orgId>/<code>/<lang> triple, and all three vary:
From https://careers.pageuppeople.com/533/caw/en/listing/ | |
|---|---|
| orgId | 533 — a bare number |
| code | caw — also seen as cw, eb, ci, and others |
| lang | en — or en-us |
None of it is derivable from a company's own careers domain. A university links its PageUp board from a page on its own site, and the triple is whatever PageUp assigned it.
The body is in #job-details on most tenants, not all#
This is the structural thing to know about PageUp. The description sits in a
<div id="job-details"> on the standard template — but not everywhere. One
university's board has no such wrapper at all; the body sits directly in
#job-content, and reading only #job-details scores 0 of 10 on that tenant
while looking perfectly healthy on every other. With the fallback in place it's
50 of 50 across five tenants.
That's the shape of every PageUp problem: the template is tenant-configurable, so anything you key off has to be the thing that survives the customisation.
What bites you#
page is 1-indexed, and page=0 returns zero rows. Not page one — zero
rows. So the off-by-one you'd normally shrug at silently drops the entire first
page, and the board reads as smaller than it is with no error anywhere.
page-items caps at 100 silently. Ask for 200 and you get 100, at HTTP 200,
with nothing to say you were trimmed.
Exactly 100 looks like a cap and usually isn't
A board returning exactly page-items worth of rows is the classic tell of a
silent truncation, and here it's usually just a board with more than 100 jobs.
The only way to know is to fetch page 2. Guessing either way is wrong: assume
it's a cap and you stop early, assume it isn't and you never check.
<h1> is the site name on the standard template and the job title on custom
ones. "Careers at Kelvin University" is not a job. Key off the <h2> inside
#job-content and fall back to <h1>, never the reverse.
Labels are tenant-configurable, so key off the CSS class. One tenant writes
Job no, another Job No., another translates it. The class names are the stable
part of the markup; the text next to them isn't.
<time datetime="…Z"> disagrees with the text beside it. The attribute is
Z-suffixed while the adjacent literal string says "Eastern Daylight Time" — the
two are up to five hours apart, and there's no way to tell which the tenant meant.
We keep it, because the date is what a recency filter uses and that survives
the ambiguity, but don't read posted_at on this source as a precise instant.
There's no structured salary at all. Not on the listing, not on the detail.
Pay appears in the description prose or not at all, which is why
salary is null on PageUp postings far more often
than on a platform that publishes a real field. That's the platform, not us.
Or use ours#
PageUp is the case where the fetch really is easy and the parsing really isn't.
One GET gets you a hundred job ids. Then the template forks per tenant, the body
moves between two wrappers, the <h1> means two different things, the labels are
whatever the customer typed, and the timestamp contradicts itself.
You get the description from whichever wrapper the tenant actually used, the title
from the element that is a title on both templates, the requisition id read by
class rather than by label text, a real expires_at where the board published a
closing date, and the company join — a PageUp board tells
you 533/caw/en and nothing whatsoever about who that is.
Refresh cadence varies by source, and a detail fetch per posting puts PageUp at the expensive end even though its listing is cheap. See coverage; don't assume uniform latency across sources.
Query it with ?source=pageup.
Quickstart · Pricing
Every source above normalises onto the same schema, with the hiring company already joined onto each posting.
Get an API key