Ashby Salary Scraper - Startup Job Pay Ranges
Ashby job postings with normalized annual salary ranges, equity flags and new-since-last-run detection.
datagrit › Jobs & salaries › Greenhouse Salary Scraper - Job Pay Ranges
Jobs & salariesGreenhouse job postings with published pay ranges by zone, annualised pay, filters and new-since-last-run detection.
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.
boards.greenhouse.io/robinhood means the name is robinhood.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.
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.
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.
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.
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.
Every result is one flat record, so it drops straight into a spreadsheet, a database or a CRM.
| Field | Type | Description | Example |
|---|---|---|---|
company | string | Greenhouse board name the posting belongs to. On the single status row (found = false) it lists the boards that were read, comma-separated. | robinhood |
id | string | Greenhouse identifier of the posting. | 7563040 |
title | string | Job title. | Senior Software Engineer |
departments | string | Department names joined with semicolons. | Financial Crimes |
offices | string | Office names joined with semicolons. | Denver, CO; New York, NY |
location | string | Location text of the posting. | Denver, CO; New York, NY; Westlake, TX |
requisitionId | string | Employer requisition number when published. | JR109304 |
firstPublishedAt | string | ISO 8601 time the posting was first published. | 2026-09-03T15:43:18.000Z |
updatedAt | string | ISO 8601 time of the last update. | 2026-09-25T17:03:21.000Z |
daysSincePublished | integer | Whole days between first publication and this run. | 27 |
jobUrl | string | Public page of the posting. | https://boards.greenhouse.io/robinhood/jobs/7563040 |
hasPay | boolean | True when the posting publishes at least one pay range. | true |
payMin | number | Lowest amount across the pay zones in payCurrency and payPeriod. | 148000 |
payMax | number | Highest amount across the pay zones in payCurrency and payPeriod. | 198000 |
payCurrency | string | Currency 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 |
payCurrencies | string | All currencies used by the pay zones, for example "USD, CAD". | USD, CAD |
payPeriod | string | year, 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 |
payAnnualMin | integer | Lowest yearly amount across the pay zones in payCurrency; null when the period is unknown. | 148000 |
payAnnualMax | integer | Highest yearly amount across the pay zones in payCurrency; null when the period is unknown. | 198000 |
payZones | integer | Number of published pay ranges, usually one per location zone, in all currencies. | 3 |
payRangeSummaries | string | Each 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 |
equityMentioned | boolean | True when the pay text mentions equity, stock, RSUs or options. | true |
bonusMentioned | boolean | True when the pay text mentions a bonus. | true |
commissionMentioned | boolean | True when the pay text mentions commission. | false |
descriptionText | string | Plain-text description, only when requested. | |
found | boolean | False only for the single status row emitted when nothing matched. | true |
scrapedAt | string | ISO 8601 timestamp of extraction. | 2026-09-30T08:00:00.000Z |
{
"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"
}
| Field | Name | Type | What it does |
|---|---|---|---|
companies required | Companies (Greenhouse boards) | array | Greenhouse 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. |
keywords | Job title keywords | array | Optional. Keep a posting when its title contains any of these words (case-insensitive), for example engineer or designer. Leave empty for all roles. |
departments | Departments | array | Optional. Keep only postings whose department contains one of these words, for example Engineering or Sales. |
locations | Locations | array | Optional. Keep only postings whose location contains one of these words, for example London, Germany or Remote. |
onlyWithPay | Only postings with a published pay range | boolean | Skip 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. |
minAnnualPay | Minimum annual pay | integer | Keep 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. |
payCurrencies | Pay currencies | array | Optional. 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. |
publishedWithinDays | First published within days | integer | Keep only postings first published in the last N days. 0 disables the filter. |
onlyNewSinceLastRun | Only postings new since my last run | boolean | Return 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. |
includeDescription | Include description text | boolean | Adds the plain-text job description, cut to 8000 characters. Off by default to keep the dataset small. |
maxItems | Maximum results | integer | Stop after this many postings in total across all companies. |
proxyConfiguration | Proxy configuration | object | Optional proxy. Leave disabled: the Greenhouse job board API is public and needs none. |
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.
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.
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.
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.
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.
Employers decide whether to publish pay, and some regions require it. Use the pay filter to keep only postings that show it.
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.
Open an issue with the input you used; changes at the source are fixed quickly.
Ashby job postings with normalized annual salary ranges, equity flags and new-since-last-run detection.
Open jobs from Greenhouse, Lever, Ashby, Workday, Workable, SmartRecruiters, Recruitee and Personio in one schema with normalized salaries.
Find EU public contracts approaching expiry from TED award notices: incumbent, buyer, value, end date and renewal options.
French company lead lists from Sirene screened by net result and revenue, with net margin, size, matching establishment and optional directors.