Multi-ATS Jobs Scraper (Greenhouse, Lever, Ashby)
Pricing
Pay per event
Multi-ATS Jobs Scraper (Greenhouse, Lever, Ashby)
Scrape job postings from Greenhouse, Lever, and Ashby in one run. Pass a list of company slugs and get a normalised dataset — title, location, department, URL, and description — across all three ATS platforms.
Pricing
Pay per event
Rating
0.0
(0)
Developer
DevilScrapes
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
0
Monthly active users
2 days ago
Last modified
Categories
Share
🎯 What this scrapes
Greenhouse, Lever, and Ashby together power the hiring pipelines of thousands of tech companies — but each platform speaks its own API dialect, and you don't always know upfront which one a given company runs on. This Actor figures that out for you: point it at a company, it auto-detects the platform, normalises the fields into one consistent schema, and writes the results to a typed Apify dataset.
Give it a bare board slug ("stripe"), a full careers URL ("https://jobs.lever.co/palantir"), or an explicit {atsType, companySlug} object if you already know the platform — mix all three forms freely in a single run. That auto-detection is the part the single-ATS wrappers on the Store don't do.
Supported platforms:
| ATS | API endpoint style |
|---|---|
| Greenhouse | REST — boards-api.greenhouse.io/v1/boards/{slug}/jobs |
| Lever | REST — api.lever.co/v0/postings/{slug}?mode=json |
| Ashby | REST — api.ashbyhq.com/posting-api/job-board/{slug} |
🔥 What we handle for you
- 🛡️ Browser fingerprint rotation — requests impersonate real browser TLS handshakes so the target sees a browser, not a bot.
- 🌐 Residential proxy rotation via Apify Proxy — fresh session and exit IP when upstream pushes back.
- 🔁 Retries with exponential backoff on
408 / 429 / 5xx— up to 5 attempts,Retry-Afterhonoured. - 🧱 Rate-limit-aware pacing — we slow down rather than get banned; partial successes surface via a clear status message.
- 🧊 Normalised, typed dataset rows — Pydantic-validated, ISO-8601 timestamps, stable IDs across all three ATS platforms.
- 💰 Pay-Per-Event pricing — you pay only for job postings that land in your dataset.
💡 Use cases
- Talent intelligence pipelines — track hiring velocity at target companies; diff successive runs to spot new headcount or sudden freezes.
- Competitive analysis — monitor which roles a competitor is hiring for and in which locations.
- Job aggregators — power a niche job board (e.g. "remote fintech jobs") without scraping fragile HTML pages.
- Recruitment automation — feed a CRM or ATS of your own with normalised job data from multiple sources in one shot.
- Market research — identify which departments are growing fastest across an industry by counting roles by department field.
- Targeted talent mapping — set
departmentFilter,locationFilter, orremoteOnlyon the run itself so you only pay for the postings you actually want, instead of filtering the whole dataset afterward. - No-code workflows (n8n / Make / Zapier) — connect via the Apify integration node; pipe new postings to a Slack channel or Google Sheet without writing code.
⚙️ How to use it
- Click Try for free at the top of the page.
- In the Companies field, enter a JSON array — mix bare slugs, careers URLs, and explicit
{atsType, companySlug}objects freely:
Bare slugs and URLs get auto-detected; the explicit object skips detection entirely.[{"atsType": "greenhouse", "companySlug": "stripe"},"https://jobs.lever.co/palantir","https://jobs.ashbyhq.com/ramp"] - Optionally set Department filter, Location filter, or Remote only to narrow results before they're written (and billed).
- Adjust Max items per company if you want a quick sample; leave at
0for all postings. - Toggle Include description off if you only need metadata (faster, smaller dataset).
- Click Start. Results stream into the run's dataset in real time.
- Export from Storage → Dataset as JSON, CSV, or Excel — or fetch via the Apify API.
For no-code workflows: install the Apify node for n8n or the Apify module for Make, connect your Apify token, and point it at DevilScrapes/multi-ats-jobs-scraper.
📥 Input
| Field | Type | Required | Default | Notes |
|---|---|---|---|---|
companies | array | yes | — | 1–1000 entries. Each is a bare slug (auto-detected), a full careers/ATS URL (auto-detected), or a {atsType, companySlug} object (skips detection). Mix freely. |
maxItemsPerCompany | integer | no | 0 | Max postings per company, applied after filtering. 0 = unlimited. |
includeDescription | boolean | no | true | Fetch and store full job description HTML in description_html. |
departmentFilter | string | no | null | Case-insensitive substring match against department or team. |
locationFilter | string | no | null | Case-insensitive substring match against location. |
remoteOnly | boolean | no | false | Keep only postings where remote == true; unknown-remote rows are excluded. |
proxyConfiguration | object | no | {"useApifyProxy": true} | Apify Proxy config. We rotate sessions to absorb rate limits. |
Example input:
{"companies": [{"atsType": "greenhouse", "companySlug": "stripe"},"https://jobs.lever.co/palantir","https://jobs.ashbyhq.com/ramp"],"maxItemsPerCompany": 50,"includeDescription": true,"departmentFilter": null,"locationFilter": null,"remoteOnly": false,"proxyConfiguration": {"useApifyProxy": true}}
📤 Output
One dataset row per job posting. All fields are present on every row; optional fields are null when the ATS doesn't expose them — salary in particular is Ashby-only, see Limitations.
| Field | Type | Notes |
|---|---|---|
company | string | Company slug from your input, lowercased |
ats | string | "greenhouse", "lever", or "ashby" — auto-detected or explicit |
job_id | string | Posting ID from the ATS |
title | string | Job title |
department | string | null | Department name |
team | string | null | Team name, when distinct from department (Lever/Ashby only — Greenhouse doesn't split the two) |
location | string | null | Location string as reported by the ATS |
remote | boolean | null | true = remote eligible; false = not; null = unknown |
salary_min | integer | null | Minimum salary. Ashby only — Greenhouse and Lever don't publish a structured salary field |
salary_max | integer | null | Maximum salary. Ashby only |
salary_currency | string | null | ISO 4217 currency code for the salary fields. Ashby only |
url | string | Canonical link to the job posting |
posted_at | string | null | ISO-8601 creation timestamp |
updated_at | string | null | ISO-8601 last-updated timestamp. Only Greenhouse exposes this; Lever and Ashby leave it null |
description_html | string | null | Full description HTML (null if includeDescription is false) |
Example output row:
{"company": "ramp","ats": "ashby","job_id": "3b1c2e9a-0000-0000-0000-000000000000","title": "Senior Software Engineer, Payments","department": "Engineering","team": "Payments","location": "New York City","remote": false,"salary_min": 211400,"salary_max": 290600,"salary_currency": "USD","url": "https://jobs.ashbyhq.com/ramp/3b1c2e9a-0000-0000-0000-000000000000","posted_at": "2026-05-02T18:11:00.000Z","updated_at": null,"description_html": "<p>About the role...</p>"}
💰 Pricing
This Actor uses Pay-Per-Event pricing — you pay only for job postings written to your dataset.
| Event | Price |
|---|---|
| Actor start (one-off warm-up) | $0.005 |
| Per result emitted | $0.0015 |
Effective rate: $1.50 / 1 000 results.
A run that collects 500 job postings across three companies costs approximately $0.75 in result charges plus the $0.005 start fee. Empty runs (no matching postings) cost only the start fee.
🚧 Limitations
- Salary is Ashby-only, and we won't fake the rest. Only Ashby's public API exposes a structured salary field. Greenhouse and Lever don't publish comp data structurally — pulling a number out of their free-text descriptions would mean unreliable regex guesses, so
salary_min/salary_max/salary_currencyare honestlynullfor those two platforms instead. - Company slugs or URLs must exist on the chosen ATS. Passing an incorrect one returns zero results for that company (logged as a warning, not a fatal error).
- Auto-detect tie-breaks on ambiguous slugs. If the same bare slug happens to resolve on more than one platform (rare — not observed in practice), we keep the one with more open postings and log a warning. Pass an explicit
{atsType, companySlug}object if you need to force a specific platform. - Ashby's job-board schema may evolve without notice — we track public API changes but cannot guarantee instant compatibility if Ashby renames or drops fields.
description_htmlfield can contain raw HTML. Strip tags on your end if plain text is required.- Pagination is handled automatically, but very large boards (thousands of postings) may produce long run times at the default proxy tier.
- The Actor does not support authenticated ATS boards (private job listings require employer credentials).
❓ FAQ
Can I scrape multiple companies in one run?
Yes — that's the design. The companies array can hold up to 1 000 entries, mixing bare slugs, careers URLs, and explicit {atsType, companySlug} objects. They run sequentially; use maxItemsPerCompany to cap each one.
Do I need to know which ATS a company uses before I start?
No. Paste a bare company slug or its careers URL and we detect the platform for you. If you already know the ATS, the explicit {atsType, companySlug} form skips detection entirely and runs slightly faster.
Does every job posting include a salary range?
No — and we'd rather tell you that up front than have you find out from a column full of nulls. Only Ashby publishes a structured salary field through its public API, so salary_min / salary_max / salary_currency come back null for Greenhouse and Lever postings. If salary data is a hard requirement, point the Actor at Ashby-hosted employers.
Does this work with private / internal job boards? No. The Actor hits the same public API endpoints visible in a browser without authentication. Internal-only boards require employer credentials that this Actor does not support.
Can I schedule this to run daily? Yes. Click Schedule in the Apify Console and set a cron expression. Each run produces a fresh snapshot — diff two runs to find new or closed postings.
Can I export to Google Sheets or Airtable?
Yes, via the Apify Console integrations or the n8n / Make connectors. Point them at DevilScrapes/multi-ats-jobs-scraper and map the output fields.
🤝 Your feedback
Found a company slug that returns wrong results? Hit an ATS quirk we don't handle? Open a support ticket or leave a review on the Apify Store listing — we read every one.
Changelog
0.2 — 2026-07-20
- Auto-detect —
companiesnow accepts a bare board slug or a full careers/ATS URL, not just an explicit{atsType, companySlug}object (which still works and skips detection). Mix all three forms in one run. - New fields:
team,salary_min,salary_max,salary_currency(Ashby only). - Renamed fields:
source_ats→ats,description→description_html. - Dropped field:
employment_type(not consistently available across all three platforms). - New filters:
departmentFilter,locationFilter,remoteOnly— narrow results before they're written and billed. - Migrated the HTTP layer from
httpxtocurl-cffiwith browser-fingerprint impersonation and rotation (org anti-blocking standard). - Corrected a stale README claim — Ashby's job-board API is REST, not GraphQL.
0.1 — 2026-06-07
- Initial release. Greenhouse, Lever, and Ashby scrapers via public JSON APIs. Normalised typed dataset rows with Pydantic validation.