Personio is the default ATS across DACH
If you're doing anything with hiring data in Germany, Austria, or Switzerland, Personio comes up constantly — it's the leading HR/recruiting SaaS across the DACH region, and every company using it to publish open roles gets a careers page at {company}.jobs.personio.de. That page is backed by a public, unauthenticated XML feed (still carrying its legacy workzag-jobs branding under the hood) listing every currently-open position: title, office, department, employment type, seniority, years of experience, and a full job description split into named sections.
There was no dedicated Personio Actor on the Apify Store as of when I built this, despite Personio's footprint being large enough to matter for anyone doing DACH-region recruiting or labor-market research. So I built one under Devil Scrapes: point it at a list of company subdomains and get one clean row per open posting, across as many companies as you want in a single run.
What the data looks like
A real row from the dataset:
{
"company": "chrono24",
"position_id": 1234567,
"job_title": "Senior Backend Engineer",
"subcompany": null,
"office": "Karlsruhe",
"additional_offices": null,
"department": "Engineering",
"recruiting_category": "Tech",
"employment_type": "Full-time",
"seniority": "Senior",
"schedule": null,
"years_of_experience": "3-5 years",
"keywords": ["backend", "python", "aws"],
"occupation": "Software Development",
"occupation_category": "IT",
"created_at": "2026-06-01T09:00:00",
"job_url": "https://chrono24.jobs.personio.de/job/1234567",
"description_html": null,
"description_text": "We are looking for a Senior Backend Engineer to join our team ...",
"scraped_at": "2026-07-26T12:00:00Z"
}
description_text is always populated regardless of settings; description_html only fills in when you opt into the raw HTML sections — useful if you're rendering postings somewhere downstream rather than just reading them.
The naive approach and why it's more work than it looks
Personio's XML feed is genuinely public and well-structured, which makes it tempting to think a quick script will do. It mostly will — until you try running it across more than a handful of companies:
- Feed availability varies per subdomain. Some companies have deactivated their board, some never had one; your loop needs to treat "zero postings" as a normal outcome and keep going, not as a reason to stop the batch.
- Company input normalization. People will hand you a bare subdomain, a full URL, a URL with or without the trailing path — resolving all of those to the same identifier cleanly is fiddly enough that it's easy to get wrong on the first pass.
- Fingerprinting and pacing. A bare Python client doesn't look like a browser at the TLS layer, and firing requests across dozens of companies in a tight loop is exactly the pattern that starts drawing attention over time.
The Actor
Personio Jobs Scraper resolves any of those input formats to the right subdomain automatically, walks the XML feed for every company you list, and isolates failures per company so one bad subdomain never derails the rest of the run. We rotate Chrome/Firefox TLS fingerprints via curl-cffi and retry 408/429/5xx with exponential backoff up to 5 attempts, honoring Retry-After when the target sends one.
from apify_client import ApifyClient
client = ApifyClient("APIFY_TOKEN")
run = client.actor("DevilScrapes/personio-jobs-scraper").call(run_input={
"companies": ["chrono24", "urbansportsclub"],
"maxJobsPerCompany": 100,
"includeHtml": False,
"proxyConfiguration": {"useApifyProxy": True},
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["job_title"], item["office"], item["seniority"])
Or paste the same JSON into the Apify Console input form for a quick one-off pull without writing any code.
What you'd actually use this for
- DACH-region recruiter pipelines — pull every open role across a portfolio of German, Austrian, and Swiss employers into one normalized feed, alongside our Teamtailor and Workday coverage.
- Hiring-intent signal for BD/sales teams — watch which offices and departments a target account is scaling before it's public news.
- Job-board aggregation — fold Personio postings into a multi-ATS feed alongside Multi-ATS and SmartRecruiters coverage.
- Labor-market research — a structured, machine-readable sample of DACH-region postings for wage- or skills-demand studies.
- Competitive intelligence — track a rival's open roles over time to infer which office or team is actually scaling.
Pricing, honestly
$0.005 per run flat, plus $0.0015 per posting written to the dataset. A 1,000-posting pull costs $1.505 total. A company with zero current openings costs nothing beyond the warm-up fee — you don't pay for rows that never existed. Apify's $5 free trial credit for new accounts covers roughly 3,300 postings before you'd spend anything yourself.
Why this is more interesting than it looks
The part that took actual iteration wasn't the XML parsing itself — it was normalizing Personio's inconsistently-populated optional fields (subcompany, schedule, additional_offices) across companies that each configure Personio slightly differently, without inventing a value the source feed never actually provided. Getting a stable schema that holds across an arbitrary set of DACH employers, most of whom you'll never have tested against directly, is most of the real engineering here.
Honest limitations
This Actor covers Personio only — Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Teamtailor, and BambooHR live in sibling Actors. It only resolves the bare {company}.jobs.personio.de subdomain form — a company fronting Personio behind its own careers.example.com domain isn't resolvable this way. There's no expiry field either; Personio's feed only ever shows currently-open postings, with no way to tell how long a listing has been live beyond its created_at timestamp. And it's a live-only snapshot — schedule recurring runs if you want to track change over time.
Try it
Personio Jobs Scraper is live on the Apify Store at $1.50 per 1,000 results, with Apify's standard $5 free trial credit and no card required to start. The devil's in the data — and it's one of several ATS-coverage Actors we run under Devil Scrapes, alongside BambooHR, Ashby, Recruitee, and Breezy HR.
If you're tracking hiring across DACH and hitting a Personio board this doesn't handle right, tell me in the comments — that's exactly the feedback that ends up in next week's fix.
Top comments (0)