ATS Hiring-Signal Scraper (Greenhouse, Lever, Ashby)
Pricing
from $3.00 / 1,000 job results
ATS Hiring-Signal Scraper (Greenhouse, Lever, Ashby)
Scrape jobs and hiring signals from Greenhouse, Lever and Ashby public JSON APIs into one uniform, deduplicated schema.
Pricing
from $3.00 / 1,000 job results
Rating
0.0
(0)
Developer
Daan Hoeven
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
2 days ago
Last modified
Categories
Share
ATS Hiring-Signal Scraper — Greenhouse, Lever & Ashby Jobs API
Scrape job postings and hiring signals from the three biggest modern Applicant Tracking Systems — Greenhouse, Lever and Ashby — straight from their public, auth-free JSON APIs. Every job from every source is returned in one clean, uniform, deduplicated schema, with normalised locations, parsed salaries and optional change-detection (new / existing / closed roles) that turns raw vacancies into intent data.
No browser, no Playwright, no fragile HTML scraping. Pure HTTP + JSON → fast, cheap and maintenance-light. These endpoints are embedded in the career pages of thousands of companies, so the ATS vendors can't quietly break them without breaking all their customers — which makes this Actor far more stable than typical job scrapers.
Keywords: Greenhouse scraper, Lever scraper, Ashby scraper, ATS scraper, jobs API, hiring signals, intent data, sales intelligence, recruitment sourcing, job board aggregator, vacancy data, salary benchmarking.
What it does
- 🟢 Greenhouse —
boards-api.greenhouse.io/v1/boards/{token}/jobs - 🔵 Lever —
api.lever.co/v0/postings/{token} - 🟣 Ashby —
api.ashbyhq.com/posting-api/job-board/{token}
For each company you list (by ATS + token, or just by career-page URL), the Actor fetches every open posting, normalises it, deduplicates across sources, applies your filters, and pushes the results to the dataset.
Key features
- Uniform cross-ATS schema — one record shape no matter the source. Fields a source doesn't provide are
null, never missing. - Hiring-signal / change detection — run on a schedule and each job is tagged
new,existingorclosed. A company that just opened five sales-engineer roles is a growth signal; a closed role is a different signal. This is the intent data layer. - Normalised data — locations parsed into
{city, country, remote}, salaries parsed into{min, max, currency, interval}, HTML descriptions cleaned to plain text. - Cross-ATS deduplication — stable
jobIdof the form{ats}_{token}_{id}; first occurrence wins. - Powerful filtering — by keyword (title/description), location (incl.
remote), and department/team, plus a hardmaxItemscost cap. - Robust by design — per-company error isolation (one failure never kills the run), retries with exponential backoff,
429/Retry-Afterrespected, andSCHEMA_CHANGEDwarnings if an ATS ever changes its response shape.
Who it's for — use cases
- Sales intelligence / intent data — detect company growth by department. "Acme is hiring 5 Sales Engineers" is a buying signal for B2B sellers.
- Recruitment & sourcing — a live, structured feed of open roles across your target companies, with salary and location already parsed.
- Job-board aggregators — pull thousands of clean, deduplicated postings from many companies into one schema, ready to publish.
- Market & salary benchmarking — aggregate compensation data across companies, roles and regions.
- Competitive monitoring — watch which roles competitors open and close over time.
Input
Provide at least one of companies or companyUrls. Everything else is optional.
| Field | Type | Description | Default |
|---|---|---|---|
companies | array<object> | Explicit { ats: "greenhouse" | "lever" | "ashby", token: string }. | — |
companyUrls | array<string> | Career-page / job-board URLs; ATS + token are derived automatically. | [] |
keywords | array<string> | Keep jobs whose title/description match any term (case-insensitive). | [] |
locations | array<string> | Keep jobs matching location/city/country (use remote for remote roles). | [] |
departments | array<string> | Keep jobs matching department/team. | [] |
includeDescription | boolean | Include cleaned plain-text + HTML description. | true |
includeCompensation | boolean | Include normalised compensation where available. | true |
detectChanges | boolean | Hiring-signal mode: tag jobs new/existing/closed vs. the previous run. | false |
maxItems | integer | Hard result cap for cost control (0 = unlimited). | 0 |
proxyConfiguration | object | Apify proxy settings (datacenter is enough). | { "useApifyProxy": true } |
Change detection needs a previous snapshot, so run the Actor on a schedule to get meaningful
new/closedsignals.
Example input
{"companies": [{ "ats": "greenhouse", "token": "stripe" },{ "ats": "lever", "token": "netflix" },{ "ats": "ashby", "token": "ramp" }],"companyUrls": ["https://boards.greenhouse.io/airbnb"],"keywords": ["sales engineer", "account executive"],"locations": ["Amsterdam", "remote"],"departments": ["Sales"],"includeDescription": true,"includeCompensation": true,"detectChanges": false,"maxItems": 0,"proxyConfiguration": { "useApifyProxy": true }}
You can also point the Actor at plain career-page URLs and let it figure out the ATS:
{"companyUrls": ["https://boards.greenhouse.io/stripe","https://jobs.lever.co/netflix","https://jobs.ashbyhq.com/ramp"]}
Output
One record per job, in a uniform schema:
{"jobId": "greenhouse_acme_4012345","source": "greenhouse","companyName": "Acme Inc.","companyToken": "acme","title": "Senior Sales Engineer","department": "Sales","team": "Revenue","location": "Amsterdam, NL","city": "Amsterdam","country": "NL","remote": true,"employmentType": "Full-time","description": "Plain-text description…","descriptionHtml": "<p>…</p>","compensation": { "min": 80000, "max": 110000, "currency": "EUR", "interval": "year" },"applyUrl": "https://boards.greenhouse.io/acme/jobs/4012345","postedAt": "2026-06-01T00:00:00Z","updatedAt": "2026-06-03T00:00:00Z","scrapedAt": "2026-06-04T10:00:00Z","changeStatus": "new"}
changeStatus is only present in detectChanges mode. Any field a source doesn't expose is null, so the schema stays consistent across all three ATS providers.
At the end of every run a small summary is written to the key-value store (RUN_SUMMARY): { companiesOk, companiesFailed, totalJobs }.
Finding a company's token
The token is the company slug in its public career-page URL:
| ATS | Career-page URL | Token |
|---|---|---|
| Greenhouse | https://boards.greenhouse.io/stripe | stripe |
| Lever | https://jobs.lever.co/netflix | netflix |
| Ashby | https://jobs.ashbyhq.com/ramp | ramp |
If you're not sure, just drop the full URL into companyUrls and the Actor resolves the ATS and token for you. Unresolvable URLs are skipped with a warning instead of failing the run.
Pricing
This Actor uses the pay-per-result model: you're charged per job pushed to the dataset. Use maxItems to set a hard cost ceiling. Because everything runs over plain HTTP JSON (no headless browser), compute cost is minimal.
FAQ
Does this need an API key or login for Greenhouse / Lever / Ashby? No. All three endpoints are public, auth-free JSON APIs embedded in companies' career pages.
How stable is it?
Very. These endpoints power thousands of live career pages, so the vendors can't break them without breaking their own customers. The only realistic maintenance signal is a SCHEMA_CHANGED warning, which the Actor logs automatically.
What are "hiring signals"?
Turn on detectChanges and run on a schedule. The Actor compares each run to the previous one and tags jobs as new, existing or closed — letting you spot when a company starts (or stops) hiring for a role or department. That's intent data for sales and market research.
Does it scrape applicant or candidate data? No. Only public job postings and company metadata. No personal/applicant data (GDPR by design).
Can I get salaries?
Where the ATS exposes them (notably Ashby and some Lever boards), yes — normalised into {min, max, currency, interval}. Set includeCompensation: true (the default).
Which other ATS providers are supported? v1 covers Greenhouse, Lever and Ashby. The adapter pattern makes adding Workable, Recruitee or Personio straightforward in a future version.
Running locally
npm installnpm run check # typecheck + lint + testsapify run # runs with .actor/ input
Built with Node.js 20 + TypeScript (strict), Apify SDK v3, Crawlee HttpCrawler and got-scraping. Pure API-first — no browser.