Multi-ATS Jobs Scraper (Greenhouse, Lever, Ashby) avatar

Multi-ATS Jobs Scraper (Greenhouse, Lever, Ashby)

Pricing

Pay per event

Go to Apify Store
Multi-ATS Jobs Scraper (Greenhouse, Lever, Ashby)

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

DevilScrapes

Maintained by Community

Actor 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:

ATSAPI endpoint style
GreenhouseREST — boards-api.greenhouse.io/v1/boards/{slug}/jobs
LeverREST — api.lever.co/v0/postings/{slug}?mode=json
AshbyREST — 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-After honoured.
  • 🧱 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, or remoteOnly on 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

  1. Click Try for free at the top of the page.
  2. In the Companies field, enter a JSON array — mix bare slugs, careers URLs, and explicit {atsType, companySlug} objects freely:
    [
    {"atsType": "greenhouse", "companySlug": "stripe"},
    "https://jobs.lever.co/palantir",
    "https://jobs.ashbyhq.com/ramp"
    ]
    Bare slugs and URLs get auto-detected; the explicit object skips detection entirely.
  3. Optionally set Department filter, Location filter, or Remote only to narrow results before they're written (and billed).
  4. Adjust Max items per company if you want a quick sample; leave at 0 for all postings.
  5. Toggle Include description off if you only need metadata (faster, smaller dataset).
  6. Click Start. Results stream into the run's dataset in real time.
  7. 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

FieldTypeRequiredDefaultNotes
companiesarrayyes1–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.
maxItemsPerCompanyintegerno0Max postings per company, applied after filtering. 0 = unlimited.
includeDescriptionbooleannotrueFetch and store full job description HTML in description_html.
departmentFilterstringnonullCase-insensitive substring match against department or team.
locationFilterstringnonullCase-insensitive substring match against location.
remoteOnlybooleannofalseKeep only postings where remote == true; unknown-remote rows are excluded.
proxyConfigurationobjectno{"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.

FieldTypeNotes
companystringCompany slug from your input, lowercased
atsstring"greenhouse", "lever", or "ashby" — auto-detected or explicit
job_idstringPosting ID from the ATS
titlestringJob title
departmentstring | nullDepartment name
teamstring | nullTeam name, when distinct from department (Lever/Ashby only — Greenhouse doesn't split the two)
locationstring | nullLocation string as reported by the ATS
remoteboolean | nulltrue = remote eligible; false = not; null = unknown
salary_mininteger | nullMinimum salary. Ashby only — Greenhouse and Lever don't publish a structured salary field
salary_maxinteger | nullMaximum salary. Ashby only
salary_currencystring | nullISO 4217 currency code for the salary fields. Ashby only
urlstringCanonical link to the job posting
posted_atstring | nullISO-8601 creation timestamp
updated_atstring | nullISO-8601 last-updated timestamp. Only Greenhouse exposes this; Lever and Ashby leave it null
description_htmlstring | nullFull 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.

EventPrice
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_currency are honestly null for 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_html field 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-detectcompanies now 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_atsats, descriptiondescription_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 httpx to curl-cffi with 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.