Thousands of European employers, most of them in Germany, run their hiring on Personio, and every Personio career site publishes its open positions as one public XML document. Personio documents it so that employers can show their jobs on their own websites, and says no credentials are needed. One request per company. We built a tool on it this month; this is what the feed contains, measured on 21 October 2026 on two employers, the energy company 1KOMMA5° (300 open positions) and the insurer ottonova (10).
One URL per company
https://<company>.jobs.personio.de/xml https://<company>.jobs.personio.com/xml Both domains answer for the same company, and both serve a robots.txt that reads Allow: /. A company name that does not exist returns HTTP 404 with an HTML "Page not found", not an empty feed, so a typo fails loudly, which is the right way round.
The whole board comes back in one response. There is no paging: all 300 of 1KOMMA5°'s positions arrive in a single document.
What a position carries
<workzag-jobs> <position> <id>2800938</id> <subcompany>1KOMMA5° GmbH</subcompany> <office>Berlin</office> <additionalOffices><office>Berlin (Remote)</office></additionalOffices> <department>Marketing</department> <recruitingCategory>1K5° Holding Marketing</recruitingCategory> <name>(Junior) SEO & AI Marketing Manager (m/w/d)</name> <jobDescriptions> ... </jobDescriptions> <employmentType>permanent</employmentType> <seniority>entry-level</seniority> <schedule>full-time</schedule> <yearsOfExperience>1-2</yearsOfExperience> <keywords>join ,organic growth manager,SEO,Organic Growth,Marketing,Einstieg,Junior,Berlin</keywords> <occupation>brand_and_product_marketing</occupation> <occupationCategory>marketing_and_product</occupationCategory> <createdAt>2026-09-17T07:57:12+00:00</createdAt> </position> </workzag-jobs> The root element is workzag-jobs, after Workzag, the company Personio grew out of. Check for it: it is the cheapest way to tell a real feed from an error page.
1. The legal entity carries an internal number
subcompany is the legal entity that is hiring, and on some boards it ends in an internal code: "ottonova Holding AG - 9680", "ottonova Krankenversicherung AG - 9698". Strip a trailing - <digits> before showing it to anyone. On 1KOMMA5°'s board the same field names 32 different entities, from "1KOMMA5° GmbH" to "Heartbeat AI GmbH" and regional installer companies, which is useful: it tells you which part of a group is hiring.
2. Remote is written into the office name
There is no remote flag. Remote roles show up as offices: "Remote", "Baden-Württemberg (Remote)", "Niedersachsen (Remote)". 85 of the 310 positions had one. Read office and every additionalOffices/office together; 118 of the 310 positions listed more than one place, up to five.
3. The description is several sections of styled HTML
jobDescriptions holds a list of jobDescription elements, each with a name (the section heading the employer chose) and a value in CDATA. The HTML is pasted from an editor, with a full inline font stack on almost every phrase:
<span style="font-family:Inter, '-apple-system', 'system-ui', ... ;font-size:14px;...">Bei </span> <strong style="...">1KOMMA5°</strong> Join the sections with their headings, then reduce to text. After that the median description was about 2,800 characters. Do not keep the HTML: it is mostly styling.
4. createdAt is when the posting was created, not when it was last open
72 of 1KOMMA5°'s 300 open positions were created in 2022, 2023 or 2024 and are still listed: evergreen roles for installers and electricians, re-used for every hiring round. Eight postings on the two boards were open applications ("Initiativ bewerben (m/w/d)", "Initiativbewerbung"). If you sort by createdAt to find what is new, you will see neither the evergreen roles nor that they are still hiring. Track ids between runs instead and you get both: a position you have not seen before is new, whatever its date.
5. Pay has its own element, and sometimes only the text has it
Personio's developer page does not list it, but the feed carries a salaryInformation block when the employer fills it in:
<salaryInformation> <min>36000.00</min><max>50000.00</max> <currencySymbol>€</currencySymbol><currencyCode>EUR</currencyCode> <type>yearly</type> </salaryInformation> type is yearly, monthly or hourly, and max is sometimes missing. Across six companies and 513 open positions it was filled on 40, mostly monthly figures. Four more stated pay only in the description, the German way: "zwischen 55.000 € und 75.000 €", "von 67.300,00 € bis 74.400,00 €". A dot for thousands and "bis" or "und" between the figures, so a parser written for "$55,000 - $75,000" sees nothing. Most ads say "attraktive Vergütung" (attractive pay) with no figure at all; leave those empty rather than guessing.
The tool
Personio Jobs Scraper on Apify Store does the above: company names or career-site URLs in, one request per company, filters on title, place and date applied before anything is saved, and 30 fields per job out as JSON with the full description as text. The two boards above, 310 jobs, took 3 seconds. With several companies and a row cap, each company gets an equal share. A monitor mode returns only new, changed and closed positions: on 1KOMMA5°'s board the first run returned 300 jobs and the next one 0, and the second run billed nothing. $1 per 1,000 jobs.
It returns the same rows as our Career Page Jobs Scraper, which reads twelve career-page systems including Greenhouse, Lever, Workday and SmartRecruiters, so Personio jobs drop into the same table as everything else.
