Greenhouse, Lever, Ashby & Workday Job Scraper | 7 ATS
Pricing
from $1.50 / 1,000 job postings
Greenhouse, Lever, Ashby & Workday Job Scraper | 7 ATS
Scrape job postings from 7 ATS - Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable and Recruitee - into one unified schema. Every run tells you what changed since the last one: new, updated and removed listings. No login, no browser, no proxies. $1.50 per 1,000 jobs.
Pricing
from $1.50 / 1,000 job postings
Rating
0.0
(0)
Developer
Softyways
Maintained by CommunityActor stats
0
Bookmarked
3
Total users
2
Monthly active users
16 days ago
Last modified
Categories
Share
Greenhouse, Lever, Ashby & Workday job scraper with change tracking
Scrape job postings from company career pages across 7 applicant tracking systems — Greenhouse, Lever, Ashby, Workday, SmartRecruiters, Workable and Recruitee — and get one clean dataset with every open role. Paste a careers URL, get structured jobs: title, location, department, employment type, apply link.
The part other scrapers leave to you: every run also tells you what changed since the last one — which jobs are new, which were edited, and which disappeared.
Don't know which companies to start from? Pick a ready-made set and press Start — AI labs, fintech, developer tools, European tech or large enterprises. No URLs to hunt down.
Runs on Apify, so you can schedule it daily, call it from the API, and pipe the output into a spreadsheet, a database or your own app.
What does this job scraper do?
It reads the public JSON endpoints the ATS platforms already serve to their own careers pages. No login, no browser, no proxies, no anti-bot circumvention — which is why a run of 745 jobs takes about three seconds.
For each career page you give it, you get:
- every currently open job posting, normalized into the same shape regardless of which ATS it came from;
- a
changeTypeon every row:new,updated,unchangedorremoved.
Which ATS platforms are supported?
| Platform | Example URL to paste |
|---|---|
| Greenhouse | https://job-boards.greenhouse.io/stripe |
| Lever | https://jobs.lever.co/spotify |
| Ashby | https://jobs.ashbyhq.com/linear |
| Workday | https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite |
| SmartRecruiters | https://jobs.smartrecruiters.com/BoschGroup |
| Workable | https://apply.workable.com/moneyfarm |
| Recruitee | https://channable.recruitee.com |
Paste the careers URL and the platform is detected automatically. You can also write it explicitly as greenhouse:stripe.
Which companies can I scrape without looking anything up?
Pick one or more ready-made company sets and press Start. Each set is a curated list of real career pages, checked against the live endpoints — nothing to paste, nothing to look up.
| Set | Companies include | Jobs when last checked |
|---|---|---|
| AI labs and AI tooling | OpenAI, Anthropic, ElevenLabs, Cursor, Cognition, Suno | ~1,900 |
| Fintech, banking and crypto | Stripe, Brex, Binance, Affirm, Coinbase, Robinhood | ~1,850 |
| Developer tools and SaaS | MongoDB, Okta, Cloudflare, GitLab, Figma, Notion, Linear | ~1,900 |
| European tech | SumUp, Wolt, Celonis, Spotify, N26, Monzo, Contentful | ~1,250 |
| Large enterprise career sites | Salesforce, Databricks, Palantir, Visa | ~2,600 |
The sets are a starting point, not a limit: anything you add in Your own career pages is fetched too, and a company asked for twice is only fetched — and billed — once.
How to scrape jobs from a Greenhouse job board
- Open the company's careers page and copy the URL from the address bar.
- Paste it into the Your own career pages field. Add as many companies as you like.
- Click Start. The first run returns every open job, all marked
new. - Run it again tomorrow — or put it on a schedule — and the second run tells you what moved.
The same three steps work for Lever, Ashby, Workday, SmartRecruiters, Workable and Recruitee.
How do I know which jobs are new since yesterday?
Change tracking is on by default. State lives in a named key-value store (ats-jobs-tracker-<trackerName>) that survives between runs, so a daily schedule gives you a rolling feed of hiring changes.
new— was not in the previous runupdated— title, location, department or employment type changed;previousTitleandpreviousLocationhold the old valuesunchanged— still there, untouchedremoved— was in the previous run and is gone from the career page now
Set outputMode to changes to get only the first, second and fourth — useful when you are feeding alerts rather than rebuilding a table.
Use trackerName to keep separate sets of companies on separate histories.
What data do you get for each job?
| Field | Description |
|---|---|
key | Stable id across runs (ats:board:jobId) |
ats, board | Which platform and which company board it came from |
companyName | Company name as the platform reports it |
title | Job title |
department, team | As categorized by the company |
location, locations | Primary location and the full list |
remote | Remote flag where the platform exposes one |
employmentType | Full-time, contract, internship… where available |
url, applyUrl | Link to the posting and to the application form |
publishedAt, updatedAt | Platform timestamps where available |
description | Full text, only when includeDescription is on |
changeType | new, updated, unchanged, removed |
firstSeenAt, lastSeenAt, removedAt | When this Actor first and last saw the job |
previousTitle, previousLocation | What the values were before an update |
You can download the dataset in JSON, CSV, Excel, HTML or XML, or read it straight from the API.
Example row:
{"ats": "greenhouse","board": "figma","companyName": "Figma","jobId": "5364702004","title": "Account Executive, Emerging Enterprise","department": "Sales","location": "Berlin, Germany","locations": ["Berlin, DE"],"employmentType": null,"remote": null,"url": "https://boards.greenhouse.io/figma/jobs/5364702004","publishedAt": "2024-11-01T06:05:10-04:00","changeType": "new","firstSeenAt": "2026-07-31T12:00:00.000Z","previousTitle": null}
Input
| Field | Default | What it does |
|---|---|---|
companySets | — | One or more ready-made sets: ai-labs, fintech, dev-tools, europe, enterprise. |
boards | — | List of career page URLs, added to whatever the sets bring. |
trackChanges | true | Compare with the previous run and label every job. |
outputMode | all | all returns every job; changes returns only new, updated and removed ones. |
trackerName | default | Runs sharing a name share history. |
includeDescription | false | Adds the full job description. Slower, much larger output. |
maxConcurrency | 5 | How many career pages to fetch at once. |
boardTimeoutSecs | 600 | Time limit for one career page, so a slow ATS cannot hold up the run. |
How much does it cost to scrape job postings?
$1.50 per 1,000 jobs, billed per row returned. No monthly fee, no minimum.
| You track | Roughly |
|---|---|
| 10 small companies (~300 jobs) | $0.45 per run |
| A daily watch on 3,000 jobs | $4.50 per run |
| One-off pull of 10,000 jobs | $15.00 |
Two ways to spend less: leave includeDescription off, and set outputMode to changes once your first run has established the baseline — after that you only pay for what actually moved, which on a normal day is a small fraction of the board.
Apify's free plan comes with monthly credits, so you can try it without paying anything.
Cost limit
Every run has a maximum total charge, and one dataset row is one charge. A run that finds more jobs than its limit pays for cannot return all of them.
When that happens this Actor stops writing instead of letting the extra rows disappear quietly:
- the rows it can still pay for go to changes first — new, updated and removed listings before unchanged ones;
- the jobs left out are not recorded as seen, so the next run reports them again instead of swallowing them forever;
RUN_SUMMARYcarriescostLimitReached,itemsWithheldByCostLimitandcostLimitUsd.
To fit a large run: raise Maximum cost per run in the run options, set outputMode to changes, or split the career pages across several runs.
What happens when a career page breaks?
A board that goes quiet is never reported as "everything removed", because a false removal would have you take down listings that are still open:
- if a career page errors (network, 404, rate limit, or an ATS in a maintenance window that answers with a web page instead of JSON), its jobs are carried over untouched;
- if it answers with an empty list, removals are withheld until it comes back empty twice in a row — on a single run, a silent format change and a company that genuinely stopped hiring look identical.
Boards held back this way are listed under removalsWithheld in RUN_SUMMARY, together with counts, failures and cost-limit status.
What happens when a career page is slow, or enormous?
A run that hits its timeout returns nothing at all — everything it collected is thrown away with the container. So this Actor never lets one career page take the whole run down with it:
- each career page gets
boardTimeoutSecs(10 minutes by default). Past that it is dropped, the run carries on with the others, and the dropped page keeps its jobs — nothing is reported as removed for it; - fetching also stops on its own before the run's timeout, leaving enough time to write the results. You get what was collected, and
RUN_SUMMARYsays what was left out underboardsOutOfTime; - a few career sites are bigger than one run can read — the largest Workday tenants run past 5,000 postings. Those are reported under
boardsReadInPart, and no removals are claimed for them, because everything past the last page read would be indistinguishable from a job that was taken down.
If a career page keeps running out of time, give the run a longer timeout, raise its own time limit, or put that one company in its own run.
FAQ
Is scraping job postings legal?
This Actor reads public job postings through the endpoints the ATS platforms serve to their own careers pages — the same data any visitor sees, without logging in or bypassing any protection. Job postings are company records, not personal data: no candidate profiles, no emails, no contact details are collected. You are responsible for how you use the output; if you are unsure about your use case, take legal advice.
Do I need a proxy?
No. These are public JSON endpoints, so there is nothing to rotate around. That also keeps runs fast and cheap.
Why are department and posting date empty for Workday jobs?
Workday's listing endpoint returns the title, location and requisition id, and a relative string like "Posted Today" instead of a date. Fetching the real values would cost one extra request per job, so they are left empty rather than guessed.
One Workday company failed but the others worked. Why?
Workday takes career sites offline for scheduled maintenance, typically at weekends, and answers with a redirect to its maintenance page instead of JSON. The run says so in plain words and carries on with the other companies; the jobs already known for that board are kept, never reported as removed. Run it again once the window is over.
Can I get only the changes?
Yes — set outputMode to changes. You still pay only for the rows returned.
Why is the description excluded by default?
ATS platforms rewrite whitespace and tracking parameters in descriptions constantly. Including them multiplies dataset size, and comparing them would flag every job as updated every day. The change fingerprint deliberately ignores the description.
What about Personio?
Personio's public search endpoint answers 429 to every request, including a single one after a long pause. That is a closed door, not a rate limit, and this Actor does not work around protections.
Can you add another ATS?
Open an issue on the Actor's Issues tab with the careers URL of a company that uses it. Any platform with a public JSON endpoint is straightforward to add.
Development
$apify run
$npm test