datagrit

datagrit › Jobs & salaries › Greenhouse Salary Scraper - Job Pay Ranges

Jobs & salaries

Greenhouse Salary Scraper - Job Pay Ranges

Greenhouse job postings with published pay ranges by zone, annualised pay, filters and new-since-last-run detection.

Run it on Apify StoreUse the APIfrom $2.10 per 1,000 results + $10 per run · no code needed
from $2.10 per 1,000 results + $10 per runpay only for job postings you get
JSON · CSV · Excelexport or call via API
Scheduled runsdaily or weekly feeds with Apify schedules
v0.5updated 2026-09-30

Greenhouse Salary Scraper reads the public job boards of companies that hire through Greenhouse and returns every open posting as a clean record together with its published pay ranges. You get the range for every location pay zone, the overall band, the currency, the pay period and yearly amounts, plus flags for equity, bonus and commission mentioned in the pay text. Filter by minimum annual pay, currency, title keywords, department, location and posting age, and export the data as JSON, CSV or Excel, call it through the Apify API, or plug it into n8n, Make and AI agents through MCP.

Who is it for?

How to use it

  1. Enter the companies you want as Greenhouse board names or URLs. The name is the last part of the careers link: boards.greenhouse.io/robinhood means the name is robinhood.
  2. Add filters if you need them: title keywords, department, location, only postings with a pay range, minimum annual pay, pay currency, or how recently the posting was first published.
  3. For a recurring feed, switch on Only postings new since my last run and schedule the Actor. Each run returns only the postings that earlier runs with the same companies and filters have not delivered yet.
  4. Download the dataset or fetch it from the API.

The Actor reads each board in a single request that already contains the pay ranges, so pay does not slow the run down. On 30 September 2026 a run over five boards (Robinhood, Airbnb, Coinbase, Mercury and Discord) returned all 640 open postings in 10 seconds, 543 of them with a published pay range.

What data do you get?

Each record contains the title, departments, offices and location, the requisition number, first publication and update dates, the age of the posting in days and its link. When pay is published you also get the lowest and highest amounts across the pay zones, the currency of that band and the list of all currencies used, the pay period, the yearly equivalents, the number of pay zones, a one-line summary of every zone and flags for equity, bonus and commission mentioned in the pay text. The plain-text description is available on request.

Pay zones in several currencies

Many US employers publish a Canadian or European zone next to the US ones. Every zone is kept in the summary and counted in payZones. The band (payMin, payMax and the yearly amounts) is computed in one currency, because amounts are never converted between currencies: the first currency from your Pay currencies filter that the posting uses, or the currency of the first zone when you do not filter by currency. On 30 September 2026, 38 of Mercury's 63 postings had a zone in CAD after the USD zones; filtering by CAD returns all 38 with the band in CAD.

The same applies to Minimum annual pay: without a currency filter the threshold is compared with each posting in its own currency, so on a global board 150000 also keeps postings paying 150,000 INR or PHP a year. Set Pay currencies together with Minimum annual pay, for example USD with 150000, to compare like with like.

Pay period

Greenhouse does not publish a pay period field, so the period is worked out from the title of the range, for example Hourly Rate, and from the size of the amount. When it cannot be determined the period and the yearly amounts are left empty instead of guessed.

Is it legal to scrape this data?

The Actor reads only the public job board API that Greenhouse offers to employers for publishing their open roles. It does not log in, bypass access controls or collect candidate data. Job descriptions can contain personal data such as a recruiter name, so if you request them you are responsible for handling them in line with applicable data protection law. This description is not legal advice.

Output fields

Every result is one flat record, so it drops straight into a spreadsheet, a database or a CRM.

FieldTypeDescriptionExample
companystringGreenhouse board name the posting belongs to. On the single status row (found = false) it lists the boards that were read, comma-separated.robinhood
idstringGreenhouse identifier of the posting.7563040
titlestringJob title.Senior Software Engineer
departmentsstringDepartment names joined with semicolons.Financial Crimes
officesstringOffice names joined with semicolons.Denver, CO; New York, NY
locationstringLocation text of the posting.Denver, CO; New York, NY; Westlake, TX
requisitionIdstringEmployer requisition number when published.JR109304
firstPublishedAtstringISO 8601 time the posting was first published.2026-09-03T15:43:18.000Z
updatedAtstringISO 8601 time of the last update.2026-09-25T17:03:21.000Z
daysSincePublishedintegerWhole days between first publication and this run.27
jobUrlstringPublic page of the posting.https://boards.greenhouse.io/robinhood/jobs/7563040
hasPaybooleanTrue when the posting publishes at least one pay range.true
payMinnumberLowest amount across the pay zones in payCurrency and payPeriod.148000
payMaxnumberHighest amount across the pay zones in payCurrency and payPeriod.198000
payCurrencystringCurrency of the band: the first of your Pay currencies found in the posting, otherwise the currency of the first pay zone. payMin, payMax and the annual amounts are in this currency.USD
payCurrenciesstringAll currencies used by the pay zones, for example "USD, CAD".USD, CAD
payPeriodstringyear, month, week or hour. Greenhouse publishes no period field, so it is inferred from the range title and size; null when it cannot be determined.year
payAnnualMinintegerLowest yearly amount across the pay zones in payCurrency; null when the period is unknown.148000
payAnnualMaxintegerHighest yearly amount across the pay zones in payCurrency; null when the period is unknown.198000
payZonesintegerNumber of published pay ranges, usually one per location zone, in all currencies.3
payRangeSummariesstringEach pay zone with its title, amounts, currency and period, joined with a pipe; at most 12, then "+N more".Zone 1: 148K-198K USD/year | Canada: 170K-220K CAD/year
equityMentionedbooleanTrue when the pay text mentions equity, stock, RSUs or options.true
bonusMentionedbooleanTrue when the pay text mentions a bonus.true
commissionMentionedbooleanTrue when the pay text mentions commission.false
descriptionTextstringPlain-text description, only when requested.
foundbooleanFalse only for the single status row emitted when nothing matched.true
scrapedAtstringISO 8601 timestamp of extraction.2026-09-30T08:00:00.000Z

Sample record

{
  "company": "robinhood",
  "id": "7563040",
  "title": "Senior Software Engineer",
  "departments": "Financial Crimes",
  "offices": "Denver, CO; New York, NY",
  "location": "Denver, CO; New York, NY; Westlake, TX",
  "requisitionId": "JR109304",
  "firstPublishedAt": "2026-09-03T15:43:18.000Z",
  "updatedAt": "2026-09-25T17:03:21.000Z",
  "daysSincePublished": 27,
  "jobUrl": "https://boards.greenhouse.io/robinhood/jobs/7563040",
  "hasPay": true,
  "payMin": 148000,
  "payMax": 198000,
  "payCurrency": "USD",
  "payCurrencies": "USD, CAD",
  "payPeriod": "year",
  "payAnnualMin": 148000,
  "payAnnualMax": 198000,
  "payZones": 3,
  "payRangeSummaries": "Zone 1: 148K-198K USD/year | Canada: 170K-220K CAD/year",
  "equityMentioned": true,
  "bonusMentioned": true,
  "commissionMentioned": false,
  "descriptionText": null,
  "found": true,
  "scrapedAt": "2026-09-30T08:00:00.000Z"
}

Input

FieldNameTypeWhat it does
companies requiredCompanies (Greenhouse boards)arrayGreenhouse job board names or URLs, one per company. The name is the last part of the careers link: boards.greenhouse.io/robinhood means the name is robinhood. Boards that do not exist are skipped and listed in the run status; if none of them exists the run fails, so a typo never looks like an empty result.
keywordsJob title keywordsarrayOptional. Keep a posting when its title contains any of these words (case-insensitive), for example engineer or designer. Leave empty for all roles.
departmentsDepartmentsarrayOptional. Keep only postings whose department contains one of these words, for example Engineering or Sales.
locationsLocationsarrayOptional. Keep only postings whose location contains one of these words, for example London, Germany or Remote.
onlyWithPayOnly postings with a published pay rangebooleanSkip postings that do not publish a pay range. Pay ranges come with the job list itself, so this filter does not slow the run down.
minAnnualPayMinimum annual payintegerKeep only postings whose top pay, converted to a yearly amount, reaches this value. The comparison uses the currency of the band (see Pay currencies); amounts are never converted between currencies, so without Pay currencies a posting paid in INR or PHP is compared in INR or PHP. Set Pay currencies together with Minimum annual pay. 0 disables the filter. Postings whose pay period cannot be determined are skipped by this filter.
payCurrenciesPay currenciesarrayOptional. Keep only postings with at least one pay zone in these currencies (three-letter codes such as USD, CAD, GBP or EUR). A US posting with an extra Canadian zone matches CAD, and its band, yearly amounts and the minimum annual pay filter are then computed in CAD. Without this filter the band uses the currency of the first pay zone.
publishedWithinDaysFirst published within daysintegerKeep only postings first published in the last N days. 0 disables the filter.
onlyNewSinceLastRunOnly postings new since my last runbooleanReturn only postings that earlier runs with the same companies and the same filters have not delivered to you. The Actor remembers the postings it actually returned, per company and per filter combination, in a storage on your account; postings dropped by your filters or cut off by Maximum results are not remembered and can still come later. Changing a filter starts a separate feed. The first run returns everything that matches.
includeDescriptionInclude description textbooleanAdds the plain-text job description, cut to 8000 characters. Off by default to keep the dataset small.
maxItemsMaximum resultsintegerStop after this many postings in total across all companies.
proxyConfigurationProxy configurationobjectOptional proxy. Leave disabled: the Greenhouse job board API is public and needs none.

Call it from your code

Run the Actor and get the results in one request. Replace YOUR_APIFY_TOKEN with the token from your Apify account settings.

curl -X POST "https://api.apify.com/v2/acts/datagrit~greenhouse-salary-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"companies":["robinhood"],"onlyWithPay":true,"maxItems":50}'
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('datagrit/greenhouse-salary-scraper').call({
  "companies": [
    "robinhood"
  ],
  "onlyWithPay": true,
  "maxItems": 50
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.length, items[0]);

Install with npm i apify-client.

from apify_client import ApifyClient
import os

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("datagrit/greenhouse-salary-scraper").call(run_input={
  "companies": [
    "robinhood"
  ],
  "onlyWithPay": True,
  "maxItems": 50
})
items = client.dataset(run["defaultDatasetId"]).list_items().items
print(len(items), items[0] if items else None)

Install with pip install apify-client.

Frequently asked questions

Which companies are covered?

Any company that publishes its roles through a public Greenhouse job board. The run status lists boards that were not found, entries that are not board names, and boards that exist but have no open postings, so you can tell a typo from an empty board.

How long does a run take?

One request per company. Measured on 30 September 2026: five boards with 640 postings in 10 seconds, and Stripe's 712 postings in 8 seconds. Filters do not add requests.

How often should I schedule it?

Daily is a good default for a hiring feed. With the only-new mode on, each scheduled run returns just the additions, and a run that finds nothing new returns one free status row.

How does the only-new mode work?

The Actor remembers, in a storage on your account, the postings it actually returned to you, separately for each company and each combination of filters. Postings dropped by your filters or cut off by Maximum results are not remembered and can still come in a later run. Changing a filter starts a separate feed, and two scheduled tasks with different filters do not take postings from each other. Your first run returns everything that matches.

Why do some postings have no pay range?

Employers decide whether to publish pay, and some regions require it. Use the pay filter to keep only postings that show it.

What happens when Greenhouse changes its API?

If the board list stops carrying pay data or changes shape, the run fails with a message instead of returning postings that look like they have no pay.

Something looks wrong.

Open an issue with the input you used; changes at the source are fixed quickly.

Try Greenhouse Salary Scraper - Job Pay Ranges on Apify

Related Actors

Jobs & salaries

Career Site Jobs Aggregator - ATS Salary Data

Open jobs from Greenhouse, Lever, Ashby, Workday, Workable, SmartRecruiters, Recruitee and Personio in one schema with normalized salaries.

from $2.80 / 1,000 results
Company data

French Company Finder - Sirene Financials

French company lead lists from Sirene screened by net result and revenue, with net margin, size, matching establishment and optional directors.

from $5.60 / 1,000 results