Personio Jobs Scraper icon

Jobs

Personio Jobs Scraper

Scrape every open job posting from any Personio-powered company career page via Personio's own public, unauthenticated XML feed — titles, offices, departments, seniority, and full descriptions, one request per company, no login or browser required.

Free Apify credit covers a first run. No credit card to try.

What this Actor 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.

What we handle for you

  • 🛡️ 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.

Input

Paste this into the Apify Console, or send it as the run input over the API. Proxy settings are on by default; you rarely need to touch them.

FieldTypeRequiredWhat it does
companies array yes Personio company subdomain, e.g. "chrono24" (the employer identifier in https://{company}.jobs.personio.de/), or a full https://{company}.jobs.personio.de URL (with or without a trailing /xml path) — the subdomain is…
maxJobsPerCompany integer no Cap job postings emitted per companies entry, applied after parsing.
includeHtml boolean no When enabled, populate description_html (concatenated jobDescription sections) for every emitted row. description_text (HTML-tag-stripped) is always populated regardless.
{
  "companies": [
    "chrono24",
    "urbansportsclub"
  ],
  "maxJobsPerCompany": 100,
  "includeHtml": false,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}

Output

One row per result, schema-validated before it is written. Export JSON, CSV, Excel or XML from the run, or read it over the API.

companyposition_idjob_titlesubcompanyofficeadditional_officesdepartmentemployment_typeseniorityscheduleoccupationcreated_atjob_urldescription_htmldescription_text

{
  "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

EventPriceWhen
Actor start$0.20Once per run, covers warm-up and proxy session setup.
Result emitted$0.0015Per result written to the dataset.

You pay only for results that land. Cap any run with maxTotalChargeUsd. See pricing & billing for worked examples.

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.

Ready to run it?

Open the listing on Apify, paste the input above, and watch rows land. If it ever breaks, it is our problem before it is yours.

Related Actors

Teams that run this also run