HiringThing Jobs API
Query jobs from HiringThing boards through one API with ?source=hiringthing — plus how HiringThing's RSS feed really behaves.
HiringThing is a white-label applicant tracking system. Small businesses buy it
directly, but a lot of its footprint arrives the other way round: HiringThing is
the hiring engine other software companies rebrand and sell as their own, so a
board can be sitting under a name that isn't HiringThing's. The tell is the host.
Every tenant lives at <tenant>.hiringthing.com, whatever the logo says.
Each tenant publishes an RSS feed that carries every open job with its full description inline. One unauthenticated call, no token, no detail pass, no pagination — the best per-request economics in this corner of the pool. On the board we tested it was 15 jobs, all 15 with descriptions. Fifteen. That is not a number worth writing a scraper for, and it's the point of this page.
How HiringThing boards actually work#
One GET, no auth, the whole board:
curl "https://<tenant>.hiringthing.com/api/rss.xml"<tenant> is the subdomain. The response is ordinary RSS: an <item> per job,
with <title>, a <link> to the posting, and a <location> element. The link
looks like /job/23414/candidate-pool, and the number in it is the job id —
tenant-local, so two HiringThing boards can both have a job 23414 and you need
the tenant in your key or they'll collide.
The description is a decoy#
This is the one thing that matters on this platform, and it is the kind of bug that ships and stays shipped.
The standard RSS <description> is truncated at around 200 characters and
ends in an ellipsis. The full body lives in <media:description>, in the
Yahoo Media RSS namespace, and the difference is not subtle:
| Element | Length on the board we measured |
|---|---|
<description> | 193 characters |
<media:description> | 664 characters |
We cross-checked media:description against the same posting's detail-page
JSON-LD and they matched exactly, character for character, at 4,555 each. So
media:description is the real body, not another partial.
Nothing alerts you here. The feed returns 200. <description> is populated, is
plausible prose, and reads like a job description because it is the first
paragraph of one. Every fill-rate check you run says 100%. You just ship
truncated jobs forever, and the field you'd use to notice is the field that's
lying.
What bites you#
An invalid tenant returns 403, not 404. Which is convenient once you know
it — a forbidden means the subdomain isn't a board, so slugs are cheap to verify
in bulk — and misleading if you don't, because 403 reads as "we're blocked" and
sends you off to buy proxies for a tenant that never existed.
There is no posting date. The feed exposes no publish date at all, so
posted_at is null on this source. Same gap as Pinpoint and
Trakstar Hire: a time_frame filter can only work off when
we first saw the job, not when the employer posted it.
A board tells you the subdomain and not much else. The feed carries no employer identity beyond what's in the tenant name, which for a white-labelled platform is exactly where identity is least reliable. Resolving a HiringThing board to a real company with a domain is a separate problem from reading it, and it's the one that takes the time.
Or use ours#
One unauthenticated GET returns a whole HiringThing board with its bodies inline. If you have a handful of tenants, the curl above is the entire integration and you should just use it — this page exists partly to make that easy.
?source=hiringthing is served, and what it buys you is the part that isn't the
fetch. HiringThing publishes no customer index, every board is its own subdomain,
and the white-labelling means the name on the page frequently isn't the vendor's —
so the board list, and resolving each tenant to a real company with a domain, is
the work. That's what the company join does here. We read
media:description rather than the truncated <description>, so the body you get
is the whole body rather than its first paragraph. And the rows arrive in the same
Job shape as every other source on /ats.
The date gap we can't fill: posted_at is null because the feed never published
one, and we'd rather say so than derive a plausible date you'd then chart. Note
too that /v1/meta → sources is the authority on
what you can filter on — source is a csv filter, not an enum, so a miscased
?source=HiringThing returns an empty array rather than a 400.
Every source above normalises onto the same schema, with the hiring company already joined onto each posting.
Get an API key