Multi-ATS Jobs Scraper (Greenhouse, Lever, Ashby)
Pricing
from $2.00 / 1,000 job results
Multi-ATS Jobs Scraper (Greenhouse, Lever, Ashby)
Scrape public job postings from Greenhouse, Lever and Ashby career pages into one clean dataset. Filter by keyword, remote or location, get salary ranges, and use incremental mode to receive only new jobs on each run.
Pricing
from $2.00 / 1,000 job results
Rating
0.0
(0)
Developer
José Alaín Díaz Carrero
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
12 days ago
Last modified
Categories
Security
Limited permissionsShare
Collect public job postings from thousands of company career pages in one normalized format. Give it company names (or career-page URLs) hosted on Greenhouse, Lever or Ashby and get clean JSON/CSV/Excel with title, location, remote flag, department, employment type, salary range (when the company publishes it), dates and apply links.
It only uses the official public, no-authentication job-board APIs that these platforms provide for career pages. No login, no browser, no proxies, no HTML scraping, so it is fast, cheap and stable.
What you can do with it
- Build a job board or newsletter from a list of target companies.
- Track hiring at competitors or prospects (sales / recruiting intelligence).
- Get only new jobs since your last run (schedule it hourly/daily and send results to Slack, email, a webhook or Google Sheets with Apify integrations).
- Compare published salary ranges across companies.
- Filter by keyword, remote, and location.
Input
| Field | Type | Description |
|---|---|---|
companies | array | Objects { "ats": "greenhouse" | "lever" | "ashby", "token": "<company slug>" } |
boardUrls | array of strings | Career-page URLs; ATS and token are auto-detected (boards.greenhouse.io/x, job-boards.greenhouse.io/x, jobs.lever.co/x, jobs.ashbyhq.com/x) |
keywords | array | Keep jobs whose title/department/team contains any keyword |
excludeKeywords | array | Drop jobs containing any of these |
searchInDescription | boolean | Also match keywords inside the description (default false) |
remoteOnly | boolean | Only remote jobs |
locationContains | array | Keep jobs whose location contains any string (e.g. ["Chile", "United Kingdom", "EMEA"]) |
onlyNewSinceLastRun | boolean | Return only jobs not seen before (see below) |
stateStoreName | string | Named key-value store holding seen job IDs (default multi-ats-jobs-state) |
includeDescription | boolean | Add plain-text description (default false) |
maxItemsPerCompany | integer | Cap per company (0 = unlimited) |
maxConcurrency | integer | Boards fetched in parallel (default 3, max 10) |
You need at least one company or board URL. The company token is the slug in the career-page URL, e.g. https://boards.greenhouse.io/airbnb → airbnb.
Example inputs
1. Everything from a few companies (minimal)
{"companies": [{ "ats": "greenhouse", "token": "airbnb" },{ "ats": "lever", "token": "spotify" },{ "ats": "ashby", "token": "ramp" }]}
2. Remote engineering jobs, filtered, from career-page URLs
{"boardUrls": ["https://job-boards.greenhouse.io/gitlab","https://jobs.lever.co/spotify"],"keywords": ["engineer", "data"],"excludeKeywords": ["intern"],"remoteOnly": true,"locationContains": ["United States", "Canada"]}
3. Daily job alert: only new jobs, capped per company
{"companies": [{ "ats": "greenhouse", "token": "gitlab" },{ "ats": "ashby", "token": "ramp" }],"keywords": ["product manager"],"onlyNewSinceLastRun": true,"stateStoreName": "my-daily-pm-alert","maxItemsPerCompany": 50}
Output
One dataset item per job (see sample_output.json in the repository for full examples):
{"id": "8731718002","ats": "greenhouse","company": "GitLab","companyToken": "gitlab","title": "Principal Security Researcher","location": "Remote, Canada; Remote, United States","isRemote": true,"department": "Product Security","team": null,"employmentType": null,"salaryMin": 203200,"salaryMax": 275000,"salaryCurrency": "USD","salaryInterval": "year","postedAt": "2026-09-05T17:47:29.000Z","updatedAt": "2026-10-15T19:24:46.000Z","applyUrl": "https://job-boards.greenhouse.io/gitlab/jobs/8731718002","jobUrl": "https://job-boards.greenhouse.io/gitlab/jobs/8731718002","scrapedAt": "2026-10-25T18:22:11.427Z"}
Field availability per ATS
| Field | Greenhouse | Lever | Ashby |
|---|---|---|---|
| company name | real name | slug | slug |
| department / team | department | department / team | department / team |
| employmentType | only if the company uses a custom field | yes | yes |
| salary | when the company publishes pay ranges | when published | when published |
| postedAt | first published | created | published |
| updatedAt | yes | no | no |
null means the platform did not provide the value. isRemote is taken from the ATS flag when available, otherwise detected from the location/title text.
Incremental mode ("only new jobs")
With onlyNewSinceLastRun: true the Actor remembers the IDs of jobs it already returned, per ATS + company, in a named key-value store (stateStoreName, default multi-ats-jobs-state). The first run returns everything that matches your filters; later runs return only postings that were not returned before.
How it behaves, step by step:
- The board is fetched and your filters (keywords, remote, location) are applied.
- Jobs whose ID is already stored are skipped.
- If
maxItemsPerCompanyis set, the remaining new jobs are cut to that number. - Only the jobs actually returned are added to the stored "seen" list.
What this means in practice:
maxItemsPerCompanyand incremental mode: jobs beyond the cap are not marked as seen. They are returned in a later run, so nothing is lost; with a cap of 50 and 120 new jobs you get 50 now, 50 on the next run and 20 on the one after.- Filtered-out jobs are not marked as seen either, so they appear if they start matching (for example after you change your keywords).
- IDs of jobs that disappear from a board are dropped from the stored list, so the state stays small. A job that is removed and later re-posted with the same ID is treated as new.
- If a run stops early (for example because your maximum run cost was reached), the jobs not yet returned are not marked as seen and will come in the next run.
- Use a different
stateStoreNamefor each independent monitoring setup (one per Schedule or per client). To start over, change the name or delete the store in Apify Console. - Combine with an Apify Schedule and an integration (Slack, email, webhook, Google Sheets) to build a job alert pipeline.
Pricing (pay per event)
The Actor is designed for pay-per-event pricing: a small start event plus a charge per job returned (job-result). It stops early if your maximum run cost is reached. Check the pricing section on this page for current prices.
FAQ
Is this legal / allowed? It calls the public job-board endpoints that Greenhouse, Lever and Ashby expose precisely so companies' postings can be embedded and consumed elsewhere. No authentication is bypassed and no private data is accessed. You are responsible for how you use the data and for complying with applicable laws and the terms of the career pages.
Does maxItemsPerCompany lose jobs in incremental mode? No. Jobs beyond the cap are not marked as seen, so they are returned in later runs. See "Incremental mode" above.
How do I reset incremental mode? Change stateStoreName or delete the key-value store with that name in Apify Console (Storage, Key-value stores).
What happens if a company has no jobs or its token is wrong? A company with no matching jobs simply returns nothing. A wrong token or a company that left the ATS is logged as a warning (usually HTTP 404) and the rest of the run continues.
Can I export to CSV, Excel or Google Sheets? Yes. The dataset can be downloaded as JSON, CSV, Excel, XML or HTML, or sent to Google Sheets, Slack, email or a webhook with Apify integrations.
Do I need proxies or cookies? No. The Actor only calls public endpoints, so no proxy, login or cookies are needed.
How do I find a company's token? Open the company's careers page; if it redirects to boards.greenhouse.io/<token>, jobs.lever.co/<token> or jobs.ashbyhq.com/<token>, that is the token. You can also just paste the URL into boardUrls.
A company failed with HTTP 404. The token is wrong or the company no longer uses that ATS. Failures are logged per company and never stop the run; other companies are still processed.
Why is company a slug for Lever and Ashby? Those APIs do not return the company display name.
Does it support EU Lever (api.eu.lever.co) or custom domains? Not yet; only the standard global endpoints.
How fast is it? A typical board with a few hundred jobs takes about one second. Memory use is low (256–512 MB is enough).
Can I get descriptions? Yes, set includeDescription: true (plain text).
Legal note
This Actor only reads the public job-board APIs that Greenhouse, Lever and Ashby offer for companies' public career pages. It does not log in, does not bypass any access control, does not collect private data and does not scrape candidate or recruiter profiles. Job postings are published by the companies themselves to be seen and applied to. Greenhouse, Lever and Ashby are trademarks of their respective owners; this Actor is independent and not affiliated with or endorsed by them. You are responsible for how you use the data and for complying with applicable laws (including data protection rules) and the terms of the career pages you query.
Notes
- Only public job-board APIs are used. No proxies needed.
- Requests are retried with exponential backoff on 429/5xx and run with low concurrency.
- Greenhouse boards with thousands of jobs return large responses; use a few companies per run for best speed.