Greenhouse & Lever Jobs API — ATS Job Aggregator
Pricing
from $2.00 / 1,000 job posting results
Greenhouse & Lever Jobs API — ATS Job Aggregator
Job scraper API for Greenhouse & Lever ATS: structured JSON, deduplicated results, parsed salary ranges, first_published dates. No LinkedIn, no bans. Pay-per-event: $0.002 per result.
Pricing
from $2.00 / 1,000 job posting results
Rating
0.0
(0)
Developer
Farside
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
ATS Job Aggregator (Greenhouse & Lever)
ATS Job Aggregator pulls structured job postings straight from company Applicant Tracking Systems — Greenhouse and Lever — through their public JSON APIs. No LinkedIn, no scraping of rendered pages, no account bans, no duplicate jobs. Every record comes out as clean JSON with the fields LinkedIn-based scrapers usually can't give you: first_published timestamps, parsed salary ranges, and the company → ATS mapping.
Why use ATS Job Aggregator?
LinkedIn scrapers have three chronic problems: jobs duplicated across scrapes, accounts getting banned, and missing salary data. Because this Actor reads the companies' own ATS APIs, none of those apply — you get the same data the company publishes, in structured JSON, with no anti-bot risk. The APIs return the job's true posting date and (where published) its salary range, which is exactly what you need to detect new jobs and compare pay across companies.
Who is this for?
- Recruiting agencies / headhunters — batch-monitor all your client companies' open roles. Point the Actor at a list of Greenhouse board tokens and Lever slugs, run it on a schedule, and diff
first_publishedbetween runs to see what's new this week. - Job seekers with a target company list — keep a personal feed of every opening at your 20 dream companies, with salary ranges and apply links, without browsing 20 career pages daily.
- Market researchers / analysts — count open roles per company, per department, per location; track hiring velocity over time; compare salary bands across the industry (USD/EUR/SGD/MYR/etc. parsed automatically).
Why this beats the LinkedIn-based scrapers
| LinkedIn scrapers | This Actor | |
|---|---|---|
| Data source | Rendered LinkedIn pages | Company ATS public APIs |
| Duplicate jobs within a run | Common | Deduplicated via stable dedupe_key |
| Account bans | Frequent | No accounts involved — nothing to ban |
first_published | Not reliably available | Direct from Greenhouse / Lever |
| Structured salary | Missing or free-text | salary_min / salary_max / salary_currency / salary_period parsed |
| Run on schedule via Apify | Yes | Yes, with API access and integrations |
How it works
The Actor takes two input lists and fetches everything:
- Greenhouse —
GET https://boards-api.greenhouse.io/v1/boards/{token}/jobslists every open job; the Actor then fetches each job's detail endpoint (skippable) to capturefirst_published, department, and the compensation block. - Lever —
GET https://api.lever.co/v0/postings/{slug}?mode=jsonreturns the full posting list includingcreatedAtand salary text, in a single request.
Salary text is parsed with a currency-aware regex engine — "$60,000 - $97,000/year", "€2,400 - €5,600/month", "RM 3,000 - RM 5,000", "110,000 - 200,000/year SGD" all become structured salary_min / salary_max / salary_currency plus a salary_period (year / month / hour / week) so hourly rates never masquerade as annual pay. When a range can't be parsed, the raw text is kept in salary_text and the numbers are set to null — nothing is guessed.
Failures are isolated per company (60s timeout, 2 retries). One dead token never kills the run: the failure is reported in a clearly-flagged record and in the run log.
How much does it cost?
Pay-per-event pricing: $0.002 per job result (the result-item event). No per-run or startup fees unless you enable additional synthetic events in Apify Console.
- 1,000 job records ≈ $2
- A 100-company monitor producing 5,000 records ≈ $10
Compare: Amazon product data on Apify ≈ $3/1K, Google Maps ≈ $1.5–4/1K. You're paying for structured, deduplicated, salary-enriched data — not proxy traffic.
How to run it
- Collect your company list. Greenhouse: open the company's jobs board at
boards.greenhouse.io/{token}— the{token}in the URL is what you paste (e.g.airtable,anthropic). Lever: openjobs.lever.co/{slug}— the{slug}is what you paste (e.g.palantir). - Fill in the input (see below) — either or both lists, empty ones are fine.
- Hit Start. Data lands in the dataset — download as JSON, CSV, or Excel, or pull it through the API.
Input example
{"greenhouse_tokens": ["airtable", "anthropic", "datadog"],"lever_slugs": ["palantir", "canva"],"enrich_details": true,"max_results": 0}
greenhouse_tokens— Greenhouse board tokens (optional, default[])lever_slugs— Lever company slugs (optional, default[])enrich_details—true(default) fetches every Greenhouse job's detail endpoint forfirst_published, department and salary;falseskips detail requests and only returns list-level fields (faster, but those three fields staynullfor Greenhouse jobs). Lever data is always complete — its single request includes everything.max_results— stop after N job records across all companies;0= unlimited
Output example
{"company": "Airtable","source_ats": "greenhouse","job_id": "8403127002","title": "Account Executive, Strategic Accounts","location": "Remote - US","department": "Sales","salary_min": 272000,"salary_max": 354000,"salary_currency": "USD","salary_text": "$272,000 — $354,000","salary_period": "year","first_published": "2026-02-28T14:04:24.000Z","last_updated": "2026-03-27T15:33:08-04:00","apply_url": "https://job-boards.greenhouse.io/airtable/jobs/8403127002","scraped_at": "2026-08-31T03:40:00.000Z","dedupe_key": "5c23f0aeefa61d9a","errors": []}
Fields
| Field | Type | Description |
|---|---|---|
company | string | Company name (Greenhouse) or slug (Lever). Falls back to the board token for Greenhouse when details are not fetched |
source_ats | string | greenhouse or lever |
job_id | string | ATS-internal job ID |
title | string | Job title |
location | string | Location as published |
department | string | Department / team |
salary_min / salary_max | number | Parsed range bounds (null when not published or unparseable) |
salary_currency | string | ISO 4217 code (USD, EUR, MYR, SGD, ...) |
salary_text | string | Raw salary fragment as published |
salary_period | string | year / month / hour / week — compare only values sharing the same period |
first_published | string | First posting date (Greenhouse first_published, Lever createdAt), normalized to UTC; null for Greenhouse when enrich_details: false |
last_updated | string | Last modification in the ATS (Greenhouse only; always null for Lever) |
apply_url | string | Direct posting/application URL |
scraped_at | string | When this record was scraped (UTC) |
dedupe_key | string | sha256 of company identifier + normalized title + normalized location + job_id (first 16 hex chars) — stable across runs, so you can diff two scrapes without re-fetching everything |
errors | array | Always [] on normal records; a failed company produces one flagged record with the error message here |
All fields are nullable. You can download the dataset in JSON, HTML, CSV, or Excel format from the Storage tab.
Tips
- Diff two runs for new jobs:
dedupe_keyis deterministic — join runs on it and flag records whosefirst_publishedis newer than your last scrape. (Each run pushes the full current posting list again; the stable key is what makes the diff cheap.) - Keep costs flat: with
enrich_details: false, Greenhouse runs make 1 request per company instead of 1 per job — use it when you only need the posting list. - Mix both ATSs freely: companies don't all use the same ATS; combine
greenhouse_tokens+lever_slugsin one run for a single unified dataset. - Schedule it: use Apify's Scheduler with weekly runs and email/API delivery for a zero-touch monitoring pipeline.
FAQ & support
Will my IP get banned? No. The Actor only calls public, undocumented-but-stable JSON endpoints of the ATS providers themselves — the same endpoints the companies' own career pages use. There is no account and no anti-bot wall to trip.
Why is salary_min null sometimes? Many companies simply don't publish pay ranges. If the range exists but is in an exotic format, the raw text stays available in salary_text.
Do I need proxies or login credentials? No. No proxies, no cookies, no credentials.
Our Actors only extract what companies have chosen to publish publicly. The data may include personal data (e.g. names inside job descriptions), which is protected by the GDPR in the EU and similar laws worldwide. Only scrape for legitimate purposes and consult your lawyers if unsure. Use the Issues tab for feedback and the API tab for programmatic access.
Local development
pip install -r requirements.txt# put your input in storage/key_value_stores/default/INPUT.json, then:apify run# unit tests:python -m unittest discover -s tests