If you are building a job board, a job-alert bot or a hiring-signals tool, you quickly learn a nice secret: most tech companies don't host their own jobs. They use an applicant tracking system (ATS) such as Greenhouse, Lever, Ashby, Workable, Recruitee or SmartRecruiters, and almost all of these publish a free, public job-board API.

In this post I'll show the raw APIs first (free, no key), then the shortcut I built when I got tired of maintaining six parsers. Disclosure: I built the Apify Actors linked at the end.

1. The free public ATS APIs

ATS Public endpoint (no auth) Where to find the slug
Greenhouse https://boards-api.greenhouse.io/v1/boards/{slug}/jobs?content=true boards.greenhouse.io/{slug}
Lever https://api.lever.co/v0/postings/{slug}?mode=json jobs.lever.co/{slug}
Ashby https://api.ashbyhq.com/posting-api/job-board/{slug}?includeCompensation=true jobs.ashbyhq.com/{slug}
Recruitee https://{slug}.recruitee.com/api/offers/ {slug}.recruitee.com
SmartRecruiters https://api.smartrecruiters.com/v1/companies/{id}/postings jobs.smartrecruiters.com/{id}

Quick Python example for one Greenhouse board:

import requests slug = "airbnb" r = requests.get(f"https://boards-api.greenhouse.io/v1/boards/{slug}/jobs", params={"content": "true"}, timeout=30) for job in r.json()["jobs"]: print(job["title"], "|", job["location"]["name"], "|", job["absolute_url"]) 

That's great for one company. The problems start at scale:

  • Every ATS has its own JSON shape. Lever has categories.location, Ashby has compensation, Greenhouse has no salary field at all, dates come in three formats.
  • You need the slugs. The APIs are per company. To answer "show me remote data-engineer jobs posted this week" you first need a list of thousands of boards.
  • Boards die and move. Companies switch ATS, slugs change, boards go empty.

2. Normalizing several ATSs into one schema

If you already have a company list, the ATS Jobs API Actor takes career-page URLs or slugs, auto-detects the ATS and returns one row per job with the same fields: title, location, country, isRemote, salaryMin/Max/Currency, postedAt, applyUrl, descriptionText.

from apify_client import ApifyClient client = ApifyClient("YOUR_APIFY_TOKEN") run = client.actor("saadhazz/ats-jobs-api").call(run_input={ "companies": ["https://boards.greenhouse.io/airbnb", "lever:palantir", "https://jobs.ashbyhq.com/ramp"], "maxJobsPerCompany": 50, }) for job in client.dataset(run["defaultDatasetId"]).iterate_items(): print(job["ats"], job["companySlug"], job["title"], job.get("salaryMin")) 

3. Searching all companies at once by title, country and remote

The bigger problem was the slug list. So I built an index: every day it re-reads about 13,000 live company boards on Greenhouse, Lever, Ashby, Workable, Recruitee, Personio and SmartRecruiters (around 400,000 open jobs). The Job Search API: Career Site Jobs Actor searches that index and then re-checks the matching boards live, so closed jobs are dropped.

curl -X POST "https://api.apify.com/v2/acts/saadhazz~career-site-job-search/run-sync-get-dataset-items?token=$APIFY_TOKEN" \ -H "Content-Type: application/json" \ -d '{"keywords": ["data engineer"], "remoteOnly": true, "postedWithinDays": 3, "maxJobs": 100}' 

One returned row:

{ "title": "Software Engineering Manager (Mission Ops)", "companyName": "Sift", "ats": "ashby", "location": "Marina Del Rey, CA", "country": "US", "isRemote": false, "salaryMin": 200000, "salaryMax": 265000, "salaryCurrency": "USD", "salaryPeriod": "YEAR", "postedAt": "2026-10-08T20:22:47Z", "applyUrl": "https://jobs.ashbyhq.com/siftstack/33454cb1-.../application", "dataSource": "live" } 

Turning it into a daily job alert

Set "onlyNewSinceLastRun": true and an alertId, then create a daily Schedule in Apify. Each run returns only jobs it hasn't returned before, which you can push to Slack, a Google Sheet or your own database via webhook.

4. What it costs

  • Raw ATS APIs: free, plus your time to build and maintain the parsers and the board list.
  • ATS Jobs API: $1 per 1,000 jobs.
  • Career-site job search: $2 per 1,000 jobs. A new Apify account's free monthly credit covers a few thousand jobs to try it.

5. Notes on doing this responsibly

  • These are the APIs ATS vendors publish so jobs get syndicated, but keep the original jobUrl/applyUrl when you republish.
  • Don't collect recruiter or candidate personal data. Job postings only.
  • Cache, and don't hammer one board. One request per board per day is plenty.

If you build something with this, I'd love to hear about it in the comments. And if you know an ATS with a public API that I should add, tell me.