SmartRecruiters Posting API and Personio XML feed: public job endpoints in depth
SmartRecruiters lists a company's jobs at api.smartrecruiters.com/v1/companies/{id}/postings and Personio publishes an XML feed at {company}.jobs.personio.de/xml; both are public, need no key, and cover about a fifth of the employers on the boards we track. Source: Arcadium Data, data as of .
After Greenhouse, Lever and Ashby (see the job board API guide), two systems matter most for European employers: SmartRecruiters, strong in mid-size and large companies worldwide, and Personio, the default HR system of German-speaking small and mid-size firms. Together they hold the boards of roughly 4,400 employers in our directory.
The two work in opposite ways. SmartRecruiters is a paged JSON API with a second call per job for the text. Personio is one XML file for the whole company. Each has a trap that wastes a day if you do not know it.
The official routes: two public endpoints
Check the rules first
SmartRecruiters documents its Posting API for building career sites, and the host api.smartrecruiters.com serves a robots.txt that disallows generic crawlers. We treat robots.txt as a signal, not a ruling, and read only public postings, one company at a time, slowly, without personal data. You should decide for your own use with your own legal advice, and keep the volume low. Personio's feeds are meant to be consumed by job aggregators; its terms for the careers pages are on its site.
SmartRecruiters: the Posting API
GET https://api.smartrecruiters.com/v1/companies/{companyIdentifier}/postings?limit=100&offset=0 returns totalFound and a content array of up to 100 postings. Each row has the posting id, name (title), releasedDate, location (city, region, country, remote, hybrid, fullLocation), department, function, typeOfEmployment, experienceLevel and refNumber. The list has no description. One more call, GET .../postings/{id}, returns jobAd.sections (job description, qualifications, additional information) and the public postingUrl and applyUrl.
curl
curl -s "https://api.smartrecruiters.com/v1/companies/Bosch/postings?limit=1" | head -c 700
# one posting with its text
curl -s "https://api.smartrecruiters.com/v1/companies/Bosch/postings/<posting id>" | head -c 700
Python
import time
import requests
BASE = "https://api.smartrecruiters.com/v1/companies"
def sr_jobs(company: str):
offset = 0
while True:
page = requests.get(f"{BASE}/{company}/postings", params={"limit": 100, "offset": offset}, timeout=60).json()
rows = page.get("content", [])
yield from rows
offset += len(rows)
if len(rows) < 100 or offset >= page.get("totalFound", 0):
return
time.sleep(0.5)
for p in sr_jobs("Bosch"):
print(p["id"], p["name"], p["location"].get("country"), p["releasedDate"])
break
Traps that cost time:
- Unknown ids answer 200. A company that does not exist returns
totalFound: 0and an empty list, not 404. Count a board as found only when it has at least one job. - Ids are case-sensitive.
boschandBoschare different; try the capitalised form too. - Recruiter names. Postings carry a
creatorobject with a person's name. Do not store it. - One request per job for text. A board of 1,000 jobs is 10 list calls plus 1,000 detail calls. Fetch details only for new ids and keep the rest from the previous run.
- Country filters. The API supports filters such as
q,country,cityanddepartment; check the current documentation for the full list.
Personio: the XML feed
GET https://{company}.jobs.personio.de/xml?language=en (or .jobs.personio.com for companies outside the German region) returns every open position in one XML document: <workzag-jobs> containing <position> elements with id, name, office, additionalOffices, department, recruitingCategory, employmentType, schedule, seniority, subcompany, createdAt and jobDescriptions (name and HTML value pairs). The public page of a job is https://{company}.jobs.personio.de/job/{id}.
curl
curl -s "https://personio.jobs.personio.de/xml?language=en" | head -c 800
Python
import xml.etree.ElementTree as ET
import requests
def personio_jobs(company: str, tld: str = "de"):
r = requests.get(f"https://{company}.jobs.personio.{tld}/xml", params={"language": "en"}, timeout=60)
if r.status_code != 200 or "<position>" not in r.text:
return
for pos in ET.fromstring(r.content).iter("position"):
yield {
"id": pos.findtext("id"),
"title": pos.findtext("name"),
"office": pos.findtext("office"),
"department": pos.findtext("department"),
"created": pos.findtext("createdAt"),
}
for job in personio_jobs("personio"):
print(job)
Personio traps: the language parameter changes the text, not the set of jobs; the same company can sit on the .de or the .com host, so probe both; a missing company shows an error page or an empty feed, not a clean 404; and the descriptions can contain contact names and email addresses, which you should strip. Rate limits are not published. We read thousands of feeds at about three requests per second without throttling, but a per-IP limit exists in principle, so pace yourself.
Refreshing without re-reading everything
Both systems suit a delta approach. For SmartRecruiters, keep the set of posting ids from the last run, request the list again (cheap, 100 rows a call), and fetch details only for ids you have not seen; ids that vanished are closed jobs. For Personio there is no detail call, but you can compare the position ids and the createdAt values with the previous feed and store only the differences. Run each board at most once or twice a day: postings change slowly, and a larger frequency only adds load for the employer's platform without adding information.
Dates deserve a note. SmartRecruiters releasedDate is the release time of the posting; Personio's createdAt is when the position was created in the system, which can be weeks before it was published. Neither tells you when a job closed, so infer that from the first run in which the id is missing, and keep the first-seen date as your own.
The fast route: both systems in one schema, with market share
Arcadium Data reads SmartRecruiters and Personio boards (with the capitalisation and host probes), strips recruiter names and contact details, and serves jobs from every system in one schema; see the free key section of the job board guide for job search. Without a key, you can see how big each system is among the employers we track:
Calls on this page work without a key from one IP (10 requests a minute, 100 a day, short answers, point lookups). A free key lifts the limits: sign up. Routes, parameters and fields: OpenAPI.
curl
curl -s "https://api.arcadiumdata.io/v1/ats/market?ats=smartrecruiters"
Python
import requests
data = requests.get("https://api.arcadiumdata.io/v1/ats/market?ats=smartrecruiters", timeout=30).json()
print(data["live"]) # today's share among the boards we crawl
for row in data["data"]:
print(row["month"], row["share"], row["employers"])
TypeScript
const res = await fetch('https://api.arcadiumdata.io/v1/ats/market?ats=smartrecruiters');
const data = (await res.json()) as { live: { ats: string; employers: number; share: number }[]; data: { month: string; share: number }[] };
console.log(data.live[0]);
for (const r of data.data) console.log(r.month, r.share);
Response (abridged)
{
"series": "core4",
"liveEmployers": 22446,
"live": [{ "ats": "smartrecruiters", "employers": 1476, "share": 0.0658 }],
"data": [{ "month": "2023-02-01", "ats": "smartrecruiters", "share": 0.1735, "employers": 728 }]
}
The same call with ats=personio returned 2,948 employers, a 13.1% share of the 22,446 live boards. The monthly series starts in 2023 and comes from the Common Crawl archive, not the whole market (read the note field). To see which system one company uses, call GET /v1/companies/{domain}, whose hiring.ats field names it; to see who changed system, GET /v1/ats/switches. More in which ATS does a company use.
Comparison
| SmartRecruiters Posting API | Personio XML feed | Arcadium Data | |
|---|---|---|---|
| Key | None | None | None for market share; free key for job search |
| Format | JSON, 100 per page | One XML document | One job schema |
| Description text | Extra call per job | In the feed | Included |
| Unknown company | 200 with empty list | Error page or empty feed | Board found only with jobs |
| Personal data | Recruiter name | Names and emails in text | Stripped |
| Robots.txt | Disallows generic crawlers on the API host | Feeds made for aggregators | Documented per source |
Get the data without the plumbing
Arcadium Data serves these datasets from one REST API and one MCP server, with sources, licences and dates on every answer. No key is needed to try the routes in this guide.
Related
- Job board APIs: Greenhouse, Lever, Ashby
- Which ATS does a company use
- Companies using an ATS
- Workday career site JSON
- ATS vendors
Sources
Last updated 2026-10-25. This guide is ours and independent of the organisations it describes. Found a mistake? Tell us and we correct it.
Questions
Does the SmartRecruiters API need a key?
The Posting API for a company returns its public postings without a key. The list has no job text, so fetch each posting for the description. Check the current terms and the robots.txt of the API host before you use it at scale.
What is the Personio XML feed URL?
https://{company}.jobs.personio.de/xml?language=en, or the .com host for companies outside the German region. It returns all open positions of the company in one XML document.
Why does SmartRecruiters return an empty list instead of an error?
Unknown company identifiers answer 200 with totalFound 0. Identifiers are also case-sensitive, so try the capitalised form before you decide the board does not exist.
How do I find the SmartRecruiters or Personio board of a company?
Look for links to jobs.smartrecruiters.com or jobs.personio.de/.com on its careers page, or use our company lookup, which returns the system and the board URL.
Can I store recruiter names from job postings?
No. Recruiter names and contact details in descriptions are personal data. Drop the creator object and strip emails and phone numbers at parse time.
Cite as: Arcadium Data, "SmartRecruiters Posting API and Personio XML feed: public job endpoints in depth", https://arcadiumdata.io/guides/smartrecruiters-personio-public-job-api/, data as of 2026-10-25.