# Greenhouse Jobs Scraper & API — Salaries + New-Job Alerts (`wowlab/greenhouse-jobs-scraper`) Actor

Get every open job from any Greenhouse board: title, location, department, description and salary ranges. Paste board tokens, URLs or company names/websites (auto-detected) or pick a built-in company list. Filter by keyword, location, remote or date; track new, updated and removed jobs. No login.

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

## Pricing

from $1.50 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## Greenhouse Jobs Scraper & API — Salaries + New-Job Alerts

**Greenhouse Jobs Scraper** is a Greenhouse jobs scraper and Greenhouse job board API: it extracts every open job from any careers page hosted on Greenhouse (`boards.greenhouse.io` and `job-boards.greenhouse.io`), such as Airbnb, Stripe, Discord, Figma or Coinbase. It reads Greenhouse's official public Job Board API - **no login, no proxy, no browser**. Paste Greenhouse board tokens, careers URLs, or just **company names and websites** (auto-detected), filter by keyword, location, remote and date, and get **salary ranges** plus **new / updated / removed job alerts** for scheduled monitoring.

Each job includes title, location, remote flag, departments, offices, publish and update dates, the full description (text or HTML) and **structured salary ranges** (min, max, currency, period) from Greenhouse pay-transparency data. When a company publishes no structured pay, the salary is parsed from the job text (e.g. *"The annual base salary range for this role is $196,000 to $245,000"*, *"£60k–£75k per annum"*).

### What can you use it for?

- **Job boards and aggregators**: feed fresh Greenhouse jobs into your site or newsletter.
- **Recruiting and sales intelligence (lead generation)**: see which companies are hiring, for which teams and where.
- **Salary benchmarking**: collect published pay ranges by role, location and company.
- **Hiring alerts**: schedule a daily run with change detection and get only new, changed and removed jobs - in Slack, email or any webhook.
- **AI agents and LLM pipelines**: call it through the Apify MCP server and get clean JSON with plain-text descriptions.

### Features

- **Flexible input**: Greenhouse board tokens (`airbnb`), board and job URLs, or company names / websites with **automatic board detection** (careers-page links first, then name guesses). If a company is not on Greenhouse but is on another supported ATS, `RUN_SUMMARY` tells you where.
- **Built-in company lists** (`companyLists`): curated lists such as AI companies, fintech or developer tools, each entry verified to have open jobs on 2026-10-30. Run with zero typing.
- **Many companies in one run**, with **deduplication**: the same board entered twice (token + URL) is scraped once, and duplicate job IDs are dropped. Optional `dedupeSimilar` also drops identical reposts.
- **Filters**: keywords (title or description), locations, departments, remote only, posted within the last N days.
- **Salary ranges** with currency and pay period; text parsing as fallback.
- **Change detection**: `changeType` new / updated / removed, `changedFields`, `firstSeenAt`, stored in a named key-value store between runs. Or the simpler `onlyNewJobs` mode.
- **Robust**: one wrong or deleted board does not fail the run; each board's status is in `RUN_SUMMARY`. Respects your maximum cost per run.

### Quick start

Paste any of these into the input (JSON tab):

```json
{
  "companies": [
    "airbnb",
    "https://job-boards.greenhouse.io/stripe"
  ],
  "remoteOnly": true,
  "maxJobs": 50
}
```

Company names and websites work too:

```json
{
  "companies": [
    "stripe.com",
    "Discord"
  ],
  "keywords": [
    "engineer"
  ],
  "locations": [
    "United States",
    "Remote"
  ],
  "postedWithinDays": 14
}
```

A curated list, no typing:

```json
{
  "companyLists": [
    "fintech"
  ],
  "keywords": [
    "engineer"
  ],
  "maxJobs": 100
}
```

### Input

| Field | Description |
|---|---|
| `companies` | Greenhouse board tokens, board/job URLs, or company names/websites. Optional if `companyLists` is set. |
| `companyLists` | Built-in curated company lists (only entries on Greenhouse are used). |
| `autoDetect` | Find the Greenhouse board for names/websites (default on). |
| `keywords` / `searchInDescription` | Keep jobs whose title (or description) contains any keyword. |
| `locations` / `departments` | Keep jobs whose location / department contains any of these strings. |
| `remoteOnly` / `postedWithinDays` | Remote only; published in the last N days (0 = all). |
| `includeDescription` / `includeDescriptionHtml` | Description as plain text and/or HTML. |
| `includeSalary` | Salary fields. |
| `changeDetection`, `changeStoreName`, `includeUnchanged`, `includeRemoved`, `skipFirstRunOutput` | New / updated / removed tracking between runs. |
| `onlyNewJobs` / `monitoringStoreName` | Simple mode: only job IDs not seen before. |
| `dedupeSimilar` | Drop identical reposts (same title, location and description). |
| `maxJobsPerCompany` / `maxJobs` | Result limits (0 = no limit). |

### Output

Each dataset item is one job:

```json
{
  "jobId": "8334926",
  "title": "Backend Engineer, Profile & Community",
  "companyName": "Airbnb",
  "boardToken": "airbnb",
  "url": "https://careers.airbnb.com/positions/8334926?gh_jid=8334926",
  "location": "United States",
  "isRemote": true,
  "workplaceType": "Remote",
  "departments": ["Software Engineering"],
  "offices": ["United States"],
  "firstPublished": "2026-10-29T15:24:15-04:00",
  "updatedAt": "2026-10-29T15:24:15-04:00",
  "requisitionId": "ONE",
  "salaryMin": 162000,
  "salaryMax": 186000,
  "salaryCurrency": "USD",
  "salaryPeriod": "YEAR",
  "salarySource": "pay_transparency",
  "salaryRanges": [{ "min": 162000, "max": 186000, "currency": "USD", "title": "Pay Range" }],
  "metadata": [{ "name": "Workplace Type", "value": "Remote" }],
  "descriptionText": "Airbnb was born in 2007 when two hosts welcomed three guests...",
  "scrapedAt": "2026-10-30T06:13:45.639Z"
}
```

With change detection on, items also have `changeType`, `changedFields` and `firstSeenAt` (removed jobs come as separate `changeType: "removed"` items). New fields are only added, never renamed, so existing integrations keep working.

You can download results as JSON, CSV, Excel, XML or HTML, or fetch them through the Apify API.

### Job alerts and change detection (new / updated / removed)

Turn on **`changeDetection`** and schedule the Actor (for example daily in **Schedules** in Apify Console). Every run is compared with the previous run that used the same companies and filters:

- `changeType: "new"` - the job appeared since the last run.
- `changeType: "updated"` - the title, location, department, employment type, salary or description changed; `changedFields` lists which.
- `changeType: "removed"` - the job was open last time and is gone now (filled or closed). The item has `jobId`, `title`, `url`, `location`, `firstSeenAt` and `removedAt`.
- Unchanged jobs are skipped unless you turn on `includeUnchanged`. Every output job also gets `firstSeenAt`.

The snapshot lives in a named key-value store (`changeStoreName`, default `greenhouse-jobs-changes`), so it survives between runs. Use a different store name per monitor. Turn on `skipFirstRunOutput` to save the baseline silently on the first run. Change counts per company appear in `RUN_SUMMARY`. If a result limit (`maxJobsPerCompany` / `maxJobs`) cuts the output, the jobs that were not output stay pending and come out as `new` on the next run, so nothing is missed.

```json
{ "companies": ["airbnb"], "changeDetection": true, "skipFirstRunOutput": true, "changeStoreName": "my-hiring-monitor" }
```

Prefer something simpler? `onlyNewJobs` outputs only job IDs not seen before (no updated/removed tracking).

#### Send changes to Slack, email or a webhook

- **Slack / email:** in the Actor's **Integrations** tab, add the Slack or email integration and trigger it on *Run succeeded*. Because change-detection runs output only changes, the message is your alert.
- **Webhook (your own endpoint, Zapier "Catch Hook", n8n "Webhook" node, Make "Custom webhook"):** add an HTTP webhook for the event `ACTOR.RUN.SUCCEEDED`. The default payload contains `resource.defaultDatasetId`; fetch the changes from `https://api.apify.com/v2/datasets/<defaultDatasetId>/items?clean=true` (add `&token=...` or an `Authorization: Bearer` header) and filter by `changeType`.

### Use with AI agents (MCP)

This Actor can be called by AI agents through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp). It runs with limited permissions and pay-per-event pricing, so agents only pay for the results they get. To give Claude, Cursor, VS Code or any MCP client a dedicated tool for it, add this server URL:

```
https://mcp.apify.com?tools=wowlab/greenhouse-jobs-scraper
```

The agent can then call it with plain inputs such as `{"companies": ["stripe.com"], "keywords": ["engineer"], "maxJobs": 20}`. Every input field has a description written for agents, and results come back as clean JSON with plain-text descriptions, ready for RAG, classification or summaries. Set `maxJobs` (and a max total charge in the call options) to keep agent spend predictable.

### Integrations: Make, n8n, Zapier, Google Sheets

- **Make:** use the Apify app's *Run an Actor* module with `wowlab/greenhouse-jobs-scraper`, then *Get Dataset Items*.
- **n8n:** use the Apify node (*Run Actor* + *Get dataset items*), or an HTTP Request node calling `POST https://api.apify.com/v2/acts/wowlab~greenhouse-jobs-scraper/run-sync-get-dataset-items`.
- **Zapier:** use the Apify app (*Run Actor* action, or *Finished Actor Run* trigger + *Fetch Dataset Items*).
- **Google Sheets, Airtable, Slack, email:** connect them in the Actor's **Integrations** tab, or export the dataset as CSV/Excel/JSON.
- **API:** `POST https://api.apify.com/v2/acts/wowlab~greenhouse-jobs-scraper/run-sync-get-dataset-items` with the input as JSON body returns the jobs directly.

### Pricing

Pay per result: **$1.50 per 1,000 jobs** ($0.0015 per job) plus a tiny start fee ($0.00005 per run per GB of memory; the default is 256 MB). No subscription. The Pricing tab shows the current price. Example: 500 jobs cost about $0.75. With change detection, only changed jobs (and removed-job records) are output and charged, so daily monitoring stays cheap. Set `maxJobs` or a maximum cost per run to cap spend.

### How do I find a company's Greenhouse board token?

Open the company's careers page and click a job. If the URL contains `greenhouse.io/<token>` or `gh_jid=`, the company uses Greenhouse, and `<token>` is usually the company name in lowercase (`airbnb`, `stripe`, `discord`). You can paste the full URL, or just the company name or website and let auto-detection find it.

### Is it legal?

This Actor reads only the **public job postings** that companies publish on purpose through Greenhouse's official public Job Board API. It collects no personal data and needs no login. You are responsible for how you use the data, so check the terms of the sites you rely on.

### FAQ

**Why are some salary fields empty?** Not every company publishes pay ranges. When none is published or found in the text, the salary fields are `null`.

**A board returns "not found".** The board token is wrong, or the company has left Greenhouse. `RUN_SUMMARY` says so, and if the company has a board on another supported ATS it names it (`foundOnOtherAts`).

**Other ATS platforms?** See our [Lever Jobs Scraper](https://apify.com/wowlab/lever-jobs-scraper) and [Ashby Jobs Scraper](https://apify.com/wowlab/ashby-jobs-scraper), which use the same input and similar output.

# Actor input Schema

## `companies` (type: `array`):

One entry per company. Accepts: a Greenhouse board token (e.g. `airbnb`), a board URL (`https://boards.greenhouse.io/airbnb`, `https://job-boards.greenhouse.io/stripe`), an embed URL (`?for=token`), a job URL, or - with auto-detection on - a company name or website (e.g. `Stripe`, `stripe.com`). Optional if you pick a company list below.

## `companyLists` (type: `array`):

Add curated companies to the run (verified to have open jobs on 2026-10-30). Only the list entries that use this ATS are added. Values: `ai-companies` (AI labs & AI startups), `fintech` (Fintech & crypto), `developer-tools` (Developer tools, cloud & data infrastructure), `consumer-tech` (Consumer apps & marketplaces), `b2b-saas` (B2B SaaS), `robotics-autonomy-defense` (Robotics, autonomy & defense tech). Lists are a convenience, not complete directories.

## `autoDetect` (type: `boolean`):

For entries that are a company name (with spaces) or a website domain, find the Greenhouse board automatically (website careers-page links first, then name guesses). If the company is not on Greenhouse but is on another supported ATS, the run summary says which one.

## `keywords` (type: `array`):

Keep only jobs whose title (or description, if 'Search keywords in description' is on) contains at least one of these words or phrases. Case-insensitive substring match. Example: \["engineer", "data scientist"]. Leave empty to keep all jobs.

## `searchInDescription` (type: `boolean`):

Also match keywords against the job description, not only the title. Slower on ATSs that need one request per job for the description.

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

Keep only jobs whose location (any of its locations) contains one of these strings. Case-insensitive substring match. Example: \["London", "United States", "Remote"].

## `departments` (type: `array`):

Keep only jobs whose department or team contains one of these strings. Example: \["Engineering", "Sales"].

## `remoteOnly` (type: `boolean`):

Keep only jobs marked remote by the ATS, or whose location text says remote.

## `postedWithinDays` (type: `integer`):

Keep only jobs published within this many days. 0 = no limit.

## `includeDescription` (type: `boolean`):

Include the full job description as plain text in `descriptionText`.

## `includeDescriptionHtml` (type: `boolean`):

Also output the original description HTML in `descriptionHtml`. Makes results larger.

## `includeSalary` (type: `boolean`):

Output salary fields. Uses Greenhouse pay-transparency data when the company publishes it (one extra request per job), otherwise the pay range parsed from the job text (min, max, currency, period).

## `changeDetection` (type: `boolean`):

Compare with the previous run that used the same companies and filters. Each job gets `changeType` = `new` or `updated` (with `changedFields`), and jobs that disappeared since the last run are output as `changeType` = `removed` items. Unchanged jobs are skipped unless 'Include unchanged jobs' is on. State is kept in the named key-value store below, so schedule this Actor to get a daily diff. If a result limit (`maxJobsPerCompany` / `maxJobs`) cuts the output, the jobs that were not output stay pending and are reported as `new` on the next run, so nothing is missed.

## `changeStoreName` (type: `string`):

Name of the key-value store that keeps the job snapshot for change detection. Use a different name for each independent monitor.

## `includeUnchanged` (type: `boolean`):

With change detection on: also output jobs that did not change (`changeType` = `unchanged`), so every run returns the full current list.

## `includeRemoved` (type: `boolean`):

With change detection on: output jobs that were open in the previous run but are gone now (`changeType` = `removed`, with `removedAt`).

## `skipFirstRunOutput` (type: `boolean`):

With change detection on: the first run for a company only saves the baseline and outputs nothing (no result charges); later runs output only the changes.

## `onlyNewJobs` (type: `boolean`):

Simpler alternative to change detection: output only jobs whose IDs were not seen in previous runs with this option on. Seen job IDs are stored in the named key-value store below.

## `monitoringStoreName` (type: `string`):

Name of the key-value store that keeps seen job IDs for 'Only new jobs'. Use different names for different monitors.

## `dedupeSimilar` (type: `boolean`):

Drop jobs that have the same title, location and description as a job already output in this run (e.g. one role reposted under several boards or companies). Exact duplicates of the same job ID are always removed.

## `maxJobsPerCompany` (type: `integer`):

Maximum jobs to output per company after filtering. 0 = no limit.

## `maxJobs` (type: `integer`):

Maximum jobs to output in this run. 0 = no limit. Use this to cap cost; the run also stops at your maximum cost per run.

## Actor input object example

```json
{
  "companies": [
    "airbnb",
    "https://job-boards.greenhouse.io/stripe"
  ],
  "companyLists": [],
  "autoDetect": true,
  "keywords": [],
  "searchInDescription": false,
  "locations": [],
  "departments": [],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "includeDescription": true,
  "includeDescriptionHtml": false,
  "includeSalary": true,
  "changeDetection": false,
  "changeStoreName": "greenhouse-jobs-changes",
  "includeUnchanged": false,
  "includeRemoved": true,
  "skipFirstRunOutput": false,
  "onlyNewJobs": false,
  "monitoringStoreName": "greenhouse-jobs-monitor",
  "dedupeSimilar": false,
  "maxJobsPerCompany": 0,
  "maxJobs": 0
}
```

# Actor output Schema

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

All scraped job postings (title, company, location, remote flag, departments, salary min/max/currency/period, dates, URL, description).

## `runSummary` (type: `string`):

Per-board status: jobs on board, jobs matched, jobs saved and any errors.

# 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": [
        "airbnb",
        "https://job-boards.greenhouse.io/stripe"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("wowlab/greenhouse-jobs-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": [
        "airbnb",
        "https://job-boards.greenhouse.io/stripe",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("wowlab/greenhouse-jobs-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": [
    "airbnb",
    "https://job-boards.greenhouse.io/stripe"
  ]
}' |
apify call wowlab/greenhouse-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,wowlab/greenhouse-jobs-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/5x27ydRz9od2ZWcYj/builds/uCo9cJ9trcMtKh0yR/openapi.json
