Personio Jobs Scraper
Pricing
from $1.50 / 1,000 results
Personio Jobs Scraper
Scrape every open job posting from any Personio-powered company career page via Personio's own public XML feed, no login or browser required. Get job titles, offices, departments, and full descriptions instantly for recruiter pipelines or as a DACH-region hiring-intent signal for sales and BD teams.
Pricing
from $1.50 / 1,000 results
Rating
0.0
(0)
Developer
DevilScrapes
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
17 hours ago
Last modified
Categories
Share
🎯 What this scrapes
Personio is Germany's leading HR/recruiting SaaS, and every company that uses it to publish open roles gets a career page at https://{company}.jobs.personio.de. Behind that page sits a public, unauthenticated XML feed (legacy workzag-jobs branding) that lists every currently-open position for that employer — job title, office, department, employment type, seniority, years of experience, keywords, and the full job description split into named sections.
This Actor walks that feed for any list of company subdomains you supply, resolves full career-page URLs down to the bare subdomain automatically, and emits one normalised, Pydantic-validated row per open position. It absorbs the retry/backoff and per-company failure isolation this feed needs in production so one bad subdomain never derails the rest of your run.
🔥 Features
- 🛡️ We rotate browser fingerprints —
curl-cffiimpersonates real Chrome / Firefox TLS handshakes so the target sees a browser, not a bare Python client. - 🔁 We retry with exponential backoff on
408 / 429 / 5xx— up to 5 attempts per company,Retry-Afterhonoured. - 🌐 We rotate Apify Proxy sessions on every block — fresh session ID, fresh exit IP.
- 🧱 Per-company failure isolation — one company's request failure never stops the rest of the run; the final status message reports exactly how many of N companies returned data.
- 🧊 Clean, typed dataset rows — Pydantic-validated, ISO-8601 timestamps, stable
(company, position_id)keys, JSON / CSV / Excel export straight from the Apify Console. - 📝 Flexible company input — a bare subdomain (
"chrono24") or a fullhttps://{company}.jobs.personio.deURL, with or without the trailing/xmlpath, resolves the same way. - 📄 Optional full HTML descriptions — keep the plain-text description always, or opt into the raw HTML sections for richer downstream rendering.
- 💰 You pay only for results that land. No data → no charge (only the small actor-start warm-up fee).
💡 Use cases
- DACH-region recruiter pipelines — pull every open role across a portfolio of German, Austrian, and Swiss employers into one normalised feed alongside
teamtailor-jobs-scraper,workday-jobs-scraper, and the rest of this fleet's ATS coverage. - Hiring-intent signal for sales and BD teams — track headcount growth, new-market entry, and tech-stack changes visible in job descriptions as a lead-scoring input.
- Job-board aggregators — fold Personio-hosted postings into a multi-ATS aggregator alongside Greenhouse, Lever, Workday, and Teamtailor feeds.
- Competitive intelligence — monitor a competitor's open roles over time to infer strategic priorities (which office is scaling, which department is hiring hardest).
- Academic / labour-market research — a structured, machine-readable sample of DACH-region job postings for wage- or skills-demand studies.
- HR-tech tooling — build a Personio-compatible job board mirror, alert bot, or Slack digest from the same normalised rows.
⚙️ How to use it
- Open the Actor in Apify Console (or call it via the API/SDK).
- Fill in
companieswith one or more Personio subdomains or career-page URLs. - Optionally set
maxJobsPerCompanyandincludeHtml. - Run it. Results land in the default dataset as they're scraped — export to JSON, CSV, or Excel directly from the Console, or pull them via the API.
{"companies": ["chrono24", "urbansportsclub"],"maxJobsPerCompany": 100,"includeHtml": false,"proxyConfiguration": { "useApifyProxy": true }}
📥 Input
| Field | Type | Default | Description |
|---|---|---|---|
companies | array of strings | — (required) | Personio subdomain (e.g. "chrono24") or full https://{company}.jobs.personio.de URL. A custom-domain career page is not resolvable — use the bare Personio subdomain. |
maxJobsPerCompany | integer | 100 | Cap job postings emitted per companies entry, applied after parsing. |
includeHtml | boolean | false | Populate description_html (concatenated job-description sections) for every row. description_text is always populated regardless. |
proxyConfiguration | object | {"useApifyProxy": true} | Apify Proxy configuration. |
📤 Output
One dataset row per open job posting:
| Field | Type | Description |
|---|---|---|
company | string | Normalized input subdomain. |
position_id | integer | Stable within one company's feed; (company, position_id) is the uniqueness key. |
job_title | string | Position title. |
subcompany | string | null | Sub-brand/entity, if the employer splits postings across several. |
office | string | null | Primary office location. |
additional_offices | array | null | Other offices this posting is open to, in feed order. |
department | string | null | Department name. |
recruiting_category | string | null | Personio's recruiting category tag. |
employment_type | string | null | Full-time / part-time / etc. |
seniority | string | null | Seniority level. |
schedule | string | null | Schedule (e.g. flexible, fixed). |
years_of_experience | string | null | Required experience band. |
keywords | array | null | Comma-split keyword tags. |
occupation | string | null | Occupation classification. |
occupation_category | string | null | Broader occupation category. |
created_at | string | ISO-8601 posting creation timestamp. |
job_url | string | Constructed https://{company}.jobs.personio.de/job/{position_id} link. |
description_html | string | null | Raw HTML, only when includeHtml is enabled. |
description_text | string | Plain-text description, always populated. |
scraped_at | string | ISO-8601 wall-clock time this row was scraped. |
{"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"}
💰 Pricing
Pay-Per-Event — you're charged only for what actually runs and lands:
| Event | Price | When |
|---|---|---|
actor-start | $0.005 | Once per run |
result | $0.0015 | Per job posting written to the dataset |
At $0.0015/job that's $1.50 per 1,000 postings plus a flat $0.005 warm-up fee per run — no subscription, no minimum spend, no charge for companies that return zero jobs.
🚧 Limitations
- Personio only. This Actor does not cover Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Teamtailor, or BambooHR career sites — see
multi-ats-jobs-scraper,workday-jobs-scraper,smartrecruiters-jobs-scraper,workable-jobs-scraper, andteamtailor-jobs-scraperfor those, together an "ATS Jobs Intel" suite. - Bare subdomain only. Custom-domain career sites (e.g. a company's own
careers.example.comeven if it's Personio-powered behind the scenes) are not resolvable — supply the{company}.jobs.personio.deform. - No expiry field. Personio's feed doesn't expose a
validThrough/expiry date — only currently-open postings appear at all; there's no way to tell how long a listing has been live beyondcreated_at. - One row per posting.
additional_officesis returned as a list column on that same row, not exploded into extra rows per office. job_urlis a convenience link. It's constructed from the feed'sposition_id, not independently verified by loading every page — in the rare case a company's job pages differ, treat it as a best-effort deep link.- Live-only. No historical or removed postings — only what the feed currently returns.
❓ FAQ
Do I need a Personio account or API key? No. The XML feed backing this Actor is public and unauthenticated — you only need the company's subdomain.
What if I paste a full career-page URL instead of a subdomain?
Both work. https://chrono24.jobs.personio.de and chrono24 resolve to the same company.
What happens if a subdomain has no Personio board? The company is treated as returning zero jobs — the run doesn't fail, and the final status message says how many of your companies returned data versus how many failed outright.
Can I get the raw HTML description instead of plain text?
Yes — set includeHtml to true. The plain-text description_text field is always populated either way.
How fresh is the data? Each run fetches the feed live at request time — there's no caching layer between your run and Personio's own board.
🐛 Your feedback
Found a Personio board this Actor doesn't handle right, or want a field added? Open an issue on the Actor's Apify Store page or reach out via https://apify.com/DevilScrapes — we read every report.