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
2
Total users
1
Monthly active users
2 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.
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.
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 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 |
|---|---|---|
boards | — | List of career page URLs. Required. |
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. |
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), 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.
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.
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