DEV Community

Ken-Mutisya
Ken-Mutisya

Posted on

Every Major ATS Has a Public JSON API for Job Openings. Nobody Uses Them.

If you have ever scraped a careers page with a headless browser, this will hurt a little. The four big applicant tracking systems, the software behind most tech company job boards, all expose public, keyless JSON APIs that return every open job. Same data as the careers page, zero HTML parsing.

The four endpoints

Greenhouse (Stripe, Figma, Airbnb):

GET https://boards-api.greenhouse.io/v1/boards/{board}/jobs?content=false
Enter fullscreen mode Exit fullscreen mode

Lever:

GET https://api.lever.co/v0/postings/{company}?mode=json
Enter fullscreen mode Exit fullscreen mode

Ashby (Ramp, Plaid, many newer startups):

GET https://api.ashbyhq.com/posting-api/job-board/{org}
Enter fullscreen mode Exit fullscreen mode

SmartRecruiters (Visa and other enterprises), with offset paging:

GET https://api.smartrecruiters.com/v1/companies/{company}/postings?limit=100&offset=0
Enter fullscreen mode Exit fullscreen mode

No key, no login, no rate limit drama for reasonable use. Stripe's Greenhouse board returns 490 open jobs in one request.

The quirks that cost me an afternoon

Greenhouse hides departments. The jobs list has title, location, and dates, but no department. Fetch /v1/boards/{board}/departments once and build a jobId to department map; it comes back with jobs nested per department.

Lever cannot say "not found". An unknown company and a company with zero openings both return an empty array. If you are probing names, treat empty as a miss and keep trying other providers.

Ashby returns full descriptions whether you want them or not, including HTML. Strip what you do not need before storing, and filter on isListed.

Board tokens are not company names. Usually stripe works for Stripe, but brands, abbreviations, and renames break the guess. Try a few slug variants (acme-corp, acmecorp, acme) across all four providers and take the first hit. This is also how you survive companies migrating: Plaid quietly moved from Lever to Ashby, and probing both finds them either way.

Normalizing across providers

Each API names things differently, so map to one row shape early:

Field Greenhouse Lever Ashby SmartRecruiters
title title text title name
department via /departments categories.department department department.label
location location.name categories.location location location.city + region
remote infer from location workplaceType isRemote location.remote
posted first_published createdAt (ms) publishedAt releasedDate
link absolute_url hostedUrl jobUrl build from id

Once normalized, a keyword filter, a location filter, and a remote flag get you a personal job alert, a niche job board, or a hiring tracker for sales signals: a company hiring three SDRs is a company about to buy sales tooling.

What it costs to run

Almost nothing. Five companies, 163 job rows, under a cent of compute, no browser in sight. Run it daily and diff against yesterday to catch openings the hour they post.

If you would rather skip the probing and normalizing, I packaged all four providers into one pay per use actor: Company Job Openings Scraper. Company names in, normalized job rows out, and companies it cannot find are free. The first 25 rows of every run are free.

Top comments (0)