Tools / ATS Jobs Openings Scraper – 10 ATS Platforms, One Schema
ATS Jobs Openings Scraper – 10 ATS Platforms, One Schema
Live job postings from any company's Greenhouse, Ashby, Lever, Recruitee, Workable, SmartRecruiters or Workday career board, normalized into one schema with city/region/country, remote status and salary wherever the board publishes it — including Greenhouse boards, where the pay range is printed in the posting rather than exposed as an API field (139 of 166 live airbnb postings carry one). Don't know which ATS a company uses? Leave it out and the Actor auto-detects the platform from the slug alone. Filter by title, location, department or team, posting-date window or a keyword in the full description text; watch mode returns only the postings that are new since your last run.
Give it a list of companies as {"ats": "auto|greenhouse|ashby|lever|recruitee|workable|smartrecruiters|workday", "slug": "..."} and it pulls every live posting straight from each company's own career-board API — no browser, no login. Omit ats or set it to "auto" and it tries all 6 non-Workday platforms for that slug and keeps whichever one is real — you only need to know the company, not the ATS. Seven ATSes normalized into one schema — the only one of these seven to also include Recruitee. Hand-typed board locations are normalized into city/region/country, so you can group or filter by country without writing a location parser — neither Greenhouse nor Workday publishes usable structured geography, and the same normaliser fills country on 95% of 1,914 live Greenhouse postings and on 100% of Workday postings once descriptions are fetched (35% from the cheap list call alone). Measured on 800 live Workday postings, locationKeyword: "United States" matched 0 of them before and matches 89 now; on Greenhouse the same filter matches a posting whose own text only reads "San Francisco, CA". Greenhouse returns only the leaf department, and on 76% of 1,909 live postings that leaf is a nested internal name, so department used to report an internal cost-centre code: one Stripe board showed 191 distinct “departments”. Since 0.1.36 the board's department tree is resolved once per company, so department is the real top-level department (191 → 4 on Stripe, 44 → 13 on Databricks, 29 → 3 on Airbnb), team is the sub-team it sits under (filled on 76% of Greenhouse postings, previously always empty) and departmentPath is the whole chain. departmentKeyword matches any level of that chain, so a filter written against either the top-level name or the leaf keeps working. Supports titleKeyword/titleExcludeKeyword, locationKeyword/locationExcludeKeyword, hasSalary/minSalary/maxSalary, remoteOnly and postedAfter filters applied before you're charged — a salary floor costs you nothing on the postings it rejects. Companies that have moved off an ATS (common for Lever) are skipped with a warning instead of failing the run. No start fee.
Run it
Pay-per-result on Apify, no FetchSmith account needed. Use your own Apify API token, or hit Run on the Store page with no token at all.
curl -X POST "https://api.apify.com/v2/acts/fetchsmith~ats-jobs-scraper/run-sync-get-dataset-items?clean=true" \
-H "Authorization: Bearer YOUR_APIFY_TOKEN" -H "Content-Type: application/json" \
-d '{"companies": [{"ats": "greenhouse", "slug": "airbnb"}, {"ats": "ashby", "slug": "ramp"}, {"ats": "smartrecruiters", "slug": "ElasticBandCompany"}], "maxJobsPerCompany": 25, "maxResults": 75}'
Returns the rows as JSON when the run finishes. A hosted FetchSmith API (one key, no Apify account, see API docs) is planned but not open yet.
Output fields
company atsSource jobId title department team departmentPath employmentType workplaceType isRemote location secondaryLocations country region city salaryMin salaryMax salaryCurrency salaryInterval publishedAt updatedAt jobUrl applyUrl descriptionHtml descriptionText scrapedAt watchEvent previousHasSalary
Sample output
{
"atsSource": "greenhouse",
"city": "London",
"company": "airbnb",
"country": "United Kingdom",
"department": "2. Business",
"departmentPath": [
"2. Business",
"Sales \u0026 Partnerships",
"Business Development"
],
"isRemote": false,
"jobUrl": "https://careers.airbnb.com/positions/8184174?gh_jid=8184174",
"location": "London, United Kingdom",
"region": "England",
"salaryCurrency": "GBP",
"salaryInterval": "year",
"salaryMax": 54000,
"salaryMin": 46000,
"team": "Business Development",
"title": "Account Manager"
}
Guides
- "Location is not one field either: normalizing ten ATS job-board APIs down to city/region/country" — Three of ten applicant-tracking systems hand back structured geography, three hand back one free-text string a human typed, and Workday's list payload gives you neither — it takes a second per-job call to resolve, and until 0.1.39 that call ran too late to help its own filter.
- "Workable's job board API: no pagination, and the description flag is the one that's actually worth gating" — apply.workable.com/api/v1/widget/accounts/<slug> serves any company's open roles as public JSON with no key. page/limit/offset are all silently ignored, but details=true is a genuine on/off switch — 4.4x-11.3x more bytes for one field, description, that we were fetching unconditionally even when nobody asked for it.
- Teamtailor, Personio and Breezy — three more ATS job-board APIs, three more shapes — Teamtailor's board is a JSON Feed with an embedded schema.org JobPosting, Personio's is XML with the title field called "name", and Breezy's structured location only shows up once you stop testing on the vendor's own demo tenant.
- "SmartRecruiters' job board API: real pagination, a real per-posting cost, and one board that quietly leaks a salary nobody else publishes" — api.smartrecruiters.com/v1/companies/<slug>/postings is the only one of seven ATS APIs we scrape that paginates for real — verified live from a 2-posting board up to a 24,867-posting one. The list endpoint carries no description at all, so a detail call per posting costs ~130x more per item than the list page it came from. And a freeform admin-configured field on one real board carries genuine pay-transparency text the API's own schema has no place for.
- "Recruitee's job board API: one real filter hiding among ignored ones, and a 42 MB board that's 97% pasted screenshots" — <slug>.recruitee.com/api/offers/ serves any company's open roles as public JSON with no key. page, limit, offset and per_page are silently ignored — but department is real, exact-match and case-sensitive, so one wrong capital letter returns an empty board instead of an error. And the largest board we measured is 42 MB for 86 postings, 97.6% of which is base64 PNGs pasted into the job descriptions.
- "Lever's job board API actually paginates — the one ATS that does" — api.lever.co/v0/postings serves any company's open roles as public JSON with no key, and unlike Greenhouse it honors skip/limit for real. One real board is 4,200+ postings and 50 MB in a single unpaginated request — here's what changes when you page it properly.
- Greenhouse's job board API has no auth and no pagination — a big board is one 10 MB response — boards-api.greenhouse.io serves any company's full job board as public JSON with no key. But page/per_page/limit are silently ignored, the description is double-encoded HTML, there is no salary or employment-type field, and remote/hybrid hides in a board-specific metadata array.
- "\"full-time\" is not a filter: employmentType across ten ATS job-board APIs" — Ten applicant-tracking systems publish an employment-type field ten different ways — two never populate it, one is free text the employer typed, and on Personio the string "full-time" matches zero postings because its vocabulary contains neither word.
- "Ashby's job board API: no pagination, no size cap, and pay bands baked into one number" — api.ashbyhq.com/posting-api serves any company's open roles as public JSON with no key. There's no limit/offset param at all — a 814-posting board arrives as one 13.5 MB response, descriptions included whether you want them or not — but the compensation field already does the hard part of merging multi-location pay bands for you.
- A Try-for-free click shouldn't cost a first-time buyer $1.26 and five minutes — Two of our own Actors' default inputs quietly walked past a good first impression — one billed $1.26 before showing a single row, the other ran within 23 seconds of our own test's failure threshold. Neither showed up as broken. Here's what we measured and how we fixed both.
- Workday's public career-site API has no auth, a hard page-size cap, and no department field — Workday powers the career pages of most large enterprises, retailers and government agencies. Its JSON API needs no login — but limit caps at 20, total is only meaningful on the first page, and there's no department field anywhere in either response.
- Six ATS job-board JSON APIs, six different ideas of what a job posting is — Greenhouse, Ashby, Lever, Recruitee, Workable and SmartRecruiters each expose public job-board JSON with no login — but the title field, the location field and even the concept of "structured pay" mean something different on every one of them.
Only publicly accessible pages are read. Respect the source site's terms when using the data.