Greenhouse, Lever, Ashby & Workday Jobs Scraper - 9 ATS API
Pricing
from $4.00 / 1,000 job scrapeds
Greenhouse, Lever, Ashby & Workday Jobs Scraper - 9 ATS API
Scrape jobs from Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Recruitee, Rippling and Personio in one run. Give a company name or domain and it auto-detects the ATS and board slug - no token needed. One schema with salary, department, location, remote flag and apply URL.
Pricing
from $4.00 / 1,000 job scrapeds
Rating
0.0
(0)
Developer
Make No Mistakes LLC
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
9 hours ago
Last modified
Categories
Share
Does it scrape your ATS? Greenhouse, Ashby, Lever, Workday, SmartRecruiters, Recruitee, Rippling, Workable and Personio — nine applicant tracking systems, one run, one schema.
Give it company names or domains. It finds the ATS.
Every other job-board Actor makes you supply the board token — you have to already know that Stripe
is on Greenhouse under stripe, OpenAI is on Ashby under openai, and NVIDIA is on Workday under
nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite. That lookup is the actual work, and nobody
ships it.
This one does. Hand it ["stripe", "openai.com", "Match Group"] and it resolves each input to a
platform and a board slug, pulls every open job posting from that company's career page feed, and
returns one dataset item per job on a single normalized schema across all nine platforms — title,
department, team, city, country, remote flag, employment type, salary range, posted and updated
dates, full description and the real apply URL — with the untouched platform record under raw.
Board URLs and slugs work too, and discoverOnly mode answers "which ATS is this company on?"
for a fraction of a cent.
No HTML scraping, no proxies, no logins, no CAPTCHAs, no API keys. Just the job-board APIs the ATS vendors published for aggregators to consume.
For AI agents and MCP clients
Resolves company names or domains to their applicant tracking system and returns every open job on one normalized schema across nine ATS platforms.
Minimal input that returns something useful:
{ "companies": ["stripe", "openai", "ramp"], "maxItems": 200 }
Cheap mode — which ATS does each company use, without paying for jobs:
{ "companies": ["stripe.com", "notion.so", "doordash.com"], "discoverOnly": true }
You get one dataset item per job, with company, company_slug, ats, job_id, title,
department, team, location_raw, city, state, country, is_remote, employment_type,
salary_min, salary_max, salary_currency, salary_period, posted_at, updated_at,
apply_url, description_html, description_text, source_url and raw (the untouched platform
record). In discoverOnly mode you get one item per company instead: company_input,
company_slug, ats, board_url, job_count, resolved, other_matches, candidates_tried.
Cost: $0.005 to start a run, plus $0.004 per job returned — or $0.001 per company in discoverOnly
mode. The first call above costs $0.005 if it finds nothing and $0.805 if it hits the 200-job cap.
Set maxItems to bound the spend before you call. A single large employer can have 2,000 open
jobs.
Typical questions it answers: which ATS does this company use; every open engineering role at these
40 companies; what changed on these boards in the last 7 days (postedWithinDays: 7); which of
these companies publish salary ranges and what they are; remote-only roles across a competitor set.
The nine platforms
All nine were verified live before shipping. "Boards" is what a single request returns; "salary" is whether the platform publishes a structured pay range at all, not whether a given employer filled it in.
| Platform | Endpoint | Salary | Department / team | Dates |
|---|---|---|---|---|
greenhouse | boards-api.greenhouse.io/v1/boards/{token}/jobs | ✅ pay ranges when the employer opts into transparency | department only | posted and updated |
ashby | api.ashbyhq.com/posting-api/job-board/{org} | ✅ full structured comp — min, max, currency, interval | department and team | posted |
lever | api.lever.co/v0/postings/{org} | ✅ salaryRange when filled in | department and team | posted |
smartrecruiters | api.smartrecruiters.com/v1/companies/{co}/postings | ❌ | department + function | posted and updated |
recruitee | {co}.recruitee.com/api/offers/ | ⚠️ structured but usually empty | department | posted and updated |
rippling | api.rippling.com/platform/api/ats/v1/board/{co}/jobs | ⚠️ payRangeDetails, rarely populated | department and sub-team | posted |
personio | {co}.jobs.personio.de/xml | ❌ | department + recruiting category | posted |
workday | {tenant}.wd{N}.myworkdayjobs.com/wday/cxs/{tenant}/{site}/jobs | ❌ | ❌ neither | posted |
workable | apply.workable.com/api/v1/widget/accounts/{co} | ❌ | department + function | posted |
Measured field coverage
Percentage of returned jobs with a non-null value, on a live sample of 40 jobs per board, 2026-09-02. A dash means the platform does not publish the field at all.
| Board | Dept | Team | City | Country | Remote flag | Type | Salary | Posted | Updated | Description |
|---|---|---|---|---|---|---|---|---|---|---|
greenhouse / gitlab | 100% | — | 55% | 80% | ✅ | — | 22% | 100% | 100% | 100% |
greenhouse / stripe | 100% | — | 80% | 45% | ✅ | — | 0% | 100% | 100% | 100% |
ashby / ramp | 100% | 100% | 95% | 100% | ✅ | 100% | 100% | 100% | — | 100% |
ashby / openai | 100% | 100% | 92% | 100% | ✅ | 100% | 90% | 100% | — | 100% |
lever / matchgroup | 100% | 100% | 98% | 100% | ✅ | 95% | 50% | 100% | — | 100% |
lever / palantir | — | 100% | 100% | 100% | ✅ | 100% | 0% | 100% | — | 100% |
smartrecruiters / Ubisoft2 | 100% | 95% | 100% | 100% | ✅ | 100% | — | 100% | — | 100% |
recruitee / vandebron | 100% | 100% | 100% | 100% | ✅ | 100% | 23% | 100% | 100% | 100% |
rippling / rippling | 100% | 100% | 95% | 98% | ✅ | 100% | 0% | 100% | — | 100% |
personio / wandelbots | 100% | 100% | 67% | — | ✅ | 100% | — | 100% | — | 0% |
workday / nvidia | — | — | 100% | 100% | ✅ | 100% | — | 100% | — | 100% |
workday / salesforce | — | — | 100% | 100% | ✅ | 100% | — | 100% | — | 100% |
workable / adverity | 100% | 64% | 86% | 100% | ✅ | 86% | — | 100% | — | 100% |
The honest summary: if you want salary data, Ashby is the platform that has it — Ramp publishes
a range on 100% of its postings and OpenAI on 90%. Greenhouse has ranges only where the employer
turned on pay transparency (GitLab 22%, Stripe 0%). Lever is a coin flip. The other six publish no
usable pay data at all, and this Actor returns null rather than guessing a number out of the
description text.
Discovery
Discovery is the point of this Actor, so here is exactly what it does and how well it works.
Each input is reduced to a short list of plausible board slugs — the domain label, the company name
lowercased and de-spaced, the hyphenated variant, the variant with Inc / GmbH / Group and
friends removed. Those are probed across the platforms in three tiers, cheapest first, stopping at
the first tier that finds a board of meaningful size:
- Greenhouse, Ashby, Lever, SmartRecruiters, Recruitee, Rippling — one cheap GET each, all candidates in parallel.
- Personio and Workable — both rate-limit by IP, so they are paced and only reached when tier 1 comes up empty (or turns up only a handful of jobs).
- Workday — needs per-tenant host and site discovery, so it goes last.
A board URL short-circuits the whole thing: paste https://jobs.lever.co/matchgroup or
https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite and the platform and slug are read
straight off it.
Every resolution is cached in the run's key-value store under ATS_DISCOVERY_CACHE, keyed by the
raw input string, so an unchanged watchlist does not re-probe.
Measured hit rate: 18 of 20 (90%) on a mixed test set — Stripe, OpenAI, Anthropic, Databricks, Ramp, Notion, Linear, Datadog, NVIDIA, Vercel, Figma, Airtable, Brex, Cloudflare, Match Group, Vandebron, Adverity, scale.com. Misses: DoorDash and Retool — both run career sites that are not on any of the nine public job-board APIs, so there is nothing to find.
Where discovery gets it wrong
- A slug can belong to someone else.
scale.comresolves to a six-job Personio board owned by a different company called Scale; Scale AI's real board is on Greenhouse underscaleai. When the slug guess is ambiguous there is no signal that distinguishes them. If the resolvedjob_countlooks absurd for the employer, pass the board URL instead. - Some companies are on two platforms at once. Mid-migration boards are common. The larger board
wins, the other is listed in
other_matchesindiscoverOnlymode, and jobs that appear on both are de-duplicated by title and location. - A rate-limited probe reads as a miss. Personio and Workable both answer HTTP 429 when several requests land close together. The Actor paces and retries them, but a company whose only board is on one of those two can occasionally come back unresolved. Re-run, or pass the board URL.
resolved: falseis the honest answer, not an error.candidates_triedtells you which slugs were probed so you can supply the right one.
Input
{"companies": ["stripe", "openai.com", "Match Group", "https://jobs.ashbyhq.com/ramp"],"maxItems": 500,"postedWithinDays": 7,"titleIncludes": ["engineer", "designer"],"location": "United States","remoteOnly": false,"includeDescription": true}
| Field | Type | Default | Notes |
|---|---|---|---|
companies | array of string | ["stripe","openai","ramp"] | Names, domains, board slugs or board URLs. Mix them freely. |
discoverOnly | boolean | false | Return one row per company — which ATS, which board, how many jobs — and stop. Charged as company-resolved, not job-scraped. |
platforms | array of enum | all nine | Restrict discovery and fetching to a subset. |
maxItems | integer | 500 | Hard cap across all companies combined, split evenly between the ones that resolved so no single board eats the budget. Unused allowance rolls forward. 0 = unlimited. |
postedWithinDays | integer | — | Incremental mode. Keeps a job when the newer of posted_at / updated_at falls inside the window. |
titleIncludes | array of string | — | OR'd, case-insensitive, matched against the job title. |
location | string | — | Substring match against location_raw, city, state and country, case-insensitive. |
remoteOnly | boolean | false | Keep only jobs the platform flags remote, or whose location says remote / anywhere / distributed. |
includeDescription | boolean | true | Off omits description_html and description_text entirely — items get roughly 10× smaller. |
maxItems is your spend bound. Set it deliberately: 20 well-known tech companies is about
5,000 open jobs, and a single Workday tenant can be 2,000 on its own.
Output
One item per job. Every item carries all 24 fields; anything the platform doesn't publish is null
rather than missing, so the dataset exports cleanly to CSV or a database without ragged columns.
{"company": "Stripe","company_slug": "stripe","ats": "greenhouse","job_id": "8044460","title": "AI Engineer","department": "1150 Solutions Architecture","team": null,"location_raw": "Chicago","city": "Chicago","state": null,"country": null,"is_remote": false,"employment_type": null,"salary_min": null,"salary_max": null,"salary_currency": null,"salary_period": null,"posted_at": "2026-07-03","updated_at": "2026-08-26","apply_url": "https://stripe.com/jobs/search?gh_jid=8044460","description_html": "<h2><strong>Who We Are</strong></h2>…","description_text": "Who We Are\n\nAbout Stripe\n\nStripe is a financial infrastructure platform…","source_url": "https://boards-api.greenhouse.io/v1/boards/stripe/jobs","raw": { "internal_job_id": 3486653, "requisition_id": "See Opening ID", "offices": [], "…": "…" }}
A record from Ashby, where the compensation block is real:
{"company": "Ramp", "ats": "ashby", "title": "Security Engineer, Cloud","department": "Engineering", "team": "Backend","location_raw": "New York, NY (HQ) | Remote (Canada) | Remote (US) | Miami, FL","city": "New York City", "state": "NY", "country": "US", "is_remote": true,"employment_type": "FULL_TIME","salary_min": 211400, "salary_max": 290600, "salary_currency": "USD", "salary_period": "YEAR","posted_at": "2026-04-07","apply_url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application"}
raw holds the untouched platform record (minus the description blobs, which are already broken
out above), so nothing an ATS publishes is lost even where this Actor's normalized schema has no
home for it.
Normalization rules worth knowing
employment_typeis mapped ontoFULL_TIME,PART_TIME,CONTRACT,TEMPORARY,INTERNSHIP,VOLUNTEER. A value nobody recognises passes through upper-cased rather than being dropped.salary_periodisYEAR,MONTH,WEEK,DAYorHOUR.- Salary comes only from structured compensation fields. Descriptions are never regex-mined for
pay. A guessed number in a
salary_mincolumn is worse than an honestnull. is_remoteis true when the platform sets a remote flag, or whenlocation_rawcontains remote / anywhere / distributed / work-from-home. Job titles and descriptions are never consulted — they say "remote" for reasons that have nothing to do with the role.city/state/countrycome from the platform's structured address where it publishes one (Ashby, SmartRecruiters, Recruitee, Workable) and from parsinglocation_rawotherwise. ATS location strings are unconstrained free text, so treat the parse as best-effort andlocation_rawas the truth.- Multiple locations are joined with
|inlocation_raw;city/state/countrydescribe the primary one. - Dates are dates,
YYYY-MM-DD, in every field on every platform. companyis the employer's own name where the platform publishes one — Greenhouse, SmartRecruiters, Recruitee, Rippling, Personio and Workable all do. Ashby, Lever and Workday do not, socompanythere is derived from what you passed in:"Match Group"stays"Match Group", but a bare slug likeopenaibecomes"Openai". Pass the name you want to see, or readcompany_slug, which is always exact.
Cost
Pay-per-event. You pay for what the run actually returns.
| Event | Price | When |
|---|---|---|
| Actor start | $0.005 | once per run |
job-scraped | $0.004 | per job written to the dataset |
company-resolved | $0.001 | per company in discoverOnly mode |
job-scraped and company-resolved are never both charged in the same run. Jobs filtered out by
postedWithinDays, location, titleIncludes or remoteOnly are never written and never billed.
Worked examples: resolving a 200-company watchlist with discoverOnly costs $0.205. Pulling
500 jobs costs $2.005. A daily postedWithinDays: 1 sweep over 50 companies typically returns
a few dozen jobs, so it runs at cents a day rather than re-paying for the whole board — which is the
point of incremental mode.
Honest limits
Workday is the fragile one. It is the only platform of the nine with no documented public
job-board API and no directory of tenants. Every employer lives on its own host (wd1 … wd12)
under a site name it chose (External, NVIDIAExternalCareerSite, Salesforce_Careers, …), and
both have to be discovered. This Actor reads the real site name off each host's robots.txt, which
is reliable today, and falls back to probing common site names. None of that is contract. If
Workday changes its behaviour, Workday companies start coming back unresolved — the other eight
platforms are unaffected, and the run does not fail. Workday also publishes no department, no team
and no salary anywhere in its public feed, so those columns are always null for Workday jobs.
Salary coverage is thin outside Ashby. SmartRecruiters, Workable, Personio and Workday publish no compensation on their public endpoints at all. Recruitee and Rippling have the fields but employers almost never fill them in. See the coverage table above before you build anything that assumes a pay range.
Personio and Workable rate-limit by IP. Both answer HTTP 429 when requests arrive close together. Requests to them are paced and retried, but a large watchlist that leans heavily on those two can see individual companies come back unresolved. Re-run, or pass board URLs.
Discovery misses employers that aren't on these nine. DoorDash and Retool both run career sites with no public ATS job-board API. Neither is a bug; there is nothing to fetch.
Personio's XML feed usually omits the description. The <jobDescriptions> element is empty on
most boards, so description_html and description_text are frequently null there.
Rippling repeats a job once per work location. The board endpoint returns the same uuid
several times; the Actor merges the locations and de-duplicates on (ats, job_id), so 16 board rows
can legitimately become 7 jobs.
updated_at exists on three platforms only — Greenhouse, SmartRecruiters and Recruitee.
Everywhere else it is null and postedWithinDays filters on posted_at alone.
Job counts move. Boards change hour to hour. A job_count from discoverOnly is a snapshot,
not a promise about the next run.
How it works
- Calls each vendor's public job-board API directly — nine adapters, one per platform, in
src/platforms/. - Every company and every platform is fetched inside its own try/except: one dead source doesn't
take down the run, and per-platform counts and an
errorslist are written either way. - Failed requests retry with exponential backoff and honour
Retry-After. A 404 board slug is treated as "not on this platform", not as an error. - Jobs are de-duplicated on
(ats, job_id). When a company is live on two platforms, postings that match on title and location are dropped from the smaller board. - A
RUN_SUMMARYrecord is written to the default key-value store with the resolved filters, item counts per platform and per company, unresolved companies, and any per-source errors. - Runs in 256–1024 MB. A 20-company sweep finishes in well under a minute.
Data source and attribution
All data comes from job-board APIs the ATS vendors publish for exactly this purpose — Greenhouse's
is documented at developers.greenhouse.io, Ashby's and Lever's likewise. This Actor reads public
endpoints only: no authentication, no proxies, no HTML scraping, no browser.
The employer is the system of record. Job postings belong to the companies that published them,
and their content is theirs. This Actor does not correct, infer or enrich — it normalizes. If a
posting looks wrong, it looks that way on the employer's own careers page too. source_url and
apply_url on every item point back to where it came from; check the employer's and the ATS
vendor's terms for your use case before redistributing.
No personal data is collected. These are job postings, not candidates.