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

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

Pricing

$3.00 / 1,000 job returneds

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

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

Scrape open job listings from the three big ATS job boards — Greenhouse, Lever and Ashby — straight from their public JSON APIs. No key, no login. Pass companies as "ats:token" (e.g. greenhouse:stripe) and get every posting normalized to one clean schema.

Pricing

$3.00 / 1,000 job returneds

Rating

5.0

(1)

Developer

Dami's Studio

Dami's Studio

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

1

Monthly active users

a day ago

Last modified

Share

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

Scrape open job listings straight from the public JSON APIs of the three most common applicant-tracking systems — Greenhouse, Lever and Ashby. No API key, no login, no anti-bot to fight. Point it at a list of company boards and get every posting back in one normalized schema.

Thousands of companies post their jobs through exactly these three boards. Instead of writing a scraper per company, give this actor a list like ["greenhouse:stripe","lever:spotify","ashby:ramp"] and it figures out the right endpoint for each and merges the results.

Input

FieldNotes
companiesArray of "ats:token" strings. ats is greenhouse, lever, or ashby; token is the company's board slug. e.g. greenhouse:stripe, lever:spotify, ashby:openai.
ats + companyConvenience pair for a single board (e.g. ats: "greenhouse", company: "stripe"). Ignored when companies is set.
maxItemsMax postings per company. 0 (default) = all open postings.
proxyConfigurationOptional, off by default. These public APIs have no anti-bot, so no proxy is needed. Only enable one if you hit IP-based rate limits across many boards.

Where do I find the token?

It's the slug in the public careers URL:

  • Greenhouse → boards.greenhouse.io/<token> (also <token>.greenhouse.io)
  • Lever → jobs.lever.co/<company>
  • Ashby → jobs.ashbyhq.com/<name>

Output

One dataset row per job posting, deduped by url:

{
"ok": true,
"ats": "greenhouse",
"company": "stripe",
"title": "Account Executive, AI Sales",
"location": "San Francisco, CA",
"department": "1650 AI GTM Strategy & Solutions",
"employmentType": "FullTime",
"url": "https://stripe.com/jobs/search?gh_jid=7964697",
"postedAt": "2026-06-05T15:44:04-04:00",
"description": "Who we are… (plain text, HTML stripped)",
"salary": "$211.4K – $290.6K • Offers Equity"
}

Field availability differs by ATS, so several fields can be null even on a successful row:

FieldGreenhouseLeverAshby
employmentTypenull (not exposed)✓ (commitment)
salarynull (not exposed)best-effort (often null)✓ (structured, may be null)
department✓ (dept / team)✓ (dept / team)
location / postedAt / url✓ when present, else null✓ when present, else null✓ when present, else null

title and company are always present on a success row. Every other field above is null when the board doesn't supply it — this is honest data, not a failed scrape.

Greenhouse job descriptions come HTML-entity-encoded; the actor decodes and strips them to clean plain text.

Diagnostic rows (ok:false)

When a board has no open postings, uses a wrong/retired slug, or hits a network/blocked error, the actor pushes a single ok:false diagnostic row instead of job rows, for example:

{ "ok": false, "errorCode": "NO_RESULTS", "error": "The request succeeded but returned no results for this input.", "ats": "greenhouse", "company": "acme", "note": "No open postings for greenhouse:acme." }

An ok:false row means that one board had nothing to return — not that the run failed. These rows are never charged. Filter on ok:true to get only billable job postings. Empty input produces an ok:false row with errorCode: "BAD_INPUT" instead of crashing the run.

Billing & empty boards

Billed per job posting returned (job), and only for genuine ok:true rows. A board with no open postings (or a wrong/retired slug) produces a single ok:false diagnostic row explaining why — and is not charged. Network/blocked errors and empty/invalid input likewise return a coded ok:false diagnostic row instead of failing the whole run, and are never charged.

Example

{
"companies": ["greenhouse:stripe", "greenhouse:airbnb", "lever:spotify", "ashby:ramp", "ashby:openai"],
"maxItems": 0
}