# HireBridge Jobs API

> Query jobs from HireBridge boards through one API with ?source=hirebridge — plus how HireBridge's list.aspx pages really behave.

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

HireBridge is a long-running recruitment platform serving small and mid-sized
employers, with boards hosted centrally at `recruit.hirebridge.com`. The boards
are small, the surface is ASP.NET pages rather than an API, and the volume is
low: across the tenants we've driven, boards ran 42, 2, 51, 41 and 49 jobs.

There's no JSON anywhere. You are parsing markup, and the markup is the contract.

## What the adapter reads

```bash
# List — the whole board, one page, no paging
curl "https://recruit.hirebridge.com/v3/jobs/list.aspx?cid=<cid>"

# Detail — one job
curl "https://recruit.hirebridge.com/v3/Jobs/JobDetails.aspx?cid=<cid>&jid=<jid>"
```

The board token is a **numeric `cid` query param**, not a subdomain — worth
noting if your board store assumes tokens are hostnames. The `locvalue` and `bid`
params you'll see in the board's own hrefs are not needed.

The list is complete on one page, so there's no pagination to get wrong. The body
is detail-only, which makes it a hard N+1 against a platform that does not want
to be fetched quickly.

## What bites you

### Burst-429s that arrive as empty bodies, not errors

About **15 rapid requests** earns an HTTP 429. That alone would be fine. The
problem is what a 429 looks like downstream: a response with status 429 and an
**empty body** — and an empty body parses cleanly as "this board has no jobs".

<Warning title="This is the failure mode to actually plan for">
  Rate limiting on this platform does not present as an error. It presents as a
  company that isn't hiring. If your fetch layer doesn't retry, and your parser
  treats an empty document as an empty board, a HireBridge run "succeeds" while
  silently expiring every job on every board it touched.

  It needs explicit pacing *and* backoff — throttle below the burst budget so you
  don't earn the 429, and retry with a real wait when you do anyway. The same
  failure gave Paylocity a 14% description rate elsewhere in this pool before
  anyone noticed.
</Warning>

### The list is grouped by location, and the rows aren't

The job rows carry no location at all. The location comes from a
`<span class="groupbyname-simple">` **header** that precedes each group of jobs.
Parse the rows on their own and every posting is location-less; parse them in
document order, carrying the last header seen, and they aren't.

Those headers are also tenant-formatted, and some of them are store addresses
rather than places:

```
"Bogota, Colombia"                              → fine as-is
"AL - Birmingham - JA #07 (3320 Galleria Cir)"  → the store code and street are noise
"AL - East South Blvd - #MNT001 (2805 East…)"   → "#MNT001" parsed as the CITY
```

The parenthesised address and the `#`-code segments have to go before a geocoder
sees them, and the same cleanup has to run on the detail's fallback field too —
rows with no group header take that path, and uncleaned it produced exactly the
`#MNT001`-as-a-city result above.

### `jobdesc` contains nested `</span>` tags

The description lives in a `<span id="…_jobdesc">`, and the body inside it has
spans of its own. The non-greedy regex everyone reaches for —
`<span id="…_jobdesc">([\s\S]*?)</span>` — stops at the **first inner close** and
leaks the rest of the posting into whichever field gets read next. You have to
depth-count the spans, not pattern-match them.

### `og:description` is a teaser wearing a description's clothes

It's about 200 characters, it's populated on every posting, and it looks like
exactly the field you want. It isn't. The real body is in `jobdesc`, and the
requirements are in a **separate** `jobreq` span that is often half the posting —
take one without the other and you've stored a job ad with no requirements in it,
at full apparent fill. `div#divCompanyInfo` sits next to the body and is company
boilerplate rather than part of the posting.

### There is no posting date. Anywhere.

Checked across 10 postings on 5 tenants: HireBridge exposes no publish date on
the list, on the detail, or in any meta tag. `posted_at` on a HireBridge row
cannot come from HireBridge. That's a real, permanent gap in what this platform
can tell you, and no amount of parsing fixes it.

Descriptions themselves came back at 10/10 across the 5 tenants tested, once
`jobdesc` and `jobreq` are joined and the burst-429 is paced around.

## Where this stands

`?source=hirebridge` is served today, and we'd rather set expectations correctly
than sell it: HireBridge is small boards, HTML parsing and low volume, and its
tenants are identified by bare numeric cids that carry no employer name at
source — which makes the [company join](/docs/objects/company) hard here in a way
it isn't on platforms that publish a name. It's covered because breadth is the
product, not because it's where the jobs are.

The keys of [`/v1/meta`](/docs/api-reference/meta)`.sources` are the live
vocabulary — the values `?source=` actually accepts, with counts, and the only
thing worth coding against. [Sources & coverage](/docs/reference/sources) says the
same in prose.

See [all platforms](/ats) · [quickstart](/docs/quickstart).
