# HiringThing Jobs API

> Query jobs from HiringThing boards through one API with ?source=hiringthing — plus how HiringThing's RSS feed really behaves.

Source: https://hyperjobs.io/ats/hiringthing

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:

```bash
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](/ats/pinpoint) and
[Trakstar Hire](/ats/recruiterbox): 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](/docs/objects/company) 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`](/docs/objects/job) shape as every other source on [`/ats`](/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`](/docs/api-reference/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`.
