Working code for pulling job postings straight out of company applicant tracking systems — Greenhouse, Lever, Workday, Ashby, Workable, SmartRecruiters, Breezy, Personio, Pinpoint and Rippling — and doing something useful with them.
Source: github.com/HwangByeongSeon/ats-job-feed-recipes. Every script runs as-is.
export APIFY_TOKEN=... # https://console.apify.com/settings/integrations
python python/new_jobs_to_csv.py
Each of these platforms publishes an open JSON endpoint that the company's own careers page reads — no key, no login, no scraping of rendered HTML. The awkward parts are everything around that.
"Posted 30+ Days Ago";
SmartRecruiters caps a response at 100 rows whatever limit you pass; Breezy answers
403 for a board that does not exist, where everyone else uses 404.| File | What it does |
|---|---|
javascript/watch-companies.mjs | Diff a shortlist between runs — what opened, what closed |
javascript/salary-data.mjs | Postings that publish a pay range, normalised to an annual figure |
python/one_company.py | Every open role at one company, from its careers URL |
python/new_jobs_to_csv.py | Only postings that appeared since the last run, appended to CSV |
These scripts call Apify Actors that bill per row delivered. Every script caps itself at a couple
of hundred rows and reads MAX_ROWS if you want more, so running an example does not
produce a surprise bill.
The registry the Actors bundle, as re-validated on 2026-08-19. It is a list of which company slug sits on which platform — the part that is not available anywhere publicly.
| Platform | Boards | Live postings |
|---|---|---|
| Greenhouse | 5,578 | 179,052 |
| Personio | 5,172 | 41,685 |
| SmartRecruiters | 4,334 | 363,544 |
| Breezy HR | 2,800 | 47,301 |
| Workday | 2,658 | 595,820 |
| Lever | 1,917 | 63,622 |
| Workable | 1,710 | 77,339 |
| Ashby | 1,596 | 32,007 |
| Rippling | 1,298 | 18,647 |
| Pinpoint | 618 | 18,235 |
| Total | 27,681 | 1,437,252 |
If you would rather call the platforms directly, these are the public endpoints. All of them answer
without a key. Replace {company} with the board slug.
| Platform | Endpoint |
|---|---|
| Greenhouse | https://boards-api.greenhouse.io/v1/boards/{company}/jobs?content=true |
| Lever | https://api.lever.co/v0/postings/{company}?mode=json |
| Ashby | https://api.ashbyhq.com/posting-api/job-board/{company}?includeCompensation=true |
| SmartRecruiters | https://api.smartrecruiters.com/v1/companies/{company}/postings?limit=100&offset=0 |
| Workable | https://apply.workable.com/api/v1/widget/accounts/{company}?details=true |
| Breezy | https://{company}.breezy.hr/json |
| Personio | https://{company}.jobs.personio.de/search.json |
| Pinpoint | https://{company}.pinpointhq.com/postings.json |
| Rippling | https://api.rippling.com/platform/api/ats/v1/board/{company}/jobs |
| Workday | POST https://{tenant}.{cluster}.myworkdayjobs.com/wday/cxs/{tenant}/{site}/jobs |
Two things that will bite you if you write your own client:
limit. Page with offset, and take the real total from
totalFound rather than counting rows.403, not 404, for a subdomain with no
board on it. Treating that as rate limiting and backing off turns a half-hour job into a
fourteen-hour one.
The flagship is also reachable as a remote MCP server at
https://mcp.apify.com/?tools=starbright_overlap/ats-job-feed.