If you scrape job boards, these four cover a huge share of tech employers. They are also four different APIs with four different conventions. Here is the honest comparison I wish I had before writing my fourth parser.
Greenhouse
Endpoint: boards-api.greenhouse.io/v1/boards/{token}/jobs
- One request returns every job. Add
?content=truefor descriptions. -
Trap:
content=trueroughly 12×'s the payload (5.4 MB vs 436 KB for a 715-job board). If you only need N jobs, fetch the list first and then per-job/jobs/{id}(8 KB each). -
contentis double-encoded HTML — entities then tags. - Salary is sometimes in
metadata, a label/value array ("Salary Range" → "$180k–$230k"), and often nowhere.
Ashby
Endpoint: api.ashbyhq.com/posting-api/job-board/{token}?includeCompensation=true
- The only one of the four with structured compensation: numbers live on
compensation.summaryComponents[]wherecompensationType === "Salary", not on the tier object most code reads first. -
Trap:
isRemote: trueis set for Hybrid roles. Trust theworkplaceTypestring ("Remote" / "Hybrid" / "OnSite"), or your "remote only" filter silently returns hybrid jobs. - Board tokens are case-sensitive, and no server-side limit exists — a board like OpenAI is ~14 MB.
Lever
Endpoint: api.lever.co/v0/postings/{token}?mode=json
- Supports
&limit=N, which is genuinely useful for large boards (19 KB for 2 postings vs 775 KB for all). -
Trap: the public endpoint has no
salaryRangefield at all. Any tool that reportsats-fieldsalary evidence for Lever is inventing it. - Locations hide in
categories.allLocations; workplace type is a hyphenated string.
SmartRecruiters
Endpoint: api.smartrecruiters.com/v1/companies/{token}/postings?limit=100&offset=N
- Clean pagination with
limit/offsetand atotalFoundcount. - Trap: the public API is off by default. Of twenty well-known companies I tested, only a handful returned anything. Document that or users will read empty boards as your bug.
- Descriptions require a second request per posting (
/postings/{id}) and live insidejobAd.sections.
The summary table
| Greenhouse | Ashby | Lever | SmartRecruiters | |
|---|---|---|---|---|
| Pagination / limit | no | no | limit |
limit+offset
|
| Structured salary | sometimes (metadata) | yes | no | no |
| Description in list | with content=true
|
yes | yes | per-posting |
| Workplace type | no | workplaceType |
string |
remote + hybrid
|
| Company name in payload | via board metadata | no | no | from first posting |
What to build instead of four parsers
I put the four behind one schema — one row per job with title, locations, workplace type, salary with evidence provenance, apply URL, and stable jobUid/recordHash for change detection. It is an Apify Actor, so there is nothing to host:
→ ATS Job Scraper: Greenhouse, Ashby & Lever
{ "boards": ["greenhouse:airtable", "ashby:Linear", "lever:spotify"], "maxJobsPerBoard": 100 }
No API key, and a 5-job test costs a fraction of a cent.
Top comments (0)