Greenhouse, Lever, Ashby & Workday Jobs Scraper - 9 ATS API avatar

Greenhouse, Lever, Ashby & Workday Jobs Scraper - 9 ATS API

Pricing

from $4.00 / 1,000 job scrapeds

Go to Apify Store
Greenhouse, Lever, Ashby & Workday Jobs Scraper - 9 ATS API

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

Make No Mistakes LLC

Maintained by Community

Actor 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.

PlatformEndpointSalaryDepartment / teamDates
greenhouseboards-api.greenhouse.io/v1/boards/{token}/jobs✅ pay ranges when the employer opts into transparencydepartment onlyposted and updated
ashbyapi.ashbyhq.com/posting-api/job-board/{org}✅ full structured comp — min, max, currency, intervaldepartment and teamposted
leverapi.lever.co/v0/postings/{org}salaryRange when filled indepartment and teamposted
smartrecruitersapi.smartrecruiters.com/v1/companies/{co}/postingsdepartment + functionposted and updated
recruitee{co}.recruitee.com/api/offers/⚠️ structured but usually emptydepartmentposted and updated
ripplingapi.rippling.com/platform/api/ats/v1/board/{co}/jobs⚠️ payRangeDetails, rarely populateddepartment and sub-teamposted
personio{co}.jobs.personio.de/xmldepartment + recruiting categoryposted
workday{tenant}.wd{N}.myworkdayjobs.com/wday/cxs/{tenant}/{site}/jobs❌ neitherposted
workableapply.workable.com/api/v1/widget/accounts/{co}department + functionposted

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.

BoardDeptTeamCityCountryRemote flagTypeSalaryPostedUpdatedDescription
greenhouse / gitlab100%55%80%22%100%100%100%
greenhouse / stripe100%80%45%0%100%100%100%
ashby / ramp100%100%95%100%100%100%100%100%
ashby / openai100%100%92%100%100%90%100%100%
lever / matchgroup100%100%98%100%95%50%100%100%
lever / palantir100%100%100%100%0%100%100%
smartrecruiters / Ubisoft2100%95%100%100%100%100%100%
recruitee / vandebron100%100%100%100%100%23%100%100%100%
rippling / rippling100%100%95%98%100%0%100%100%
personio / wandelbots100%100%67%100%100%0%
workday / nvidia100%100%100%100%100%
workday / salesforce100%100%100%100%100%
workable / adverity100%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:

  1. Greenhouse, Ashby, Lever, SmartRecruiters, Recruitee, Rippling — one cheap GET each, all candidates in parallel.
  2. 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).
  3. 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.com resolves to a six-job Personio board owned by a different company called Scale; Scale AI's real board is on Greenhouse under scaleai. When the slug guess is ambiguous there is no signal that distinguishes them. If the resolved job_count looks 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_matches in discoverOnly mode, 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: false is the honest answer, not an error. candidates_tried tells 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
}
FieldTypeDefaultNotes
companiesarray of string["stripe","openai","ramp"]Names, domains, board slugs or board URLs. Mix them freely.
discoverOnlybooleanfalseReturn one row per company — which ATS, which board, how many jobs — and stop. Charged as company-resolved, not job-scraped.
platformsarray of enumall nineRestrict discovery and fetching to a subset.
maxItemsinteger500Hard 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.
postedWithinDaysintegerIncremental mode. Keeps a job when the newer of posted_at / updated_at falls inside the window.
titleIncludesarray of stringOR'd, case-insensitive, matched against the job title.
locationstringSubstring match against location_raw, city, state and country, case-insensitive.
remoteOnlybooleanfalseKeep only jobs the platform flags remote, or whose location says remote / anywhere / distributed.
includeDescriptionbooleantrueOff 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_type is mapped onto FULL_TIME, PART_TIME, CONTRACT, TEMPORARY, INTERNSHIP, VOLUNTEER. A value nobody recognises passes through upper-cased rather than being dropped.
  • salary_period is YEAR, MONTH, WEEK, DAY or HOUR.
  • Salary comes only from structured compensation fields. Descriptions are never regex-mined for pay. A guessed number in a salary_min column is worse than an honest null.
  • is_remote is true when the platform sets a remote flag, or when location_raw contains 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 / country come from the platform's structured address where it publishes one (Ashby, SmartRecruiters, Recruitee, Workable) and from parsing location_raw otherwise. ATS location strings are unconstrained free text, so treat the parse as best-effort and location_raw as the truth.
  • Multiple locations are joined with | in location_raw; city/state/country describe the primary one.
  • Dates are dates, YYYY-MM-DD, in every field on every platform.
  • company is 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, so company there is derived from what you passed in: "Match Group" stays "Match Group", but a bare slug like openai becomes "Openai". Pass the name you want to see, or read company_slug, which is always exact.

Cost

Pay-per-event. You pay for what the run actually returns.

EventPriceWhen
Actor start$0.005once per run
job-scraped$0.004per job written to the dataset
company-resolved$0.001per 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 (wd1wd12) 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 errors list 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_SUMMARY record 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.