Greenhouse Jobs Scraper - Fast ATS API
Pricing
Pay per event
Greenhouse Jobs Scraper - Fast ATS API
Jobs from Greenhouse boards (Airbnb, Stripe, Anthropic, Figma, thousands more). Title/dept/location filters + diff mode for daily new-jobs. New `report` mode: 1 row/company with hiringScore, velocity, priorityRoles, remoteShare. $0.50/1k jobs or $0.025/report.
Pricing
Pay per event
Rating
0.0
(0)
Developer
An Tan Huynh
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
2 days ago
Last modified
Categories
Share
Greenhouse Jobs Scraper – Fast ATS API
Scrape Greenhouse-hosted career pages via the public JSON API. One row per job, all open positions returned in a single API call, no proxy required.
$0.50 per 1,000 jobs ($0.0005 / job, pay-per-event).
Who is this for?
Thousands of companies host their careers on Greenhouse — including Airbnb,
Stripe, Anthropic, Coinbase, Reddit, Figma, Robinhood, Instacart, Discord,
Pinterest, DoorDash, Chime, SoFi and many more. If a board URL looks like
https://boards.greenhouse.io/<company> or
https://job-boards.greenhouse.io/<company>, this actor can scrape it.
Input
| Field | Type | Default | Notes |
|---|---|---|---|
boardSlugs | string[] | required | Company slugs or full board URLs. The actor extracts the slug automatically. Pass as many companies as you want in a single run. |
includeContent | bool | false | Add ?content=true to pull full description HTML, departments, offices and AI disclaimer flags. Response grows ~15× (Airbnb: 127 KB → 2.3 MB). |
maxJobsPerBoard | int | 5000 | Safety cap per board (no pagination on Greenhouse). |
titleKeywords | string[] | (empty) | Keep only jobs whose title contains any of these (case-insensitive substring). Example: ["engineer","data scientist","ml"]. |
titleExclusions | string[] | (empty) | Drop any job whose title contains any of these. Example: ["senior","manager","intern"]. |
departments | string[] | (empty) | Keep jobs whose department name contains any of these substrings. Auto-enables includeContent because departments are only returned with content. Case-insensitive. |
locations | string[] | (empty) | Keep jobs whose locationName contains any of these substrings. Case-insensitive. |
workplaceTypes | string[] | (empty) | Match the normalised workplaceType: remote, hybrid, onsite. Many boards expose this via "Workplace Type" or "Location Type" metadata (Airbnb, Anthropic). |
onlyRemote | bool | false | Shortcut: keep only jobs with workplaceType=Remote or locationName containing "remote". |
updatedAfter | string | (empty) | ISO date / datetime cut-off; keep only jobs with updatedAt on or after. |
onlyNewSinceLastRun | bool | false | Diff mode. The actor remembers (per stateKey + board) the set of jobIds seen on previous runs in a named key-value store, and on each subsequent run only pushes jobs that are new. Every pushed row carries isNew: true. Pair with Apify Scheduler for daily deltas. |
stateKey | string | default | Optional label for the diff state bucket. Different stateKey values keep independent "seen" memories; shared values share memory. |
proxyConfiguration | object | (none) | Optional Apify Proxy – the API is open and usually works direct. |
Example input:
{"boardSlugs": ["airbnb", "stripe", "anthropic"],"includeContent": false,"maxJobsPerBoard": 5000}
Output (one row per job)
{"boardSlug": "airbnb","jobId": 8184174,"internalJobId": 3541546,"title": "Account Manager","absoluteUrl": "https://careers.airbnb.com/positions/8184174?gh_jid=8184174","locationName": "London, United Kingdom","workplaceType": "Hybrid","metadata": [{"name": "Is this job part of ACC?", "value": "no", "valueType": "yes_no"},{"name": "Workplace Type", "value": "Hybrid", "valueType": "single_select"}],"departments": [],"offices": [],"requisitionId": "MULTI","firstPublished": "2026-09-09T04:35:19-04:00","updatedAt": "2026-09-29T20:00:54-04:00","applicationDeadline": null,"language": "en","companyName": "Airbnb","contentHtml": null,"aiDisclaimer": null,"aiOptOutUrl": null,"scrapedAt": "2026-10-15T20:55:27+00:00"}
When includeContent=true, contentHtml, departments, offices,
aiDisclaimer and aiOptOutUrl are populated.
workplaceType normalisation
Boards tag workplace under different metadata names. The actor looks for
Workplace Type, Location Type, Work Arrangement, Work Setup,
Remote Type, Work Location, Remote Work, and maps the value to
Remote / Hybrid / Onsite whenever recognisable. Boards that don't
tag workplace at all (Stripe, Figma, Discord in our smoke tests) return
workplaceType: null.
Smoke run numbers (2026-10-16)
| Input | Boards | Rows | Cost |
|---|---|---|---|
| Default smoke – 3 boards no content | airbnb, discord, figma | 355 | ~$0.00013 compute + $0.1775 PPE |
| Content + filter – engineering & data, remote/hybrid | airbnb only (includeContent=true, departments=[engineering,data], workplaceTypes=[remote,hybrid]) | 48 | ~$0.00029 compute + $0.024 PPE |
| Big boards + updatedAfter | anthropic + stripe, updatedAfter=2026-09-01 | 1115 | ~$0.00076 compute + $0.5575 PPE |
Field coverage over the default smoke (355 rows):
| Field | Coverage |
|---|---|
jobId / title / absoluteUrl / locationName / companyName / firstPublished / updatedAt / language / requisitionId | 100% |
workplaceType | 46% (Airbnb tags it; Discord and Figma don't) |
Airbnb workplace distribution: 136 Remote / 27 Hybrid / 2 Onsite. Anthropic workplace distribution: 348 Onsite / 14 Remote / 3 Hybrid / 22 untagged.
Pricing
Pay-per-event. Two charge types:
$0.0005perjobrow pushed (defaultmode: "jobs").$0.025perreportrow pushed (mode: "report", one row per company).
Example runs:
- 3 boards (Airbnb + Discord + Figma, 355 jobs, jobs mode) → $0.18.
- 2 big boards (Anthropic + Stripe, 1,115 jobs, jobs mode) → $0.56.
- 1 board full content HTML (Airbnb, ~165 jobs, jobs mode) → $0.08.
- 3 boards in report mode (Airbnb + Stripe + Anthropic) → $0.075 (3 reports).
Compute is almost free (direct API, no proxy, ~1 second per board).
Report mode (mode: "report")
Instead of one row per job, emit one hiring-signal report per company. Each report row carries:
totalOpenRoles+hiringScore0-100 +hiringLabel(aggressive_hiring/active_hiring/steady_hiring/low_hiring/no_signal).hiringVelocity:{ recentWindowDays, netOpenRolesChange, hadPreviousRun, previousTotal }— the delta vs the previous run of the samestateKey.priorityRoles[]: top 5 role families (engineering, data, research, product, design, sales, customer_success, support, marketing, operations, finance, legal, people_hr, security, other) with example titles.departmentSignals[],regionSignals[](us / canada / europe_uk / europe_eu / latam / mena / apac / africa / remote_global / remote_other / other / unknown),remoteShare0.0-1.0,seniorityMix(junior / mid / senior / leadership).keywordHits: user-supplied strings mapped to how many open titles contain each (case-insensitive substring).expansionSignals:newDepartments[],newRegions[],newRoleFamilies[]compared to the previous run.recommendedOutreach[]: short English suggestions for BD / sales / talent outreach based on the above signals.reportConfidence0.1-1.0 pluswarnings[].
Example input:
{"boardSlugs": ["airbnb", "stripe", "anthropic"],"mode": "report","keywordHits": ["AI", "ML", "LLM", "security"],"stateKey": "weekly-pipeline"}
Previous-run deltas live in the named key-value store
greenhouse-report-<stateKey> on your account.
Daily deltas (onlyNewSinceLastRun)
Pair the actor with Apify Scheduler to get only newly-posted jobs each run.
The first run of a given (stateKey, boardSlug) records every open jobId
and pushes the first maxJobsPerBoard rows (all tagged isNew: true).
Every later run compares the current open set against the stored one and
emits only rows whose jobId was not seen before.
{"boardSlugs": ["airbnb", "stripe", "anthropic"],"onlyNewSinceLastRun": true,"stateKey": "daily-pipeline","titleKeywords": ["engineer", "data"],"includeContent": true}
The dedup state lives in a named key-value store greenhouse-diff-<stateKey>
on your account; delete it from the Apify Console to force the next run to
treat every job as new. Use different stateKey names when you want
independent memories for different downstream pipelines (e.g. daily-slack
vs weekly-report).
Notes
- The Greenhouse public API returns every open job on a board in a single
response. There is no pagination – use
maxJobsPerBoardonly to trim very large boards (Stripe/Anthropic ~700 jobs). - All timestamps are ISO 8601 with timezone offset exactly as Greenhouse
returns them (
2026-09-29T20:00:54-04:00). - Unknown board slugs return HTTP 404; the actor logs and skips.
- The actor also accepts pasted URLs like
https://boards.greenhouse.io/airbnborhttps://job-boards.greenhouse.io/anthropic– the slug is extracted automatically.