ATS Job Listings API Scraper — 1,639 Boards, 6 Platforms
Pricing
from $1.35 / 1,000 job records
ATS Job Listings API Scraper — 1,639 Boards, 6 Platforms
Query 1,639 verified job boards — 60,983 live roles across Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee and Workable. Filter by seniority, employment type, workplace, country, salary floor, plus keyword and company excludes. Schedule it and pay only for jobs you have not seen before.
Pricing
from $1.35 / 1,000 job records
Rating
0.0
(0)
Developer
K O
Maintained by CommunityActor stats
0
Bookmarked
11
Total users
7
Monthly active users
16 days ago
Last modified
Categories
Share
Job Postings Aggregator — Six ATS Platforms, One Schema
No company list needed. Pick a verified company bundle - top tech, AI/ML, devtools, fintech, YC-backed or remote-first - and hit Start. Every company in a bundle was checked against its live board, so you never have to research a board slug to get a result.
Covers six ATS platforms in one feed — Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee and Workable — twice the board coverage of the nearest combo-ATS listing on the Store (which covers three: Greenhouse, Lever and Ashby). Filters run before you're charged — keyword includes and excludes inside the fetch loop, then seniority, employment type, workplace, country and pay on the normalized record — all before anything reaches the dataset or a billable event, so you only pay for postings that actually match.
Pain points
- Each ATS has its own API, its own field names, its own quirks — Lever hands you epoch milliseconds, Recruitee a non-standard timestamp, SmartRecruiters pages by offset and nests every value behind
{label}. Wiring up even three or four of these yourself is real integration work. - Tracking a target-company list across more than one ATS means running several separate scrapers and merging their output into one schema yourself.
- Filtering after the fact — pulling everything, then throwing away what you didn't want — means paying for records you never use.
- Watching a list of companies for genuinely new roles across multiple platforms means storing what you already pulled, per platform, and diffing every run yourself.
What we solve
- One schema, six ATS platforms. Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee and Workable all normalize to the same 12-field record — no per-platform integration work.
- Filters run before billing. Keyword includes and excludes are checked inside the fetch loop; seniority, employment type, workplace, country,
hasSalaryandsalaryFloorare checked on the normalized record. Both run before anything is pushed or charged, so postings that don't match never cost you anything. - Filter on the derived columns, not just the raw text. Every record already carries
seniorityLevel,employmentTypeNormalized,countryCodeDerivedand parsed pay — and you can filter the run on all of them, so "senior US roles paying over $200k, no contract work" is one query instead of a full pull plus your own post-processing. - Filters that still work when the posting says nothing. Half of live postings state no seniority and six in ten state no employment type, so a filter on those columns quietly drops most of the market. Each one has an inferred twin (
aiSeniorityLevel,aiEmploymentType,aiWorkArrangement) that is populated on every record and carries its own confidence score — so you can filter across the whole result and still tell which values the posting actually stated. - Per-source failure isolation. A source that fails (bad slug, unreachable API) is skipped with a logged warning — the rest of your source list still comes back.
- Dedup built in, namespaced correctly.
onlyNewSinceLastRun(default on) tracks the last 20,000 ids seen, keyed byats:company:id— so the same numeric id from two different platforms, or two different companies, never collides or gets silently skipped.
Who uses it
Recruiters and sourcers tracking a list of target companies, niche and aggregator job boards, sales teams watching accounts for hiring signals, and labor-market/HR-tech products that need one consistent jobs schema instead of five or six different APIs.
Company bundles - no company list needed
You do not need to know a single board slug to run this. Pick a bundle in presetLists, hit Start, and you get live postings straight away.
| Bundle | Boards | What it covers |
|---|---|---|
top-tech | 83 | Large, well-known technology employers. |
ai-ml | 75 | AI labs, ML infrastructure and applied-AI companies. |
devtools | 65 | Developer tools, infrastructure, observability and security. |
yc-backed | 62 | Y Combinator alumni, from recent batches to public companies. |
fintech | 56 | Payments, banking, lending, crypto and financial infrastructure. |
remote-first | 54 | Companies that hire remote-first or distributed by default. |
Every slug in a bundle was checked against its live board on 2026-08-24 - a 200 response with at least one open role. Each bundle file carries its own verifiedAt date, and any board that stops answering is reported in the run's FAILED_TARGETS record instead of silently shrinking your results.
Bundles merge with anything you list yourself and are deduped, so you can start from a bundle and add your own companies to it.
Search every board we know - no company list at all
Turn on Search every board we know and you do not supply companies, slugs or bundles. The run queries our index of 1,639 verified boards - 60,983 open roles at the last check on 2026-08-24 - and applies your title, location and remote filters across all of them.
Ask it things a company list cannot answer:
- every remote senior backend role posted this week
- everything matching "machine learning" across every board in the index
- new postings only, across the whole index, on a daily schedule
| ATS | Boards |
|---|---|
| greenhouse | 617 |
| ashby | 615 |
| lever | 224 |
| workable | 66 |
| smartrecruiters | 59 |
| recruitee | 58 |
Every board in the index answered its ATS with at least one open role when the index was built. Boards are fetched most-active first and capped by Maximum boards to search (default 200), because each board is one request. The index is rebuilt on a refresh sweep; a board that stops answering three sweeps running is retired from it, and anything that fails during your run is reported in FAILED_TARGETS rather than silently shrinking your results.
Input
{"presetLists": ["top-tech"],"sources": ["greenhouse:stripe", "lever:ro", "ashby:Ramp"],"titleIncludes": ["engineer"],"locationIncludes": ["remote"],"remoteOnly": false}
| Field | What it does |
|---|---|
searchIndex | No company list at all. Query every board in the shipped index and apply your filters across all of them. See the section above. |
maxBoards | Only used with searchIndex. Each board is one request, so this caps run length. Boards are searched most-active first. |
presetLists | Start here if you have no company list. One or more verified company bundles; every board in them was checked against the live API. Merges with anything you list yourself, deduped. |
sources | List of "<ats>:<company>" entries. Supported ats: greenhouse, lever, ashby, smartrecruiters, recruitee, workable. The company is the slug from that ATS's careers URL. E.g. greenhouse:stripe, lever:ro, ashby:Ramp, workable:walter-careers. |
maxPerCompany | Stop after this many jobs from each board, so one large employer cannot fill the whole result. Recommended with bundles. Empty = no cap. |
postedWithinDays | Keep only postings published in the last N days — 7 gives you this week's roles. Postings on boards that publish no date are left out, because they cannot be shown to be recent. Applied before billing. |
titleIncludes | Keep only jobs whose title contains one of these keywords (case-insensitive). Empty = all. Applied before billing. |
locationIncludes | Keep only jobs whose location contains one of these keywords. Empty = all. Applied before billing. |
remoteOnly | Keep only remote-flagged roles (or locations mentioning remote). Applied before billing. |
titleExcludes | Drop jobs whose title contains any of these. An exclude beats an include, so engineer minus engineering manager is one query instead of a second pass over your own data. Applied before billing. |
locationExcludes | Drop jobs whose location contains any of these — strip regions you cannot hire in. Applied before billing. |
companyExcludes | Mute these company slugs entirely. The board is dropped before the request goes out, so excluding a noisy employer from a bundle makes the run shorter as well as cleaner. |
seniorityLevels | Keep only these derived levels: EXECUTIVE, VP, DIRECTOR, PRINCIPAL, STAFF, MANAGER, SENIOR, JUNIOR, INTERN. Applied before billing. |
employmentTypes | Keep only these derived types: FULL_TIME, PART_TIME, CONTRACT, INTERN, APPRENTICESHIP, VOLUNTEER. Applied before billing. |
workplaceTypes | Keep only remote, hybrid or onsite. Read from the source ATS's own label, so it covers Lever, Ashby and Workable only — use remoteOnly for a filter that works across all six. Applied before billing. |
hasSalary | Keep only postings where a pay figure was actually found. Applied before billing. |
salaryFloor | Keep only postings paying at least this much, read from salaryMax (or salaryMin where there is no max). Compared in the posting's own currency with no FX conversion. Applied before billing. |
countryCodes | Keep only these two-letter derived country codes, e.g. US, GB, DE. Applied before billing. |
aiSeniorityLevels | Same bands as seniorityLevels plus MID, but filtered on the inferred column, which is populated on every posting. Use this when you want coverage; use seniorityLevels when you want only postings that said so themselves. Applied before billing. |
aiEmploymentTypes | Same values as employmentTypes, filtered on the inferred column. Applied before billing. |
aiWorkArrangements | remote, hybrid or onsite on the inferred column — unlike workplaceTypes this works across all six platforms. Applied before billing. |
aiConfidenceMin | Drop postings whose inference is weaker than this (0-100). Scores only the ai* columns you actually filtered on, or all three if you filtered on none. 100 keeps only values the posting stated itself. Applied before billing. |
onlyNewSinceLastRun | Recommended for schedules — skips jobs already returned by a previous run, so you're only charged for genuinely new postings. Default true. |
maxItems | Stop after this many normalized records. |
includeSalary | Fill the salary fields from whichever pay data each source ATS publishes. On by default; costs no extra requests. |
Output
| Field | Description |
|---|---|
jobId | The posting's id in its source ATS. |
ats | Which platform this came from: greenhouse, lever, ashby, smartrecruiters, recruitee, or workable. |
company | The company/account slug you queried. |
title | Job title. |
location | Primary location, mapped from that ATS's own location shape. |
department | Department/team, mapped from that ATS's own tagging. |
employmentType | Employment type, mapped from that ATS's own field. |
remote | Boolean where the source ATS exposes one; null for ATSs (like Greenhouse) with no explicit remote flag. |
postedAt | Posting date, normalized to ISO 8601 regardless of the source's native format. |
url | Public posting URL. |
applyUrl | Direct apply-form URL, where the ATS separates it from the posting URL. |
source | Always "Jobs Aggregator". |
workplaceType | The source ATS's own workplace label, where it publishes one. |
workplaceTypeNormalized | REMOTE, HYBRID or ONSITE — the label above folded onto one enum, so records from all six platforms compare directly. Read from the posting body for the three platforms that publish no workplace field. null when neither says. |
salaryMin / salaryMax | Pay range as plain numbers, so you can sort and filter on it. null when the posting does not state pay — never a guess. |
salaryCurrency | ISO code (USD, EUR, GBP, …) where the posting makes it clear. |
salaryInterval | YEAR, MONTH, WEEK, DAY or HOUR. |
salaryText | The pay exactly as the posting worded it, kept so you can audit the parse. |
salarySource | ats when the board published structured pay, ats-summary or description when it was read out of the posting text. null when no pay was found. |
cityDerived / regionDerived / countryDerived / countryCodeDerived | The location string split into parts, so "Austin, TX" is filterable by state and country instead of by substring. |
locationType | REMOTE, HYBRID or ONSITE. |
seniorityLevel | INTERN, JUNIOR, SENIOR, STAFF, PRINCIPAL, MANAGER, DIRECTOR, VP or EXECUTIVE, read from the title. null when the title carries no signal. |
employmentTypeNormalized | FULL_TIME, PART_TIME, CONTRACT, INTERN, APPRENTICESHIP or VOLUNTEER — one enum across every ATS, so records are comparable. |
daysSincePosted | Whole days since the posting went live, precomputed. |
organizationUrl | The public careers board this posting came from. |
aiSeniorityLevel | The band above, but never null — inferred from the title where the posting states nothing, and defaulted to MID where the title carries no signal either. Adds MID to the enum. |
aiEmploymentType | Same enum as employmentTypeNormalized, never null, defaulting to FULL_TIME. |
aiWorkArrangement | REMOTE, HYBRID or ONSITE, never null, defaulting to ONSITE. Falls back to the location text and the remote flag, so it covers all six platforms rather than the three that publish a workplace field. |
aiSeniorityConfidence / aiEmploymentTypeConfidence / aiWorkArrangementConfidence | How much to trust each inferred value, per field. See the table below. |
Why there are two sets of columns. Roughly half of live postings state no seniority and six in ten state no employment type. The columns without the ai prefix stay null in those cases — honest, but it means a filter set on one silently drops every silent posting. The ai* columns are always populated so the filters cover the whole result, and each publishes its own confidence so you can tell an observation from a guess:
| Confidence | What produced the value |
|---|---|
1.0 | The posting stated it. The ai* column simply copies its non-ai twin, so the two never disagree. |
0.9 | A language model resolved the title offline, in a lookup table shipped with the actor. No model is ever called during your run. |
0.85 / 0.7 / 0.55 | A title or location rule, strongest first — "Senior" and "VP" at the top, a bare trailing "II" at the bottom. |
0.3 | Nothing fired. This is the field's most common value, i.e. a guess — filter it out with aiConfidenceMin. |
Pay coverage. Pay comes from whichever data each source publishes: structured for Ashby and Recruitee, parsed from the posting body for Greenhouse, Lever and SmartRecruiters. Measured 08/2026 across two Ashby and Lever boards: 174 of 186 postings.
This is a real record, captured live from an Ashby source on 08/21/2026 by running this actor's own client and field-mapping code — not a placeholder:
{"jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245","ats": "ashby","company": "Ramp","title": " Security Engineer, Cloud","location": "New York, NY (HQ)","department": "Engineering","employmentType": "FullTime","remote": true,"postedAt": "2026-04-07T17:12:35.753+00:00","url": "https://jobs.ashbyhq.com/Ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245","applyUrl": "https://jobs.ashbyhq.com/Ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application","source": "Jobs Aggregator","salaryMin": 211400,"salaryMax": 290600,"salaryCurrency": "USD","salaryInterval": "YEAR","salaryText": "$211.4K – $290.6K • Offers Equity","salarySource": "ats","cityDerived": "NY (HQ)","regionDerived": "NY","countryDerived": "United States","countryCodeDerived": "US","locationType": "HYBRID","seniorityLevel": null,"employmentTypeNormalized": "FULL_TIME","daysSincePosted": 136,"workplaceType": "Hybrid","workplaceTypeNormalized": "HYBRID","organizationUrl": "https://jobs.ashbyhq.com/Ramp","aiSeniorityLevel": "MID","aiSeniorityConfidence": 0.3,"aiEmploymentType": "FULL_TIME","aiEmploymentTypeConfidence": 1,"aiWorkArrangement": "HYBRID","aiWorkArrangementConfidence": 1}
This record is a good illustration of the confidence column: Ashby published the employment type and the workplace, so both are 1 and their ai* values just copy them. Nobody published a seniority and "Security Engineer, Cloud" carries no level word, so aiSeniorityLevel falls back to MID at 0.3 — a guess, and labelled as one. aiConfidenceMin: 70 would drop this posting from a seniority filter.
Note what's missing next to the source-specific record: no compensationSummary (Ashby), no departments/offices array or requisitionId (Greenhouse), no workplaceType/allLocations/country (Lever), no requirementsHtml/benefitsHtml/countries (Workable). The unified schema trades platform-specific depth for one consistent shape across all six sources — see Which actor do I want? below if you need that depth back.
Which actor do I want?
- Tracking companies across more than one ATS, or don't want to run five separate scrapers? You're in the right place — this is the only actor in this family that covers all six platforms in one normalized feed.
- Need Greenhouse's multi-department/multi-office tagging or its internal requisition id? Use Greenhouse Jobs Scraper.
- Need Lever's remote/hybrid/onsite distinction, every location tag, or a country field? Use Lever Jobs Scraper.
- Need Ashby's published salary/compensation data? Use Ashby Jobs Scraper — this aggregator fetches Ashby's compensation payload too but drops the field to keep its schema consistent across all six sources.
- Need Workable's description/requirements/benefits split, or its deduplicated country array? Use Workable Jobs Scraper.
- Want the free CRM/Notion/HubSpot/Airtable/Supabase sync feature? It currently ships on the four single-board actors above (
syncEnabledinput), not yet on this one.
Pricing (Pay-Per-Event)
query— charged once per run for the poll across your sources.job-record— charged per normalized job pushed, after filtering. This is the primary event.apify-actor-start(Apify-managed) — covers baseline compute per run.
A daily monitor of a target-account list with a title filter returns just the handful of relevant new roles for pennies — and the filtered-out postings were never billed in the first place.
Finding company slugs
| ATS | Slug is the part of the careers URL |
|---|---|
| Greenhouse | job-boards.greenhouse.io/<slug> |
| Lever | jobs.lever.co/<slug> |
| Ashby | jobs.ashbyhq.com/<slug> |
| SmartRecruiters | jobs.smartrecruiters.com/<slug> |
| Recruitee | <slug>.recruitee.com |
| Workable | apply.workable.com/<slug> |
Source & reliability
All six sources are public ATS job-board APIs — no API keys, no proxies. A source that fails (bad slug, unreachable API) is skipped with a logged warning; the rest of your source list still comes back. SmartRecruiters sources page through up to 1,000 postings per company per run (10 pages × 100); the other five ATSs return each company's full board in a single call. Run npm test for the offline self-test covering the source parser and every ATS's field mapping.
FAQ
How am I charged?
A flat query event once per run, plus one job-record event per normalized job actually pushed — after your titleIncludes/locationIncludes/remoteOnly filters are applied. Postings your filters exclude are never normalized, pushed, or charged. With onlyNewSinceLastRun on (the default), a posting already returned in a previous run (tracked by ats:company:id, for the most recent 20,000 seen) is skipped and not re-charged.
Where does the data come from?
Six public ATS job-board APIs — Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee and Workable — the same data each platform's own hosted careers pages are built from. No API keys or proxies required.
How fresh is it?
Live at request time — there's no bundled or cached dataset behind this actor, it calls each source ATS directly on every run. Schedule it (daily or weekly) to keep catching new postings; onlyNewSinceLastRun means a recurring schedule only bills for roles you haven't seen yet.
Which actor do I want?
See Which actor do I want? above — short version: tracking multiple ATS platforms at once, stay here; need one platform's extra fields (salary, department/office arrays, workplace-type detail, or the description/requirements/benefits split), use that platform's single-board actor instead.
Need the rest of the record on these companies?
This actor returns one source. KYB Company Data Enrichment takes a company name and returns what the public record says about it across up to nine sources — state business registries, UCC lien filings, tax warrants and WARN layoff notices, the FMCSA motor-carrier census, the CMS NPI Registry, USAspending federal awards, EPA ECHO compliance, SEC EDGAR filings and CourtListener federal dockets.
Every enriched field carries its source and its match confidence. Only matches above the accept threshold are merged — anything weaker is surfaced as a suggestion and never folded into the record, because a false join is worse than returning nothing.
SEO keywords: jobs aggregator, multi ATS scraper, Greenhouse Lever Ashby scraper, job listings API, job postings API, new jobs feed, hiring signals, recruiting data, job board aggregator, ATS jobs feed