# Greenhouse Salary Scraper - Job Pay Ranges (`datagrit/greenhouse-salary-scraper`) Actor

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

- **URL**: https://apify.com/datagrit/greenhouse-salary-scraper.md
- **Developed by:** [datagrit](https://apify.com/datagrit) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

### What does Greenhouse Salary Scraper do?

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?

- **Recruiters and sourcers** who want a feed of new roles at target companies, filtered by title and location.
- **Job seekers and career coaches** who compare pay bands across companies before negotiating.
- **Compensation and HR analysts** who benchmark pay by role and location zone, including Canadian and European zones of US employers.
- **Sales teams** who use hiring as a buying signal and want to see which companies are opening roles.

### 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.

### Example output

| company | title | location | payMin | payMax | payCurrency | payPeriod | payZones |
|---|---|---|---|---|---|---|---|
| robinhood | AML Investigator | Denver, CO; New York, NY; Westlake, TX | 58000 | 87000 | USD | year | 3 |

```json
{
  "company": "robinhood",
  "id": "8162157",
  "title": "AML Investigator",
  "departments": "Financial Crimes",
  "location": "Denver, CO; New York, NY; Westlake, TX",
  "firstPublishedAt": "2026-09-03T15:43:18.000Z",
  "jobUrl": "https://boards.greenhouse.io/robinhood/jobs/8162157?t=gh_src=&gh_jid=8162157",
  "hasPay": true,
  "payMin": 58000,
  "payMax": 87000,
  "payCurrency": "USD",
  "payCurrencies": "USD",
  "payPeriod": "year",
  "payAnnualMin": 58000,
  "payAnnualMax": 87000,
  "payZones": 3,
  "payRangeSummaries": "Zone 1 (Menlo Park, CA; New York, NY; Bellevue, WA; Washington, DC): 74K-87K USD/year | Zone 2 (Denver, CO; Westlake, TX; Chicago, IL): 65K-77K USD/year | Zone 3 (Lake Mary, FL; Clearwater, FL; Gainesville, FL): 58K-68K USD/year",
  "equityMentioned": true,
  "bonusMentioned": true,
  "commissionMentioned": false,
  "found": true
}
```

### 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.

### How much does it cost?

You pay per posting returned. Pricing depends on your Apify plan: a small fee when a run starts, then a price per result that is lower on paid plans. The Apify free plan includes monthly credit you can use to try it. Status rows (for example when nothing matched) are never charged. You can set a maximum spend on the run and the Actor stops when it is reached. It reads a public JSON API over plain HTTP without a browser, so runs are light on platform resources.

### Input

- **Companies** – Greenhouse board names or URLs. Boards that do not exist are skipped and listed in the run status; if none of them exists the run fails with a clear message.
- **Job title keywords** – keep postings whose title contains any of the words.
- **Departments, Locations** – keep postings whose department or location contains any of the words.
- **Only postings with a published pay range, Minimum annual pay, Pay currencies** – pay filters, applied to each posting.
- **First published within days** – recency filter.
- **Only postings new since my last run** – incremental feed, see the FAQ.
- **Include description text** – adds the plain-text description.
- **Maximum results** – total limit for the run.

### 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.

### FAQ

**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.

### Related Actors

Other public-data Actors from the same publisher are listed on the Store profile.

# Changelog

This Actor's version history is a separate document: https://apify.com/datagrit/greenhouse-salary-scraper/changelog.md

# Actor input Schema

## `companies` (type: `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` (type: `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` (type: `array`):

Optional. Keep only postings whose department contains one of these words, for example Engineering or Sales.

## `locations` (type: `array`):

Optional. Keep only postings whose location contains one of these words, for example London, Germany or Remote.

## `onlyWithPay` (type: `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` (type: `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` (type: `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` (type: `integer`):

Keep only postings first published in the last N days. 0 disables the filter.

## `onlyNewSinceLastRun` (type: `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` (type: `boolean`):

Adds the plain-text job description, cut to 8000 characters. Off by default to keep the dataset small.

## `maxItems` (type: `integer`):

Stop after this many postings in total across all companies.

## `proxyConfiguration` (type: `object`):

Optional proxy. Leave disabled: the Greenhouse job board API is public and needs none.

## Actor input object example

```json
{
  "companies": [
    "robinhood"
  ],
  "keywords": [],
  "departments": [],
  "locations": [],
  "onlyWithPay": false,
  "minAnnualPay": 0,
  "payCurrencies": [],
  "publishedWithinDays": 0,
  "onlyNewSinceLastRun": false,
  "includeDescription": false,
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

All extracted records as a dataset.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "companies": [
        "robinhood"
    ],
    "maxItems": 50,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("datagrit/greenhouse-salary-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "companies": ["robinhood"],
    "maxItems": 50,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("datagrit/greenhouse-salary-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "companies": [
    "robinhood"
  ],
  "maxItems": 50,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call datagrit/greenhouse-salary-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datagrit/greenhouse-salary-scraper"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/0izu9SHGYbaMzKeG5/builds/81AMemu4no1IL17mB/openapi.json
