How to pull every open job from Greenhouse, Lever, Ashby and SmartRecruiters with public APIs (and monitor changes)
Job postings are one of the most underrated public data sources on the internet. Recruiters use them to spot placement opportunities, B2B teams read them as buying signals (a new Head of Data means data-tooling budget), and job seekers want to apply on day one — not when a posting finally reaches the aggregators. The usual instinct is to scrape career pages. Don't. Most tech companies host their careers page on one of a handful of Applicant Tracking Systems (ATS), and the four biggest ones — Greenhouse, Lever, Ashby and SmartRecruiters — all expose public, documented JSON APIs . No auth. No proxies. No brittle HTML selectors. The career page itself loads the same JSON you're about to fetch. In this tutorial we'll build a single-file Python tool that: fetches every open job for a company from any of the four ATS, auto-detects which ATS a company uses, normalizes everything into one clean schema, monitors changes — run it on a schedule and get only new / removed / changed postings. The four endpoints ATS Endpoint Greenhouse GET https://boards-api.greenhouse.io/v1/boards/{slug}/jobs?content=true Lever GET https://api.lever.co/v0/postings/{slug}?mode=json Ashby GET https://api.ashbyhq.com/posting-api/job-board/{slug} SmartRecruiters GET https://api.smartrecruiters.com/v1/companies/{slug}/postings (paginated) The {slug} is the company identifier you see in career-page URLs: boards.greenhouse.io/stripe → stripe , jobs.lever.co/spotify → spotify , jobs.ashbyhq.com/linear → linear , careers.smartrecruiters.com/Visa → Visa . Try one right now — no API key needed: curl -s "https://api.ashbyhq.com/posting-api/job-board/linear" | head -c 400 Step 1 — fetchers, one per ATS Each API returns a different shape, so we normalize as we fetch. Here are all four (Python 3, only requests ): import requests UA = { " User-Agent " : " ats-jobs-tutorial/1.0 " } def get_json ( url , params = None ): r = requests . get ( url , params = params , headers = UA , timeout = 30 ) r . raise_for_statu