ATS Jobs Scraper - Greenhouse, Lever, Ashby, Workable, Workday
Pricing
$5.00 / 1,000 jobs
ATS Jobs Scraper - Greenhouse, Lever, Ashby, Workable, Workday
Pull open jobs from a company's Greenhouse, Lever, Ashby, Workable or Workday board into one identical row shape. Zero open roles, a missing board and a failed fetch come back as three different rows, never as a silently empty result. Unofficial, not affiliated with these services.
Pricing
$5.00 / 1,000 jobs
Rating
0.0
(0)
Developer
NeverEmpty
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
7 hours ago
Last modified
Categories
Share
Pull open jobs from company job boards on Greenhouse, Lever, Ashby, Workable and Workday and get back one identical row shape, read straight from each ATS's own public API.
Unofficial. This Actor is not affiliated with, endorsed by, or sponsored by Greenhouse, Lever, Ashby, Workable or Workday. All product names are trademarks of their respective owners.
No HTML scraping. No proxies. No API keys. Nothing that breaks when a careers page is redesigned.
Don't know a company's board name? ATS Job Board Finder turns a domain into the board name this Actor takes - it tells you which ATS a company uses and what their board is called.
Why this one is different
Most job-board Actors return an empty dataset when something goes wrong, and an empty dataset when there is genuinely nothing to report. Those are not the same answer, and if you are monitoring hiring activity the difference is the whole point.
This Actor always returns a row. Every board you ask for produces at least one row, tagged in the rowType column:
rowType | ok | What it means | Charged? |
|---|---|---|---|
job | true | A real open job | Yes |
no-open-roles | true | The board is live and has no open jobs (or none matching your filters) | No |
board-not-found | false | No board with that name exists on that ATS | No |
unavailable | false | The ATS could not be reached, or the entry could not be read as a board; the reason column says why | No |
budget-reached | false | The run hit the Max total charge you set. Says how many rows of that board were delivered, and names every board that was not fetched because of it | No |
partial | true | The rows you got are real, but the board has more than one run returns. Says how many of how many, and why it stopped. Workday only | No |
You are charged only for job rows.
If your charge limit runs out, you are told which boards were cut
Apify lets you cap a run with Max total charge. When that cap is hit, the platform silently stops accepting rows - which would make whole boards disappear from your result with no explanation. This Actor watches for that and writes a free budget-reached row for the board that was cut short and for every board it therefore did not fetch, so a truncated run can never be mistaken for a complete one.
It does not invent a remote flag
isRemote is set only when the ATS itself says so - Lever's workplaceType, Ashby's isRemote, Workable's telecommuting. When the source is silent, isRemote stays null.
A guess based on the location text lives in a separate column named remoteHint, so you always know which is which. remoteOnly filtering uses isRemote only - it never passes a guess off as a fact. This matters: a job located in "Remote, Oregon" is not a remote job.
It does not invent a salary
salaryMin / salaryMax / salaryCurrency / salaryInterval are filled only from structured compensation data (Ashby publishes it as numbers). Nothing is parsed out of description prose, and a missing figure stays null - never 0.
It adds the columns the raw APIs don't have
- Lever returns
createdAtas a UNIX millisecond number - here it is ISO 8601, like every other source - Workable returns a date with no time, Greenhouse returns a timezone offset - all normalised to ISO 8601 UTC
full-time/part-time/contract/internshipare unified wherever the ATS publishes it (Lever calls it "Permanent")workplaceTypeis unified toremote/hybrid/onsite- Ashby's unlisted postings are excluded, so you don't get jobs that aren't actually public
What each ATS actually publishes
The columns are identical everywhere, but the ATSs do not all publish the same facts. Measured on the four default boards, 2026-08-28:
| Greenhouse (580) | Lever (89) | Ashby (137) | Workable (8) | |
|---|---|---|---|---|
title, jobUrl, postedAt | 100% | 100% | 100% | 100% |
department | 100% | 100% | 100% | 13% |
employmentType | 0% | 97% | 100% | 100% |
workplaceType, isRemote | 0% | 100% | 100% | 100% |
salaryMin / salaryMax | 0% | 0% | 96% | 0% |
Greenhouse's public board API simply does not carry employment type, workplace type or salary, so those stay null rather than being guessed. department is filled by reading Greenhouse's /departments endpoint, which is why it is populated even without descriptions. remoteOnly therefore never matches a Greenhouse board - use remoteHint if you want the guess.
Workday carries even less. Its board listing returns only the title, location, a job code and a relative "Posted 3 Days Ago" string. So department, employmentType, salary and isRemote stay null, and postedAt stays null because a relative phrase is not a date. That means remoteOnly, departmentContains and postedWithinDays filter out every Workday row - use titleContains and locationContains instead, or remoteHint for the guess.
Workday is also identified differently. The other four accept a bare board name; Workday needs its board URL (https://<tenant>.wd5.myworkdayjobs.com/<Site>), because the tenant and the site name are both part of the address. A bare name therefore never tries Workday. One run returns up to 500 roles per Workday board; if a board has more, you get a partial row telling you how many of how many you received.
Input
| Field | Type | Default | What it does |
|---|---|---|---|
boards | array | greenhouse:stripe, lever:spotify, ashby:ramp, workable:lyst | One entry per company board, up to 200. See the three accepted forms below. |
titleContains | string | (empty) | Keep only jobs whose title contains this text (case-insensitive) |
departmentContains | string | (empty) | Keep only jobs whose department or team contains this text |
locationContains | string | (empty) | Matches the primary location and every secondary location |
remoteOnly | boolean | false | Keep only jobs the ATS itself flags as remote |
postedWithinDays | integer | 0 (off) | Keep only jobs first published within N days. Jobs with an unknown date are excluded rather than assumed recent. |
maxJobsPerBoard | integer | 0 (no limit) | Cap of job rows per board, applied after filtering |
includeDescription | boolean | false | Add the full description as plain text in descriptionText |
maxRetries | integer | 3 | Total attempts per board, including the first, for temporary ATS errors (HTTP 429 / 5xx). A 404 is a permanent answer and is not retried. |
Three ways to name a board
{"boards": ["greenhouse:stripe", // <ats>:<board name>"https://jobs.lever.co/spotify", // paste the job board URL"ramp" // name only - Greenhouse, Lever, Ashby and Workable are tried// Workday needs the board URL: "https://<tenant>.wd5.myworkdayjobs.com/<Site>"]}
Giving the name only costs up to four lookups (Workday is only tried when you give its board URL) but finds the board wherever it lives. If you already know the ATS, name it - the Actor will then never wander onto a same-named board at a different company.
Output columns
rowType, ok, reason, ats, companySlug, companyName, jobId, title, department, team, employmentType, locationRaw, locationsAll, country, city, workplaceType, isRemote, remoteHint, salaryMin, salaryMax, salaryCurrency, salaryInterval, salaryText, postedAt, updatedAt, jobUrl, applyUrl, descriptionText, source
Every row from every ATS has all 29 columns, in the same order - including the marker rows. Your spreadsheet, database table or dashboard never changes shape depending on which ATS answered.
Measured, not estimated
All figures below were measured on 2026-08-28 with the shipped build:
- Default input (4 boards: stripe, spotify, ramp, lyst) → 814 jobs in 7.2 seconds, 4.1 MB downloaded, 0 failures
- Greenhouse alone: 0.35 MB of jobs plus 0.46 MB of departments without descriptions, versus 4.4 MB with them - which is why
includeDescriptionis off by default - A board that does not exist returns exactly 1
board-not-foundrow and is not charged. An entry that cannot be read as a board at all returns 1unavailablerow rather than vanishing - Test suite: 93 checks across two files, plus 40 deliberate defects injected into the source, all 40 caught by the tests. The run loop itself - charging, marker rows and the charge-limit path - is driven by fakes so every branch is exercised.
Questions this answers
- How do I get every open job from a company's Greenhouse board? -
"greenhouse:stripe", or paste the board URL. - Same for Lever, Ashby, Workable and Workday? - yes, and every row comes back in the same 29 columns.
- Which of these jobs are actually remote? -
isRemote, set only when the ATS says so; the guess lives separately inremoteHint. - What do they pay? -
salaryMin/salaryMaxwhere the employer published structured figures. - Did they stop hiring, or did the scraper break? - two different rows:
no-open-rolesandunavailable.
Typical uses
- Hiring intelligence / GTM signals - a company that just opened 12 engineering roles is a different sales target than one that closed them all.
no-open-rolesrows make "they stopped hiring" a fact you can see rather than a gap in the data. - Job aggregators - one schema across five ATSs, so adding a source doesn't mean rewriting the parser.
- Compensation research - structured salary ranges where the employer published them, never guessed.
- Recruiting ops - track a competitor's open roles daily and diff on
jobId.
Notes and limits
- Only public job boards are read. Nothing here needs a login, and no private ATS data is accessed.
- Boards are read from each ATS's own public job board API, so results reflect what the company itself publishes.
- Board names ("slugs") are the company identifier used by the ATS -
stripeinboards.greenhouse.io/stripe. - Rate limiting is handled with retries for HTTP 429 and 5xx. Permanent answers such as 404 are reported, not retried. Every request has a 45-second timeout, so a silent server cannot hang the run.
- Up to 200 boards per run.
Other tools by NeverEmpty
Every NeverEmpty Actor follows the same rule: it never returns an empty result to mean two different things, and it only charges for rows that actually carry an answer.
- ats-board-finder - a company domain to the ATS and board name it uses
- website-contacts - emails, phones and social profiles from any website, no invented addresses
- domain-lookup - WHOIS, DNS, MX, SPF, DMARC and SSL expiry for a list of domains
Support
Found a board that behaves oddly, or want another ATS supported? Open an issue on the Issues tab of this Actor and include the board name - it will be reproduced against the live API before anything is changed.