Salary Data from Job Postings - Greenhouse, Lever, Ashby
Pricing
from $5.00 / 1,000 pay records
Salary Data from Job Postings - Greenhouse, Lever, Ashby
Reads open postings on the public Greenhouse, Ashby and Lever job boards you name, extracts employer-published pay ranges, normalizes them (min/max/currency/interval, role family, seniority) and rolls them up into median, p25 and p75 salary benchmarks.
Pricing
from $5.00 / 1,000 pay records
Rating
0.0
(0)
Developer
Mikkel Bech-Hansen
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
20 days ago
Last modified
Share
Give this actor a list of company job-board slugs and it reads every open posting on their public Greenhouse, Ashby and Lever boards, pulls out the pay range the employer published on the posting itself, and returns one normalized record per job: minimum, maximum, currency, pay interval, role family, seniority, location and whether equity is offered. It also rolls those records up into benchmark rows — median, 25th and 75th percentile — grouped by role family, seniority, location and currency. The numbers are what the employer wrote on its own job ad under pay-transparency law, not crowd-sourced estimates and not a model's guess.
Most job-board scrapers hand you the raw posting and leave you to pull the salary out of the HTML yourself. This one does that part: it reads Greenhouse's structured pay-range block, Ashby's typed compensation object, Lever's salaryRange field, and falls back to parsing the range out of the description text for employers who write it in prose. It needs no login, no API key and no proxy.
What it is good for
- Compensation benchmarking against a named peer set, sourced from postings you can click through and verify.
- Tracking how a competitor's published bands move over time (re-run on a schedule and diff).
- Measuring pay-transparency coverage — what share of a company's open roles actually disclose a range.
- Salary data for a market report, with a citable source for every number.
Input
Every field has a working default. Running it with no configuration at all scans ten well-known boards and returns 300 records.
| Field | Type | Default | Notes |
|---|---|---|---|
greenhouseBoards | array of strings | ["anthropic", "databricks", "datadog", "gitlab", "cloudflare"] | Slug is the last path segment of the board URL: job-boards.greenhouse.io/anthropic → anthropic. A full URL is accepted and reduced to its slug. |
ashbyBoards | array of strings | ["openai", "ramp", "sierra", "harvey", "notion"] | Slug from jobs.ashbyhq.com/openai → openai. |
leverBoards | array of strings | [] | jobs.lever.co/leverdemo → leverdemo. Off by default — see limitations. |
onlyWithPayRange | boolean | true | When false, postings with no published pay are returned too (and are never charged for). |
maxPayRecords | integer | 300 | Hard cap on records, and therefore on cost. 1–200000. |
maxPostingsPerBoard | integer | 0 (no limit) | Stops one very large board from consuming the whole record budget. |
roleFamilies | array (select) | [] (all) | engineering, data-and-ml, product, design, sales, marketing, customer-success, operations, finance, legal, people-and-hr, security, research, other. |
seniorities | array (select) | [] (all) | intern, entry, mid, senior, staff, principal, manager, director, executive, unspecified. |
locationContains | string | "" | Case-insensitive substring, matched against both the raw and the normalized location. |
currencies | array of strings | [] (all) | ISO codes, e.g. ["USD", "GBP"]. |
minAnnualPay | integer | 0 (off) | Drops postings whose annualized maximum is below this, in the posting's own currency. |
includeBenchmarks | boolean | true | Writes roll-up rows to the named benchmarks dataset and the BENCHMARKS key-value record. |
benchmarkMinSampleSize | integer | 3 | Minimum postings before a benchmark row is emitted. 1–1000. |
maxConcurrency | integer | 5 | 1–20. Each request returns an entire board, so 5 is already fast. |
proxyConfiguration | object | no proxy | Not needed. These endpoints have no anti-bot protection. |
If you clear all three board fields you get an error telling you so, rather than an empty run.
Output
Default dataset — one record per posting. A real record from a test run:
{"source": "greenhouse","companySlug": "databricks","companyName": "Databricks","jobId": "8638847002","title": "AI Engineer — GTM Analytics","jobUrl": "https://databricks.com/company/careers/open-positions/job?gh_jid=8638847002","department": "Sales Operations","team": null,"employmentType": null,"location": "United States","locationBucket": "United States","isRemote": false,"postedAt": "2026-07-30T17:59:18.000Z","updatedAt": "2026-08-18T18:38:59.000Z","roleFamily": "data-and-ml","seniority": "unspecified","hasEquity": true,"hasBonus": true,"hasCommission": true,"scrapedAt": "2026-08-24T23:31:42.913Z","hasPayRange": true,"payMin": 146600,"payMax": 201650,"payMid": 174125,"currency": "USD","payInterval": "year","annualMin": 146600,"annualMax": 201650,"annualMid": 174125,"payRangeLabel": "Zone 1 Pay Range","paySource": "pay-range-block","payRangeCount": 4,"additionalRanges": [{ "label": "Zone 2 Pay Range", "payMin": 132000, "payMax": 181500, "currency": "USD", "payInterval": "year" },{ "label": "Zone 3 Pay Range", "payMin": 124600, "payMax": 171400, "currency": "USD", "payInterval": "year" },{ "label": "Zone 4 Pay Range", "payMin": 117300, "payMax": 161250, "currency": "USD", "payInterval": "year" }]}
payMin/payMax are exactly as published. annualMin/annualMax are the same figures annualized (hourly × 2080, monthly × 12) so that rows with different intervals are comparable. paySource tells you how the number was obtained: structured (a typed API field — Ashby, Lever), pay-range-block (Greenhouse's machine-generated block), or prose-text (parsed out of the description). When a posting lists several ranges — geographic zones, or a range per city — the first is the primary and the rest are in additionalRanges.
Named dataset benchmarks, and key-value record BENCHMARKS — the roll-ups:
{"recordType": "benchmark","roleFamily": "sales","seniority": "unspecified","locationBucket": "New York, NY","currency": "USD","sampleSize": 18,"companyCount": 3,"companies": ["anthropic", "databricks", "ramp"],"p25": 134363,"median": 192250,"p75": 228375,"minP25": 114750,"minMedian": 162000,"maxMedian": 223000,"maxP75": 261250,"lowestMin": 68000,"highestMax": 374000,"equityShare": 1,"generatedAt": "2026-08-24T23:31:42.913Z"}
p25/median/p75 are percentiles of the posting midpoints. minMedian and maxMedian are the median band floor and median band ceiling, which is what you want if you are setting your own band rather than a single number.
Key-value record COVERAGE — per board: postings found, postings carrying a range, and coverage percentage. Read this before trusting a benchmark; a company at 35% coverage is telling you far less than one at 96%.
Key-value record SUMMARY — counts for the whole run.
Cost
Billing is per delivered record, not per posting read. Postings with no published pay range are never charged for, even when you ask to receive them.
| Records returned | Approximate cost |
|---|---|
| 100 | $0.50 |
| 300 (the default run) | ~$1.80 |
| 1,000 | ~$5.50 |
| 5,000 | ~$28 |
Benchmark rows are $0.01 each; a 300-record run produced 30 of them in testing. Set maxPayRecords to cap spend exactly — the run stops the moment it is reached. ACTOR_MAX_TOTAL_CHARGE_USD is respected too: the actor stops cleanly at your limit instead of continuing to work unpaid.
Runs are fast and cheap in platform time — 5 boards and 1,998 postings took 3.5 seconds; 30 boards and 7,504 postings took 9.1 seconds.
Known limitations
Read this section. Most of it is about coverage, and coverage is the thing that will surprise you.
- Coverage is uneven and outside this actor's control. Employers publish ranges mainly where pay-transparency law requires it — California, Colorado, New York, Washington, Illinois, and increasingly the EU. In testing: Ramp 96%, Sierra 91%, Anthropic 88%, OpenAI 85%, Databricks 54%, Datadog 49%, GitLab 38%, Cloudflare 35%, Stripe 3%. A company whose roles are mostly outside those jurisdictions will return very little. Check
COVERAGEbefore concluding the actor failed — an empty result for a given company usually means that company does not disclose. - Greenhouse pay ranges live in the posting HTML, not a typed field. This actor keys off Greenhouse's
pay-rangeblock and falls back to a currency-pair regex over the description. If Greenhouse changes that markup the structured path breaks until the actor is updated; the Ashby and Lever paths are unaffected because they read typed API fields. - The prose fallback is good but not perfect. It requires a pay word ("salary", "compensation", "hourly rate", "OTE") near the figures, which keeps out things like "we process $200B in spend", but an unusually worded posting can still be missed. Missed is the failure mode, not wrong — implausible values (bands wider than 20×, hourly rates over $2,000) are rejected rather than returned.
- No currency conversion, ever. Currency is part of the benchmark grouping key. A "sales, New York, USD" row and a "sales, London, GBP" row are separate rows. There is no FX source this actor could honestly cite, so it does not invent one.
- Annualizing is a convention, not a fact. Hourly is × 2080 and monthly is × 12. For a three-month internship posted at a monthly rate, the annualized figure is arithmetically correct and practically misleading.
payMin,payMaxandpayIntervalalways carry the figure as published — use those when the distinction matters. - Role family and seniority are derived from the job title, using a keyword map. It handles the usual cases, including the distinction between "Product Manager" (an individual contributor) and "Engineering Manager" (people management), but a company with idiosyncratic titles will land in
otherandunspecified.unspecifiedseniority is the largest bucket in most runs and generally means mid-level. - Ranges are what the employer advertises, not what it pays. Published bands are typically wide, sometimes cover several levels, and often represent base salary only —
hasEquity,hasBonusandhasCommissionflag when the posting mentions other components, but this actor does not attempt to value them. Sales roles frequently publish OTE rather than base. - Lever is a weak third source. Many companies that once used Lever now 404, and Lever postings carry pay ranges far less often — a test board returned 384 postings with only 4 ranges. It is off by default for that reason.
- You supply the board slugs. There is no company-name search. The defaults and the extra verified slugs below are a starting point; for a specific company, open its careers page and take the slug from the board URL.
- This is a snapshot of currently open postings. There is no history. To build a time series, schedule the actor and keep the datasets.
- A board that 404s is skipped with a warning, not treated as a failure. If every board fails, the run exits non-zero rather than reporting success with an empty dataset.
Extra verified board slugs
Confirmed to return postings at build time. Boards do get renamed or closed.
Greenhouse: stripe, airbnb, coinbase, robinhood, instacart, affirm, figma, discord, reddit, twilio, asana, brex, duolingo, webflow, lyft, pinterest, samsara, flexport, chime, carta, scaleai, dropbox
Ashby: linear, replit, clickhouse, cursor, supabase, posthog, hex, neon, whoop, abridge
Legal
This actor is not affiliated with, endorsed by, or connected to Greenhouse, Ashby, Lever, or any of the employers whose job boards it reads.
It reads public, unauthenticated JSON endpoints that these applicant-tracking systems expose to render company job boards. No login, credential or API key is used, and nothing is bypassed to reach them. Even so: a platform's Terms of Service commonly restrict automated access and bulk collection regardless of whether the data is publicly reachable. Greenhouse, Ashby and Lever each publish their own terms, and using this actor may not comply with them. "Publicly accessible" and "permitted by the terms" are different questions, and only the second one protects you.
You are responsible for your own compliance — with those terms, with the terms of the employers whose boards you scan, and with any law that applies to how you collect and use the results. That includes any onward use: republishing the data, incorporating it into a commercial product, or making employment and pay decisions from it. If your use is commercial or high-stakes, take your own legal advice; nothing here is legal advice.
The actor returns company-level job postings only. It does not collect recruiter names, applicant data, or any other personal data, and it should not be modified to do so. Pay figures are reproduced as the employer published them; verify against the linked posting before relying on any individual number.