SAP SuccessFactors Jobs API
Query jobs from SAP SuccessFactors boards through one API with ?source=successfactors — plus how SAP's sitemal.xml feed really behaves.
SAP SuccessFactors is the HR suite most of the SAP-shaped enterprise world runs
on, and its careers sites are served by a product called Recruiting
Marketing (formerly Jobs2Web, which is why so many of its hosts still carry
that name). It's one of the largest sources in this dataset at 82,140 postings —
a number that moves hourly, so read it from
/v1/meta rather than trusting this sentence.
It is also, by a distance, the cheapest enterprise platform in this pool to collect: one unauthenticated GET returns a tenant's entire board with full descriptions.
Try it#
curl -G https://api.hyperjobs.io/v1/jobs \
-H "Authorization: Bearer $HYPERJOBS_KEY" \
--data-urlencode "source=successfactors" \
--data-urlencode "time_frame=7d" \
--data-urlencode "limit=2"{
"data": [
{
"id": "successfactors:R-0041882",
"title": "SAP Basis Administrator",
"company": {
"slug": "meridian-industries",
"name": "Meridian Industries",
"domain": "meridianindustries.com",
"website": "https://meridianindustries.com",
"logo": "https://media.licdn.com/dms/image/…",
"industry": "Chemicals",
"employee_count": 18500
},
"location": {
"remote": false,
"countries": ["Germany"],
"cities": ["Walldorf"],
"regions": null,
"raw": ["Walldorf, Germany"]
},
"employment_type": null,
"work_arrangement": null,
"seniority": null,
"salary": null,
"skills": ["sap basis", "hana"],
"taxonomies": ["IT"],
"apply_url": "https://jobs.meridianindustries.com/job/Walldorf-SAP-Basis-Administrator/R-0041882",
"url": "https://jobs.meridianindustries.com/job/Walldorf-SAP-Basis-Administrator/R-0041882",
"source": "successfactors",
"posted_at": "2026-07-11T06:22:10.000Z",
"expires_at": null,
"updated_at": "2026-07-14T06:22:10.000Z"
}
],
"total": 2,
"limit": 2,
"offset": 0
}posted_at and expires_at are the interesting fields on this source, and both
are explained below. Neither is what it looks like.
How SuccessFactors boards actually work#
One request per tenant. That's the whole thing:
curl "https://<recruiting-marketing-host>/sitemal.xml"Yes, `sitemal.xml`
Not sitemap.xml. sitemal.xml is SAP's real path, and it is not a typo on
this page — it's a typo in the product, shipped and now load-bearing. It's an
RSS 2.0 feed rather than a sitemap: full <description> bodies plus
Google-Merchant g: fields, unauthenticated, no pagination, no detail call.
The host is the employer's own careers domain (jobs.<company>.com,
<company>.jobs2web.com, or a vanity name), which means there is no shared API
host and no global index. Every tenant is found individually.
What each <item> carries:
| Field | What it holds |
|---|---|
description | The full posting body, not a teaser |
g:id | The requisition id |
g:employer | The real employer name |
g:location | Positional CSV: City[, State], ISO-CC[, postal] |
g:salary | Comp as posted, where posted |
g:job_function | A clean category, ~93% fill |
The <title> is Engineer II (Middlesex, GB) — title and location fused into one
string, so the location has to be split back out of the parentheses. Nested parens
happen (Title (City (0200), GB)), so a lazy match splits it wrong.
What bites you#
The feed has no date tag at all
There is no pubDate on a SuccessFactors item. Not "sometimes missing" —
absent, on every job and every tenant. So a posting's real publish date is
simply not in the feed, and the only honest thing to do is record when we first
saw it.
That means posted_at on this source is a first-seen time, not SAP's
datePosted. Recovering the true date would cost a detail fetch per posting,
which is a steep price for a field that's already close for anything inside the
freshness window. It's a source limitation, and it's worth knowing before you
build a "posted in the last 24h" chart on it.
g:expiration_date is a lie, and we deliberately don't read it. It's present,
it parses, and it's a synthetic constant: now + 30 days, on every job, on
every tenant. It's a rolling default, not a real posting expiry. Store it and you
ship an expires_at that is always exactly a month out and means nothing. Ours is
null here unless the platform actually declared something.
g:location needs cleaning to resolve. It's positional, and it arrives as
Clearwater, FL, US, 33762 or Walldorf, DE. The trailing token is sometimes a
postal code and the one before it is an ISO-2 country. Feed that string to a
geocoder as-is and the postal code and the bare country code both fuzz the match.
We drop the postal token and expand DE to Germany so the country is
deterministic rather than guessed.
The board token isn't the employer. g:employer is, and it's on every item.
The host is often a vanity domain that says nothing about the legal entity behind
it.
Or use ours#
This is the one enterprise platform where "just fetch it" is nearly true. One GET, no auth, no pagination, no N+1, full bodies. Descriptions land on 100% of the SuccessFactors rows we serve.
So the pitch isn't the fetch. It's that the feed doesn't publish a date and
publishes a fake expiry, that its locations need decomposing before they resolve,
that the useful employer name is a g: extension rather than anything standard,
and that there is no list of tenants anywhere — every one of the boards behind
these 82,140 postings was found individually, because SAP publishes no index of
who its customers are.
You also get these rows deduplicated against the same job on LinkedIn or an employer's other boards, and the company join.
SuccessFactors sits in the heavy refresh tier, attempted every third cycle — see coverage.
Query it with ?source=successfactors.
Quickstart · Pricing
Every source above normalises onto the same schema, with the hiring company already joined onto each posting.
Get an API key