Greenhouse, Lever, Ashby & Workday Jobs Scraper with Salaries
Open jobs from Greenhouse, Lever, Ashby, Workday and 4 more ATS — by company domain, or search ~11,000 company boards by keyword. Posted salaries, filters, and a daily only-new feed.
Verification
Output fields
Authoritative field set from the dataset schema.
| Field | Type |
|---|---|
company | string|null |
company_name | string|null |
ats | string|null |
ats_slug | string|null |
detected_by | string|null |
job_id | string|null |
title | string|null |
department | string|null |
team | string|null |
location | string|null |
locations | array|null |
remote | boolean|null |
employment_type | string|null |
posted_at | string|null |
posted_text | string|null |
updated_at | string|null |
apply_url | string|null |
requisition_id | string|null |
salary_min | number|null |
salary_max | number|null |
salary_currency | string|null |
salary_interval | string|null |
salary_text | string|null |
description | string|null |
notice | string|null |
notice_type | string|null |
source_url | string|null |
Sample row
{
"source_url": "https://jobs.ashbyhq.com/openai/240d459b-696d-43eb-8497-fab3e56ecd9b",
"company": "ashby:openai",
"company_name": null,
"ats": "ashby",
"ats_slug": "openai",
"detected_by": "explicit",
"job_id": "240d459b-696d-43eb-8497-fab3e56ecd9b",
"title": "Research Engineer",
"department": "Research",
"team": "Research",
"location": "San Francisco",
"locations": [],
"remote": null,
"employment_type": "FullTime",
"posted_at": "2025-04-05T00:03:20.653+00:00",
"posted_text": null,
"updated_at": null,
"apply_url": "https://jobs.ashbyhq.com/openai/240d459b-696d-43eb-8497-fab3e56ecd9b/application",
"requisition_id": null,
"salary_min": 250000,
"salary_max": 445000,
"salary_currency": "USD",
"salary_interval": "1 YEAR",
"salary_text": "$250K - $445K",
"description": null
} Inputs
| Field | Type | Req | Default | Description |
|---|---|---|---|---|
company | string | no | "stripe.com" | A company domain (acme.com), a careers URL, or an explicit board such as 'greenhouse:stripe', 'lever:gopuff' or 'workday:nvidia'. The Actor works out which applicant-tracking system the company uses (Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Recruitee, Breezy). Kept forever alongside 'companies' — saved tasks and API callers send it. |
companies | array | no | ["stripe.com","notion.so","greenhouse:figma"] | Look up many companies in ONE run instead of starting a run per company. Takes the same values as 'Company'. Combined with 'Company' if you fill both, and duplicates are removed. Billing is unchanged: you are charged per job returned, not per company submitted. |
searchAllCompanies | boolean | no | false | Ignore 'Companies' and search a directory of ~9,400 Greenhouse and Ashby boards (plus ~1,900 Lever boards if you add Lever below) with your filters — e.g. title 'data analyst' + location 'Canada'. Boards are read live at run time, in random order, and the run stops at 'Maximum jobs for the whole run'. Title words are matched on titles only in this mode. |
atsPlatforms | array | no | [] | Which directories to search: greenhouse, ashby, lever. Default greenhouse + ashby. Lever is opt-in because its boards publish no light list, so searching all of them is much slower. |
maxTotalResults | integer | no | 500 | Run-wide spend cap for 'Search every company': the search stops once this many matching jobs are found (each still billed per job). Not used when you list companies — there 'Maximum jobs per company' applies. |
maxResults | integer | no | 1000 | Cap on jobs returned PER COMPANY, not per run — so a batch cannot silently return only the first rows overall and read as 'these companies aren't hiring'. When a board holds more, the run log states the board's own total next to what was returned. |
postedAfter | string | no | — | Keep only jobs published on or after this date (YYYY-MM-DD) — this is what turns the Actor into a weekly new-openings feed. Jobs whose board states no publish date (Workday says 'Posted Today' rather than a date) are KEPT, because dropping them would hide every Workday job and read as 'not hiring'. |
titleKeywords | array | no | [] | Keep only jobs whose title contains ANY of these words (case-insensitive), e.g. 'engineer', 'account executive'. Applied before billing — you pay only for matching jobs. Leave empty for every job. |
keywordsInDescription | boolean | no | false | Widen 'Job title contains' to the full job text, e.g. find every job mentioning 'Snowflake' or 'visa sponsorship'. Works on Greenhouse, Ashby, Lever, Workable and Recruitee (their boards carry the text); Workday, SmartRecruiters and Breezy still match on title, and so does 'Search every company'. Does not add the description to your rows unless 'Include the full job description' is on. |
excludeKeywords | array | no | [] | Drop jobs whose title contains ANY of these words, e.g. 'intern', 'senior', 'manager'. Applied before billing. |
departments | array | no | [] | Keep only jobs whose department or team contains ANY of these words, e.g. 'Engineering', 'Sales'. Boards that publish no department (Workday) match nothing here. Applied before billing. |
employmentTypes | array | no | [] | Keep only jobs whose employment type contains ANY of these, e.g. 'full time', 'contract', 'intern' — spelling variants such as 'FullTime' and 'Full-time' match alike. Applied before billing. |
onlyNew | boolean | no | false | Return only jobs this Actor has not delivered to you before under the same 'State name' — schedule it daily for a new-openings feed, Workday included (which states no posting date). The first run returns every matching job and sets the baseline. Remembered per job ID, so you are never billed twice for the same posting. |
stateKey | string | no | "default" | Names the memory 'Only jobs new since my last run' uses. Keep one name per feed (e.g. 'sales-hiring-signals'); use a new name to start a fresh baseline. Letters, digits and hyphens. |
locationKeywords | array | no | [] | Keep only jobs whose location (primary or any additional one) contains ANY of these words, e.g. 'London', 'Remote', 'United States'. Matches the board's own wording. Applied before billing. |
remoteOnly | boolean | no | false | Keep only jobs the board marks remote — its own remote flag, or its own location wording such as 'Remote - US'. Jobs whose board says nothing either way are left out. Applied before billing. |
salaryOnly | boolean | no | false | Keep only jobs whose board publishes pay: a range on Greenhouse, Ashby, Lever and Recruitee, or Breezy's pay text, where the employer entered one (SmartRecruiters, Workday and Workable never publish pay). Applied before billing. |
includeDescription | boolean | no | false | Off by default: descriptions are the bulk of the payload (one large board is ~16 KB per job) and most buyers filter on title, team and location first. Turn it on when you need the text. |
proxyConfiguration | object | no | {"useApifyProxy":false} | Optional. These are public vendor APIs and the plain path is the fastest one, so a proxy is genuinely not needed for the boards themselves. Enable it only if a company's own careers page blocks the detection step. |
Pricing
Pay per result (job-posting) — no charge for empty runs.
Example: 1000 job postings at $0.00125/result ≈ $1.25.
FAQ
Which applicant-tracking systems are supported?
Eight: Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable, Recruitee and Breezy HR. Each is read from that vendor's own public job-board endpoint, so there is no login, no API key and no browser rendering involved. 'Search every company' covers Greenhouse, Ashby and (opt-in) Lever.
How does it know which ATS a company uses?
Tried in this order. If you name the board — 'greenhouse:stripe', 'workday:nvidia', or a careers URL — that wins. Otherwise it looks for an ATS link on the company's own careers page, which proves the board belongs to them. Failing that it derives a likely board name from the domain and accepts it only if the vendor states a company name or website that matches (Workable states the employer's own website; a Workable account with no open jobs is passed over, because large employers keep dormant ones). Last, it looks for a Workday tenant named after the company and accepts it only if one of its careers sites or the hiring organisation on a live job names the company: on 2026-10-31 that resolved nvidia.com, salesforce.com, disney.com and intel.com; boeing.com's tenant names neither, so it needs 'workday:boeing'. Every row records which path applied, in `detected_by`.
Can I search without a list of companies?
Yes — turn on 'Search every company'. Your filters run across ~11,000 boards: 5,974 Greenhouse and 3,418 Ashby by default, plus 1,926 Lever if you add Lever under 'Platforms to search'. Boards are read live at run time in random order, and the run stops at 'Maximum jobs for the whole run' (default 500). On 2026-10-31 'machine learning' + remote only returned 60 jobs from 31 employers after reading 1,684 boards in under 3 minutes. Title words match titles only in this mode. If the run's time limit or a vendor's rate limit stops it before every board was read, you get a free 'truncated' row saying how many were searched. The directory lists boards known to be live on 2026-10-31; a board created since is not in it, so name it in 'Companies' instead.
How does 'Only jobs new since my last run' work?
The Actor remembers the ID of every job it delivered to you under a 'State name' (default 'default') and skips those next time — before the per-company cap, so 'maximum 100' means 100 new jobs. The first run returns every match and sets the baseline. It works on Workday too, which states no posting date, and you are never billed twice for the same posting. Use a different state name per feed; a new name starts a fresh baseline.
What happens when a company cannot be resolved?
You get one free, unbilled row explaining why and naming the input that would work, for example 'lever:acme'. It is never a silent empty result, because 'we could not find the board' and 'this company is not hiring' are different answers and only one of them is our fault. On a 14-company test on 2026-09-06, 11 resolved from the domain alone and the three that did not were companies whose careers page is entirely JavaScript.
Why is posted_at empty on some rows?
Workday publishes a relative phrase — 'Posted Today' — rather than a date. Rather than invent a timestamp, those rows leave `posted_at` null and keep the board's own wording in `posted_text`. The other systems publish a real timestamp. For the same reason, the 'Posted on or after' filter keeps undated rows instead of dropping them, which would hide every Workday job; for a Workday feed use 'Only jobs new since my last run' instead.
Do I pay per company or per job?
Per job returned. Submitting 50 companies that collectively have 1,000 open roles costs the same as one company with 1,000 roles. Companies that resolve to nothing, and boards with no open jobs, are free.
How many jobs can one company return?
Whole boards, and they get large — on 2026-09-06 the boards in the example input held 617, 132 and 765 open roles, and one Workday tenant tested held 2,000. Use 'Maximum jobs per company' to cap it; the cap applies per company, not per run, and the run log always states the board's own total next to what was returned so a cap can never read as completeness.
Why is `remote` sometimes null instead of false?
Because the board did not say. Ashby, SmartRecruiters, Workable, Recruitee and Breezy publish a real remote flag; Greenhouse and Workday publish none, so for those the value is read from the board's own wording — 'Remote - US' gives true, 'Hybrid - London' gives false. A posting that just says 'Dublin' says nothing about remote work, and this Actor returns null there rather than asserting false. Filtering for remote roles gives the same answer either way; the difference only shows when you filter for non-remote, and there null is the honest value.
Does it return salaries?
Yes, wherever the employer posted a range: Greenhouse, Ashby and Lever all publish one when asked. On 2026-10-31, 93 of 140 Figma jobs (Greenhouse), 645 of 810 OpenAI jobs and 165 of 172 Ramp jobs (Ashby) carried a range; Stripe and Vercel posted none on the board, and Lever boards rarely do (2 of 779 at Gopuff). Recruitee publishes a structured range where the employer entered one, and Breezy a pay text (kept verbatim in `salary_text`, never parsed into numbers). SmartRecruiters, Workday and Workable publish no pay field. You get `salary_min`, `salary_max`, `salary_currency`, the vendor's interval and its own wording in `salary_text`. Greenhouse states no interval field — its range title ('Annual Base Salary Range', 'Internship') says it, so an hourly intern range shows as e.g. 46 with that title. Turn on 'Only jobs with a posted salary' to pay only for jobs that have one.
Can I filter before paying?
Yes. 'Job title contains', 'Job title must NOT contain', 'Department or team contains', 'Location contains' and 'Employment type' match any of the words you give (case-insensitive) against the board's own wording; 'Also match title words in the job description' widens the title match to the job text (Greenhouse, Ashby, Lever, Workable, Recruitee); 'Remote jobs only' keeps jobs the board marks remote; 'Only jobs with a posted salary' keeps jobs with a posted range or pay text. Filters run before billing, and the cap 'Maximum jobs per company' counts matching jobs, so 100 engineers means 100 engineers rather than the engineers among the first 100. A company with no matching job returns one free marker row.
What is not in the output?
Skills, requirements and seniority as separate columns, and any salary the employer did not post. Everything returned comes from the board; nothing here is estimated or inferred by a model.
Is a proxy needed?
No. These are public vendor endpoints and the direct path is the fastest one, so the proxy input is off by default. Turn it on only if a particular company's own careers page blocks the detection step.
Guides & use cases
Related actors