BambooHR's public job endpoint has an isRemote field.
Across 50,616 postings from 9,711 live BambooHR boards, not one had it set.
Not "most were blank". Not one. If you write a client that trusts that field, you will conclude
that no company using BambooHR is hiring remotely, and you will be wrong in a way no error message
will ever tell you about.
That is the kind of thing you only find by measuring, so I measured all of it.
What this is
Twelve applicant tracking systems publish an open JSON endpoint that their customers' careers pages
read. No key, no login, no HTML parsing:
curl -s 'https://boards-api.greenhouse.io/v1/boards/stripe/jobs?content=true'
curl -s 'https://api.lever.co/v0/postings/spotify?mode=json'
curl -s 'https://api.ashbyhq.com/posting-api/job-board/notion?includeCompensation=true'
Plenty of articles list these endpoints. What none of them tell you is which fields actually come
back populated, which is the only thing that matters once you start building. "Supports salary"
is not useful. "42.5% of postings carry a pay range, as separate numbers" is.
So I read every live board I could find on each platform — between 17,000 and 179,000 postings per
platform, 1,551,977 in total — and counted.
The table
| Platform | Posting date | Description | Pay | Marked remote | Postings measured |
|---|---|---|---|---|---|
| Workday | 78.2% | no | no | no | 41,683 |
| SmartRecruiters | yes | no | no | 7.7% | 86,464 |
| Greenhouse | yes | yes | no | no | 179,303 |
| Workable | yes | yes | no | 36.3% | 68,849 |
| BambooHR | no | no | no | no | 50,616 |
| Lever | yes | 94.7% | no | 4.8% | 46,005 |
| Breezy HR | yes | no | 45.7% | 15.7% | 47,085 |
| Personio | no | no | no | 8.8% | 41,608 |
| Ashby | yes | yes | 38.9% | 54.2% | 32,407 |
| Recruitee | yes | yes | 26.9% | 9.9% | 21,621 |
| Rippling | no | no | no | 17.4% | 18,701 |
| Pinpoint | no | yes | 42.5% | 8.8% | 17,302 |
yes means 99.5% or more. no means the endpoint carries no such field at all — not that
employers left it blank. Title, company, location and an apply URL come back everywhere.
Salary: four platforms, and the form matters more than the rate
Only four of the twelve publish compensation, and none fills it most of the time. But the split
that actually affects your code is not the percentage — it is whether you get numbers or a sentence.
Numbers you can compute on:
// Pinpoint — separate fields
{ "compensation_minimum": 45000, "compensation_maximum": 55000,
"compensation_currency": "GBP", "compensation_frequency": "annual" }
// Recruitee — same idea
{ "salary": { "min": "4500", "max": "6500", "currency": "EUR", "period": "month" } }
Sentences you cannot:
// Breezy — highest fill rate of all four, at 45.7%
{ "salary": "$28 – $100 / hour" }
// Ashby
{ "compensationTierSummary": "$211.4K – $290.6K • Offers Equity" }
Parsing the second group means guessing at currency, period, and whether equity is folded into the
top number. I do not parse them. They pass through as strings and the schema says so.
Two traps in that group:
-
Recruitee keeps the salary object with zeros when the employer skipped the field. A naive
reader ships
"0 USD hour"as a pay range. Treatmin <= 0 && max <= 0as absent. -
European employers quote monthly.
4500–6500 EUR monthis not an annual figure, and multiplying by twelve to normalise is a guess about holiday allowance and 13th-month pay.
Dates: half the platforms do not have them
Four platforms — Personio, Rippling, Pinpoint and BambooHR — publish no posting date at all.
Workday publishes prose:
{ "postedOn": "Posted 30+ Days Ago" }
"Posted Today" and "Posted 5 Days Ago" convert to a timestamp. "30+" does not, and inventing
one is worse than admitting you do not know. That is why Workday sits at 78.2% rather than 100%.
This is a design decision, not a detail. If you build a "posted in the last 7 days" filter, you
either drop undated rows — silently discarding a third of the market — or keep them, and your
filter means less than it says. There is no third option. Saying which one you chose, in the docs,
is the honest version.
Remote: a real field on five, a regex on the rest
Five platforms let the employer state the work arrangement. The other seven do not, so the only
option is matching the location string — which misses "Anywhere" and misfires on
"Remote Support Engineer, London office".
The spread between platforms says more about who uses them than about the job market:
- Ashby: 54.2% marked remote. Remote-friendly tech companies.
- Workable: 36.3%.
- Lever: 4.8% — and that is inferred, not stated.
And then BambooHR, from the top of this post: the field exists, and across 50,616 postings nobody
had touched it. "Has the field" and "the field has data" are different claims, and only one of them
is worth putting in a comparison table.
The traps that cost me the most time
SmartRecruiters caps at 100 rows whatever limit you pass. Ask for 500, get 100. Page with
offset and take the count from totalFound, not from the length of the array you got back.
Breezy answers 403, not 404, for a subdomain with no board on it. Treat that as rate
limiting and you back off from empty subdomains forever; treat it as fatal and you drop live boards.
Workday's total saturates at 2000, and the API keeps serving rows past the end of the list.
Probing offsets until a page comes back empty never terminates. My first pass recorded twelve
boards at a 60,000 probe ceiling and would have advertised 720,000 postings that do not exist. The
facets counts are the real total — they match total exactly on boards that have not saturated.
A 429 is not a dead board. I once discarded 1,644 Workable boards as dead. The only problem was
my own request rate.
Finding the boards is the actual work
Calling these endpoints is easy. Knowing that Notion is on Ashby and Figma is on Greenhouse, and
what string goes in the URL, is not — there is no public directory of that.
The practical source is the Wayback CDX index, with one non-obvious catch that cost me two separate
platforms:
CDX sorts by urlkey. For recruitee.com that means com,recruitee)/… — the platform's own
marketing pages — fills the first pages of results, and customer boards like
com,recruitee,acme)/ sit deep in a 92-page index. Read only the front and you get zero
subdomains and conclude the platform has no public boards. I wrote off both Recruitee and BambooHR
that way before going back and walking the whole index. They turned out to have 1,126 and 9,711
live boards.
The mirror-image mistake is trusting a single source. Common Crawl does not crawl jobs.lever.co
at all — one URL, domain-wide — so an early count showed 40 Lever boards against Greenhouse's
2,996 and made Lever look like a rounding error. It has 1,917.
Check whether a coverage gap is your crawler's blind spot before concluding the segment is
small.
One methodological note, because I got this wrong three times
Every number above is the full population for that platform — every live board in the registry,
not a sample.
That is not pedantry. Board sizes vary by orders of magnitude, so any sample is dominated by
whichever large boards happen to land in it. Measuring Recruitee's salary fill rate:
| Sample | Result |
|---|---|
| 1 board | 94% |
| 40 boards | 43.7% |
| All 1,126 boards (21,621 postings) | 26.9% |
I published a page with a sampled number before catching this, and had to correct it. Ashby's
remote rate went from 80.7% on a 50-board sample to 54.2% across all 1,588. If you are going to
put a percentage in front of people, walk the whole set.
Caveats
- These are listing endpoints. Several platforms expose more on a per-posting detail endpoint — BambooHR's date, description and compensation all live there — at the cost of one request per row.
- Coverage is what I have found, not every company on earth. A miss means the board is not in my registry, not that it does not exist.
- Boards close. Measured decay is about 0.6% every day and a half, so these counts drift.
- Measured 2026-08-24.
Where the details are
Per-platform pages with the request shape, the measured fields and the traps for each one:
Greenhouse ·
Lever ·
Ashby ·
Workday ·
SmartRecruiters ·
Workable ·
Breezy ·
Personio ·
Recruitee ·
BambooHR ·
Pinpoint ·
Rippling
The comparison table lives at
Which ATS API gives you what,
and runnable JavaScript and Python recipes are on
GitHub.
If you would rather not maintain twelve clients and a board registry, I run the same engine as
hosted Actors — but the endpoints above are open, and everything in this post is reproducible
without them.
Top comments (0)