Personio Jobs Scraper avatar

Personio Jobs Scraper

Pricing

from $1.50 / 1,000 results

Go to Apify Store
Personio Jobs Scraper

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

DevilScrapes

Maintained by Community

Actor 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 fingerprintscurl-cffi impersonates 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-After honoured.
  • 🌐 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 full https://{company}.jobs.personio.de URL, with or without the trailing /xml path, 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

  1. Open the Actor in Apify Console (or call it via the API/SDK).
  2. Fill in companies with one or more Personio subdomains or career-page URLs.
  3. Optionally set maxJobsPerCompany and includeHtml.
  4. 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

FieldTypeDefaultDescription
companiesarray 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.
maxJobsPerCompanyinteger100Cap job postings emitted per companies entry, applied after parsing.
includeHtmlbooleanfalsePopulate description_html (concatenated job-description sections) for every row. description_text is always populated regardless.
proxyConfigurationobject{"useApifyProxy": true}Apify Proxy configuration.

📤 Output

One dataset row per open job posting:

FieldTypeDescription
companystringNormalized input subdomain.
position_idintegerStable within one company's feed; (company, position_id) is the uniqueness key.
job_titlestringPosition title.
subcompanystring | nullSub-brand/entity, if the employer splits postings across several.
officestring | nullPrimary office location.
additional_officesarray | nullOther offices this posting is open to, in feed order.
departmentstring | nullDepartment name.
recruiting_categorystring | nullPersonio's recruiting category tag.
employment_typestring | nullFull-time / part-time / etc.
senioritystring | nullSeniority level.
schedulestring | nullSchedule (e.g. flexible, fixed).
years_of_experiencestring | nullRequired experience band.
keywordsarray | nullComma-split keyword tags.
occupationstring | nullOccupation classification.
occupation_categorystring | nullBroader occupation category.
created_atstringISO-8601 posting creation timestamp.
job_urlstringConstructed https://{company}.jobs.personio.de/job/{position_id} link.
description_htmlstring | nullRaw HTML, only when includeHtml is enabled.
description_textstringPlain-text description, always populated.
scraped_atstringISO-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:

EventPriceWhen
actor-start$0.005Once per run
result$0.0015Per 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, and teamtailor-jobs-scraper for those, together an "ATS Jobs Intel" suite.
  • Bare subdomain only. Custom-domain career sites (e.g. a company's own careers.example.com even if it's Personio-powered behind the scenes) are not resolvable — supply the {company}.jobs.personio.de form.
  • 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 beyond created_at.
  • One row per posting. additional_offices is returned as a list column on that same row, not exploded into extra rows per office.
  • job_url is a convenience link. It's constructed from the feed's position_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.