ATS Jobs Scraper - Greenhouse, Lever, Ashby, Workable, Workday avatar

ATS Jobs Scraper - Greenhouse, Lever, Ashby, Workable, Workday

Pricing

$5.00 / 1,000 jobs

Go to Apify Store
ATS Jobs Scraper - Greenhouse, Lever, Ashby, Workable, Workday

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

NeverEmpty

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

7 hours ago

Last modified

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:

rowTypeokWhat it meansCharged?
jobtrueA real open jobYes
no-open-rolestrueThe board is live and has no open jobs (or none matching your filters)No
board-not-foundfalseNo board with that name exists on that ATSNo
unavailablefalseThe ATS could not be reached, or the entry could not be read as a board; the reason column says whyNo
budget-reachedfalseThe 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 itNo
partialtrueThe 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 onlyNo

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 createdAt as 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 / internship are unified wherever the ATS publishes it (Lever calls it "Permanent")
  • workplaceType is unified to remote / 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, postedAt100%100%100%100%
department100%100%100%13%
employmentType0%97%100%100%
workplaceType, isRemote0%100%100%100%
salaryMin / salaryMax0%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

FieldTypeDefaultWhat it does
boardsarraygreenhouse:stripe, lever:spotify, ashby:ramp, workable:lystOne entry per company board, up to 200. See the three accepted forms below.
titleContainsstring(empty)Keep only jobs whose title contains this text (case-insensitive)
departmentContainsstring(empty)Keep only jobs whose department or team contains this text
locationContainsstring(empty)Matches the primary location and every secondary location
remoteOnlybooleanfalseKeep only jobs the ATS itself flags as remote
postedWithinDaysinteger0 (off)Keep only jobs first published within N days. Jobs with an unknown date are excluded rather than assumed recent.
maxJobsPerBoardinteger0 (no limit)Cap of job rows per board, applied after filtering
includeDescriptionbooleanfalseAdd the full description as plain text in descriptionText
maxRetriesinteger3Total 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 includeDescription is off by default
  • A board that does not exist returns exactly 1 board-not-found row and is not charged. An entry that cannot be read as a board at all returns 1 unavailable row 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 in remoteHint.
  • What do they pay? - salaryMin / salaryMax where the employer published structured figures.
  • Did they stop hiring, or did the scraper break? - two different rows: no-open-roles and unavailable.

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-roles rows 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 - stripe in boards.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.