Personio Jobs API
Query jobs from Personio boards through one API — and how the Personio XML feed, its structured keywords and its empty descriptions behave.
Personio is an HR platform built for European small and mid-sized companies, and
it's dominant in the DACH region. Boards live at <slug>.jobs.personio.com, and
the useful endpoint is an XML feed rather than a JSON API. It's an unusually rich
feed — Personio publishes seniority, schedule, years of experience and a genuine
keyword list as structured tags — and it has one failure mode that looks exactly
like success.
Try it#
curl -G https://api.hyperjobs.io/v1/jobs \
-H "Authorization: Bearer $HYPERJOBS_KEY" \
--data-urlencode "source=personio" \
--data-urlencode "country=Germany" \
--data-urlencode "limit=2"{
"data": [
{
"id": "personio:1284517",
"title": "Machine Learning Engineer (m/w/d)",
"company": {
"slug": "nordstern-tech",
"name": "Nordstern Tech",
"domain": "nordstern.de",
"website": "https://nordstern.de",
"logo": "https://media.licdn.com/dms/image/…",
"industry": "Software Development",
"employee_count": 180
},
"location": {
"remote": true,
"countries": ["Germany"],
"cities": ["Hamburg"],
"regions": null,
"raw": ["Hamburg, remote"]
},
"employment_type": ["FULL_TIME"],
"work_arrangement": "Remote",
"seniority": "Mid-Senior level",
"experience_level": "2-5",
"job_language": "de",
"salary": {
"min": 65000,
"max": 85000,
"currency": "EUR",
"unit": "YEAR",
"annual_min": 65000,
"annual_max": 85000,
"summary": null
},
"skills": ["Machine Learning", "LLM", "Python"],
"taxonomies": ["Software"],
"apply_url": "https://nordstern.jobs.personio.com/job/1284517",
"url": "https://nordstern.jobs.personio.com/job/1284517",
"source": "personio",
"posted_at": "2026-07-08T10:22:00.000Z",
"expires_at": null,
"updated_at": "2026-07-13T19:44:02.000Z"
}
],
"total": 2,
"limit": 2,
"offset": 0
}How Personio boards actually work#
One GET, no auth, no pagination, the whole board as XML:
curl "https://<slug>.jobs.personio.com/xml"<slug> is the subdomain. The .de host serves byte-identical content, so
<slug>.jobs.personio.de/xml is the same feed and not a separate board.
There's also a /search.json, and it's worse: the XML feed carries createdAt,
seniority, schedule, employmentType and yearsOfExperience that
search.json simply doesn't have — including any posting date at all. The <id>
is identical between the two, so switching costs nothing.
The document is a list of <position> blocks. Per position: <id>, <name>,
<office> (repeatable), <department>, <recruitingCategory>,
<employmentType>, <schedule>, <seniority>, <yearsOfExperience>,
<keywords>, <salaryInformation> and a set of <jobDescription> sections.
The description is titled sections#
<jobDescription> repeats, each with a <name> and a <value>. The name is a
heading — Your tasks, Ihr Profil, Benefits — and the value is HTML. You
reassemble them in order, and the section names are how you find perks without an
AI pass: the section named some variant of Benefits / Vorteile / Wir bieten /
We offer / Avantages is the benefits list.
<keywords> is real structured skills#
A comma-separated tag list — AI Engineer,Machine Learning,LLM,Python — verified
at 66% fill live. That's a dedicated skills field, which almost no ATS in this
pool has. Recruitee and Pinpoint make you mine
<li> bullets and hope; Personio just tells you.
<salaryInformation> is plain units#
min, max, currencyCode and type, in whole units — no cents divide like
Greenhouse. Fill is about 13% overall, but up to 88% on a
single company's board. That spread is worth internalising: pay transparency
here is a per-employer decision, not a platform average, so a sample from one
board tells you nothing about the source.
What bites you#
The feed is not the complete source it looks like
Some boards emit <jobDescriptions></jobDescriptions> empty for part of
their positions — one live board had 14 of 15 blank — while the posting page
carries the full body in its JobPosting JSON-LD. The feed returns 200 with a
well-formed document and a hole in it.
Most boards fill every one and this never fires. That's what makes it dangerous: you'll test on a healthy board, ship, and lose most of the descriptions on a board you never looked at. We detect the blanks and recover only those rows from the posting page, so a healthy board pays nothing.
Personio has no workplace-type field. No remote flag, no arrangement enum.
What it has is <office> values that are frequently the arrangement itself —
Remote, Hybrid, Remote Germany, Hamburg, remote. The office field is
doing double duty. Reading an arrangement out of it recovers about 24% of
postings including hybrid, and the city still parses out of the same string. It's
a workaround for a missing field, and it only works because employers keep typing
the arrangement where the office goes.
<keywords> leaks raw HTML entities. Sales & acquisition,
"Field Service", R&D. Store them undecoded and your skill tags
contain literal &.
<seniority> is a clean English enum even on German boards.
experienced, entry-level, student — it doesn't get localised, which is
convenient and surprising enough to be worth stating.
A position can have several <office> tags. Pass them as an array or a
multi-site job collapses to one city.
Or use ours#
Personio's feed is one request and genuinely pleasant. The reasons to take ours are the hole and the reach.
The hole: descriptions that are silently blank on some boards, recovered from the posting page only where the feed left them empty. The reach: we track 2,463 Personio boards, and Personio publishes no index of its customers — assembling that list is the actual work, not the parsing.
Plus the usual: <keywords> decoded into clean skill tags, the arrangement
recovered from <office>, sections reassembled into one body with benefits
extracted, EUR bands normalised so they compare against USD ones, dedup, expiry by
board diffing, and the company join.
Personio is in the fast refresh tier, attempted every cycle — see coverage.
Every source above normalises onto the same schema, with the hiring company already joined onto each posting.
Get an API key