Want a feed of new openings at a list of companies — for a niche job board, a recruiting pipeline, a hiring-trends dashboard
or just your own job hunt? You don't need Puppeteer. Almost every company publishes jobs through an applicant tracking
system (ATS)
, and every major ATS exposes a public, unauthenticated JSON feed for its career pages.

This post shows the endpoints, the gotchas I hit, and the small amount of code that turns 11 different formats into one.

Step 1: find out which ATS a company uses

Open the careers page and look at the links / embedded scripts:

You see in the HTML ATS
boards.greenhouse.io/stripe Greenhouse
jobs.lever.co/palantir Lever
jobs.ashbyhq.com/openai Ashby
nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite Workday
apply.workable.com/huggingface Workable
jobs.smartrecruiters.com/BoschGroup SmartRecruiters

…plus Recruitee, Personio, Breezy HR, Teamtailor and Rippling. A regex per pattern over the page HTML detects
most companies automatically.

Step 2: call the public feed

The easy ones return everything in one request:

# Greenhouse (content=true adds descriptions) curl "https://boards-api.greenhouse.io/v1/boards/stripe/jobs?content=true" # Lever curl "https://api.lever.co/v0/postings/palantir?mode=json" # Ashby (with structured salary!) curl "https://api.ashbyhq.com/posting-api/job-board/openai?includeCompensation=true" 

Workday is the odd one: a POST with a JSON body, 20 jobs per page:

curl -X POST -H "Content-Type: application/json" \ -d '{"limit":20,"offset":0,"searchText":"engineer","appliedFacets":{}}' \ "https://nvidia.wd5.myworkdayjobs.com/wday/cxs/nvidia/NVIDIAExternalCareerSite/jobs" 

Tip: use searchText — paging through 2,000 NVIDIA jobs to find 30 matching ones is slow; Workday's own search isn't.

Step 3: the gotchas

  • Greenhouse descriptions are double-encoded HTML (<p>) — decode entities first, then strip tags.
  • Workday dates are text: Posted Today, Posted 3 Days Ago, Posted 30+ Days Ago. The detail endpoint has the real startDate. locationsText may literally be "2 Locations".
  • Workday total is only correct on the first page.
  • Recruitee dates look like 2026-10-13 10:58:56 UTC — not ISO.
  • Personio returns XML, and some companies live on .jobs.personio.com instead of .de.
  • SmartRecruiters and Rippling need one detail call per job for descriptions (Rippling even for the date).
  • Teamtailor usually runs on a custom domain — /jobs.rss on the careers domain gives you everything.

Step 4: normalize

Map every source into one shape:

{ company, ats, jobId, title, department, location, locations, country, remote, workplaceType, employmentType, postedAt, salary, descriptionText, applyUrl } 

Salary deserves a note: only Ashby, Lever, Recruitee and Rippling have structured pay. For the rest, US postings
usually state a range in the text, and one regex catches most of them:

const m = /([$€£])\s?(\d{1,3}(?:,\d{3})*|\d+)\s?([kK])?\s?(?:-|–|to)\s?[$€£]?\s?(\d{1,3}(?:,\d{3})*|\d+)\s?([kK])?/.exec(text); 

Reject yearly ranges under 10,000 so "$1,000–$2,000 signing bonus" doesn't become a salary. In my tests this finds
pay for roughly half of all jobs.

Step 5: only new jobs

Keep a map of ats:company:jobId → firstSeenAt. Each run:

  • job not in the map → new
  • job in the map but gone now → closed, but only if that company's fetch succeeded (otherwise one timeout looks like a mass closure)
  • make the very first run a silent baseline, so your "new jobs" feed doesn't start with 3,000 items.

Run it daily on a schedule and you have a new-jobs feed for any list of companies.

Full endpoint reference

I documented all 11 endpoints, fields and quirks here:
👉 github.com/Mersetti/ats-job-board-apis

Don't want to maintain it?

I packaged all of the above — 11 ATS, auto-detection from careers URLs, salary extraction, filters and new/closed
monitoring — as an Apify Actor: Career Sites Jobs Feed. Pay per job,
schedule it, export to CSV/Sheets or call it from an AI agent.

Which ATS should I add next? Let me know in the comments.