Greenhouse Jobs Scraper — Salary Ranges by Company
Pricing
from $30.00 / 1,000 results
Greenhouse Jobs Scraper — Salary Ranges by Company
Every open role from any company's Greenhouse job board, with the disclosed salary range parsed into payMin / payMax / currency / period. Pass company names, board tokens or board URLs. Departments, offices, remote flag, posting age. Re-run on a schedule. Company-level data only.
Pricing
from $30.00 / 1,000 results
Rating
0.0
(0)
Developer
mochi
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
6 days ago
Last modified
Categories
Share
Greenhouse Jobs Scraper — open roles + disclosed salary ranges
Pull every open role from any company's Greenhouse job board, with the disclosed salary range parsed into numbers — payMin, payMax, payCurrency, payPeriod — instead of buried in a paragraph of HTML.
Give it company names, Greenhouse board tokens, or board URLs. Re-run it on a schedule to watch a market's hiring and pay move.
Input: ["Scale AI", "gitlab", "https://job-boards.greenhouse.io/figma"]Output: one row per open posting, with the pay band already parsed
What you get per posting
| Field | Example | Notes |
|---|---|---|
company · boardToken | Scale AI · scaleai | Board name as Greenhouse reports it |
requestedAs · matchConfidence | Scale AI · exact | How your input mapped to the board — see Company matching |
title · jobUrl · jobId · requisitionId | DevOps Engineer, Infrastructure & Security | requisitionId is the employer's own req number where they publish it |
location · offices[] · isRemote | San Francisco, CA • New York, NY · ["US"] · false | isRemote is derived from the location text |
departments[] · department | ["Sales"] · Sales | department is the first of the list |
payMin · payMax | 198400 · 311000 | Numbers, not strings |
payCurrency · payCurrencySymbol | USD · $ | ISO-4217 |
payPeriod · payPeriodInferred | year | hour | month · false | See How the period is decided |
payIsOte | true | The posting quoted On-Target Earnings, not base |
paySource | structured | prose | Where the range was found |
payRangeRaw | $198,400 — $311,000 USD | The exact source text, so every number is auditable |
payRangeCount | 3 | More than one band quoted — see Multi-band postings |
firstPublished · updatedAt | ISO timestamps | |
daysSinceFirstPublished · daysSinceUpdated | 61 · 6 | The hiring-signal fields: how long a req has been open |
descriptionText | full posting text | Only when includeDescription is on |
Measured pay-range coverage — read this before you buy
Most postings on most boards do not disclose pay. Measured across 3,064 live postings on 10 boards:
| Board | Postings with a parsed range |
|---|---|
| Duolingo | 67 / 67 — 100% |
| Anthropic | 351 / 400 — 88% |
| Robinhood | 113 / 128 — 88% |
| Discord | 43 / 45 — 96% |
| Figma | 109 / 176 — 62% |
| Databricks | 426 / 803 — 53% |
| Datadog | 204 / 429 — 48% |
| GitLab | 72 / 184 — 39% |
| Cloudflare | 104 / 284 — 37% |
| Stripe | 19 / 548 — 3% |
| Total | 1,508 / 3,064 — 49% |
That spread is the employer's choice, not a parser limitation — US pay-transparency law only binds certain roles and locations, so a company can post hundreds of roles and disclose on a handful. Set onlyWithPayRange to keep just the rows that carry a number.
Currencies seen in that sample: USD 1,421 · EUR 34 · GBP 27 · CAD 21 · CNY 3 · JPY 2.
Who this is for
- Compensation benchmarking — pull a peer set every month and see where your bands sit.
payMin/payMax/payPeriodare already normalised, so it drops straight into a sheet. - Recruiting and talent intel — what a competitor is paying, in which offices, for which levels.
- Sales / GTM signal —
daysSinceFirstPublishedand department mix tell you who is expanding which function. A req that has been open 200 days is a different signal from one opened last week. - Market research — remote mix, office footprint, department growth over time.
The data changes constantly: postings open, close and get re-published. This is built to be re-run on a schedule — set postedWithinDays to pick up only what is new since your last run.
Input
| Input | Default | What it does |
|---|---|---|
companies | 6 sample boards | Company names, board tokens, or board URLs |
maxResults | 20 | Total cap, shared round-robin across the companies you list so the last company is never starved |
onlyWithPayRange | false | Keep only postings that disclose pay |
titleKeywords | [] | Case-insensitive substring match on the title |
locationKeywords | [] | e.g. Remote, London |
departments | [] | e.g. Engineering, Sales |
postedWithinDays | 0 | Only roles first published in the last N days. 0 = no filter |
includeDescription | false | Adds descriptionText. Off by default — it multiplies dataset size ~20x |
allowUnverifiedMatches | false | See below |
Company matching, and why it can refuse
You can pass a plain company name. It is normalised to a candidate board token and looked up — but the board's own company name is then checked against what you asked for.
This exists because of a real trap. The company "Remote" maps to the board token remote, which belongs to "General Assembly Remote Jobs". Returning another company's open roles as if they were yours would quietly corrupt a benchmark, so by default that input is skipped and reported:
! "Remote" -> board "remote" exists but is named "General Assembly Remote Jobs" — not confident it is "Remote"
Every row carries matchConfidence: token (you gave a token or URL), exact, or fuzzy. Set allowUnverifiedMatches to accept doubtful matches anyway.
If a company comes back unresolved, the usual reason is that it is not on Greenhouse at all — plenty of companies use a different ATS. Passing the board URL from their careers page always works.
How the period is decided
payPeriod is read from the label the employer put next to the number — "Annual Base Salary Range", "Hourly Base Pay Range", "Gross Monthly Pay Range". Across the 1,508 ranges in the sample, every one was resolved from the text; payPeriodInferred was false on all of them. If a posting ever gives a number with no period wording at all, the field falls back to magnitude and payPeriodInferred is set to true so you can filter those rows out.
Multi-band postings
Some employers publish a separate band per pay zone — Robinhood quotes three on many roles. payMin/payMax are the first band and payRangeCount tells you how many there were, so you can spot those rows rather than silently treating a three-zone posting as a single band. Turn on includeDescription to read the rest.
What the parser refuses to do
It will not invent a salary. Each of these appears in real postings and each produced a bogus number until it was fixed:
500-5000 FTE— a customer headcount band, not 500–5000 in a currency called "FTE"1-7, BGP— a routing protocol in a networking role3 - 5 years of experience$231,000-$275,000 OTE— a real range, but "OTE" is not a currency code
A range is only accepted if it carries a real currency symbol or a valid ISO-4217 code, and the parser scans the whole posting rather than stopping at the first number-shaped thing it sees.
Source, limits and compliance
- Source: Greenhouse's documented public Job Board API —
https://boards-api.greenhouse.io/v1/boards/<token>/jobs. No key, no login, no browser.boards-api.greenhouse.io/robots.txtrestricts only/embed/. - Greenhouse boards only. Lever and Ashby are deliberately not supported; each of them restricts automated access in a way we will not work around. If the company you want is not on Greenhouse, this actor will tell you so rather than return nothing silently.
- Only open, published roles. There is no history: a role that closed yesterday is gone. To build a time series, run on a schedule and keep your datasets.
- Organisation-level data only. Company, role, office, department, pay band. No applicant or employee identity is read or emitted — the endpoint exposes none.
- Every run prints a full tally: written · with a pay range · postings seen · filtered out · unresolved companies · board fetch failures. A short run always has a stated reason.