# Greenhouse Jobs Monitor & Scraper (`redomic/greenhouse-jobs-monitor`) Actor

Monitor Greenhouse jobs from any company careers board (Airbnb, Stripe, Discord and thousands more). Get flat rows with title, location, posting date, apply link, and published salary ranges as numbers: min, max, currency, period. Schedule monitor runs to get only new, changed, and removed jobs.

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

## Pricing

$2.00 / 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 Monitor & Scraper

![Greenhouse Jobs Monitor & Scraper](https://api.apify.com/v2/key-value-stores/gWkRolOrSe1OMioQD/records/greenhouse-jobs-monitor-hero)

Monitor Greenhouse jobs from any company careers board (for example `airbnb`, `stripe`, or `discord`) and get every open job as flat rows: title, location, posting date, apply link and, when the company publishes one, **the salary range as numbers**: `salaryMin`, `salaryMax`, `salaryCurrency` (ISO 4217) and `salaryPeriod` (`year`, `month`, `hour`). Schedule runs in `monitor` mode to get only new, changed, and removed jobs since the last run.

Real rows from `airbnb`, 31 Oct 2026 (default input, no extra options):

| title | location | workplaceType | salaryMin | salaryMax | salaryCurrency | salaryPeriod |
|---|---|---|---|---|---|---|
| AirCover Specialist, Claims Experience | Canada | Remote | 58,000 | 68,000 | CAD | year |
| Associate Principal, Procurement Innovation Supplier Programs | United States | Remote | 102,000 | 120,000 | USD | year |
| Business Development Manager | Berlin, Germany | Hybrid | 71,000 | 84,000 | EUR | year |
| Business Operations and Growth Lead | United States | Remote | 168,000 | 203,000 | USD | year |
| Business Process Improvement Manager | United States | Remote | 156,000 | 195,000 | USD | year |

### What it does

Salary comes from Greenhouse itself. Companies that use Greenhouse's pay-transparency feature attach a pay range to each job; the Actor requests it (`?pay_transparency=true` on the public board API) and splits it into `salaryMin`, `salaryMax`, `salaryCurrency` and `salaryPeriod`. How often it is filled depends on the company. Counts from runs on 31 Oct 2026 with the input `{"boards": ["<board>"], "maxItems": 1000}`:

| board | jobs | with salary |
|---|---|---|
| discord | 41 | 38 |
| airbnb | 202 | 165 |
| databricks | 901 | 491 |
| figma | 140 | 93 |
| stripe | 746 | 0 (Stripe writes pay only in the description: use `includeDescription`, 24 found) |

When a job has several pay zones (Robinhood lists 3), all of them are in `salaryRanges`, and `salaryMin`/`salaryMax` span them. With `includeDescription`, a range written only in the text ("Salary: $156,800 - $235,200/yr") is read as well. A job without a published range has `null` salary fields.

In `monitor` mode the Actor stores each board's jobs in a named key-value store and returns only jobs that are new, changed (title, location, update time, requisition id or pay range) or removed since the previous run, with `changeType`. You pay for new and changed jobs only.

Every board you enter gets a status row, so a typo (`"_status": "failed"`, reason `board not found (404)`) or an empty board is visible in the dataset. The empty case, from a battery run on 31 Oct 2026:

```json
{"_status": "empty", "board": "prisma", "mode": "jobs", "rows": 0, "reason": "board has no open jobs", "total": 0}
```

A board that exists but has no open jobs (like `prisma` above) is a successful run with an `empty` status row, not a failure; a board that cannot be fetched (`_status: "failed"`, for example a 404 typo) gets a failed status row, and the run fails only when no board could be fetched; with some boards fetched it succeeds as `partial` and you keep their jobs. Rows are capped per board by `maxItems`, and the run stops at the maximum cost you set. Placeholder requisition ids (`ONE`, `MULTI`, `See Opening ID`) are returned as `null`.

The data comes from the public `boards-api.greenhouse.io` JSON API: one request per board, no proxy, no browser. A run over a few boards takes seconds.

### Input

| Field | Type | Default | Meaning |
|---|---|---|---|
| `boards` | array of strings | `["airbnb"]` | Greenhouse board slugs — the part after `boards.greenhouse.io/`, for example `airbnb`, `stripe`. Full board or job URLs are accepted; only the slug is kept |
| `mode` | enum | `jobs` | `jobs` lists every open job; `detail` fetches full descriptions for specific `jobIds`; `monitor` reports only new, changed and removed jobs since the last run |
| `jobIds` | array of integers | `[]` | For `mode: detail`: the Greenhouse job ids to fetch in full |
| `search` | string | `""` | Keep only jobs whose title, location or department (or description, when included) contains this text. Case-insensitive |
| `postedSinceDays` | integer | `null` | Keep only jobs first published within the last N days (`postedAt` at or after the run time minus N days). For "new this week" set `7`, for "new this month" set `30` — no monitor state needed. Empty for every open job. Works in `jobs`, `detail` and `monitor` modes |
| `locations` | array of strings | `[]` | Keep only jobs whose location contains one of these values (case-insensitive, OR within the list). Empty for every location. AND-combined with the other filters |
| `workplaceTypes` | array of strings | `[]` | Keep only jobs with one of these workplace types (`Remote`, `Hybrid`, `Onsite`; OR within the list). `Remote` also matches when the location says remote. Empty for every workplace type; jobs with no workplace type are excluded only when this filter is set |
| `employmentTypes` | array of strings | `[]` | Keep only jobs with one of these employment types (`Full-time`, `Part-time`, `Contract`, `Internship`, `Apprenticeship`, `Temporary`; OR within the list). Empty for every type; jobs with no employment type are excluded only when this filter is set |
| `minSalary` | number | `null` | Minimum yearly salary: keep only jobs whose `salaryMax`, converted to yearly (year x1, month x12, hour x2080), is at least this value. Jobs with no published salary are excluded |
| `salaryCurrency` | string | `""` | ISO 4217 code (for example `CAD`) that salary must be in when `minSalary` is set. Empty for any currency |
| `hasSalaryOnly` | boolean | `false` | Keep only jobs with a published salary range. Off for every job, including those with null salary |
| `includeDescription` | boolean | `false` | Add the full description (plain text and HTML), department and offices, and read pay ranges written only in the description. Same single request per board, just a bigger response |
| `maxItems` | integer | `100` | Upper limit on job rows per board, 1 to 1000 (3 boards x 100 = at most 300 rows) |

Example:

```json
{ "boards": ["airbnb"], "mode": "jobs", "maxItems": 20 }
```

### Output

One row per job, then one status row per board, then a summary row as the last row. In Apify Console the "Jobs" table shows the columns most buyers use (title, location, salary, dates, links); the "Full details" view adds ids, language, descriptions and the per-zone `salaryRanges`. Example values come from the Greenhouse board API captured on 2026-10-31 (the default input's board, `airbnb`).

| Field | Example | Meaning |
|---|---|---|
| `id` | `8306554` | Greenhouse's job id, stable across runs |
| `board` | `airbnb` | The board (company) you searched |
| `title` | `AirCover Specialist, Claims Experience` | Job title |
| `company` | `Airbnb` | Employer |
| `location` | `Canada` | The job's location as the board lists it |
| `workplaceType` | `Remote` | Remote / Hybrid / Onsite, from the company's own field, or `Remote` when the location says so; `null` otherwise |
| `url` | `https://careers.airbnb.com/positions/8306554?gh_jid=8306554` | The job posting URL |
| `applyUrl` | same as `url` | The apply link (on Greenhouse boards the posting is the application) |
| `postedAt` | `2026-10-28T14:52:05Z` | First-published date, UTC ISO-8601 |
| `updatedAt` | `2026-10-28T14:52:05Z` | Last-updated date, UTC ISO-8601 |
| `applicationDeadline` | `null` | Application deadline, when the board sets one |
| `internalJobId` | `3591434` | The company's internal job id |
| `requisitionId` | `null` | The company's requisition reference; `null` when absent or a placeholder like `ONE` |
| `language` | `en` | Posting language |
| `department` | `Community Support` | Primary department (detail / `includeDescription` only) |
| `departments` | `["Community Support"]` | All departments (detail / `includeDescription` only) |
| `offices` | `["Canada"]` | Offices (detail / `includeDescription` only) |
| `employmentType` | `null` | Full-time, Part-time, Contract, Internship, Apprenticeship or Temporary, only when the title (or the description, when included) states it; `null` otherwise |
| `salaryMin` | `58000` | Lowest published pay (across all pay zones of the same currency and period) |
| `salaryMax` | `68000` | Highest published pay |
| `salaryCurrency` | `CAD` | ISO 4217 currency code |
| `salaryPeriod` | `year` | `year`, `month` or `hour` |
| `salaryText` | `CAD 58,000 - 68,000 per year` | The range as one readable string |
| `salarySource` | `payTransparency` | `payTransparency` (the company's structured Greenhouse pay range) or `description` (read from the description text); `null` when no pay is published |
| `salaryRanges` | `[{"min": 58000, "max": 68000, "currency": "CAD", "period": "year", "label": "Canada Annual Pay Range"}]` | Every published range, e.g. one per pay zone |
| `descriptionText` | `Airbnb was born in 2007 when two hosts welcomed...` | Full description as plain text (detail / `includeDescription` only) |
| `descriptionHtml` | `<div class="content-intro"><p>...` | Full description as HTML (detail / `includeDescription` only) |
| `changeType` | `new` | Monitor mode only: `new`, `changed` or `removed` |
| `scrapedAt` | `2026-10-31T00:00:00Z` | When the row was fetched (UTC) |

The status row carries `_status` (`ok`, `partial`, `empty`, `failed`), `board`, `mode`, `rows`, `reason` and `total` (the board's own job count). The last row is a summary with `_status: "summary"` and totals: `rows`, `boardsOk`, `boardsEmpty`, `boardsFailed`, and — in monitor mode — `checked`, `unchanged`, `new`, `changed`, `removed`, `firstRun`, `runStatus`, `charged` plus the evidence ids `newIds`, `changedIds`, `removedIds` (the job ids of the event rows delivered in that run, so the summary can be reconciled against the dataset). Every monitor run pushes exactly one summary row; a quiet run that finds nothing new ends successfully with `checked` jobs examined, `unchanged` jobs untouched, `new` 0 and `charged` 0, and `runStatus` `ok`.

### Pricing

`result`: $0.002 per job row delivered, no start fee. 1,000 jobs ≈ $2.00. In monitor mode you pay only for new and changed jobs; unchanged and removed jobs are free.

### Monitor recipe

Track hiring signals for one company, and get a webhook whenever something changes:

1. Create a task with `{ "boards": ["airbnb"], "mode": "monitor" }`.
2. Schedule it hourly (or daily) in Apify Console.
3. Add a webhook for "run succeeded" to push `new` / `changed` / `removed` rows to Slack, Make or n8n — or export the dataset straight to Google Sheets.
4. Filter the dataset on `changeType` to see what actually moved since the previous run. A changed pay range counts as `changed`, so you can also watch salary bands move.

### FAQ

- **How do I find a board slug?** Open a company's careers page; if it is a Greenhouse page the slug is the part right after `boards.greenhouse.io/` (or `job-boards.greenhouse.io/`), e.g. `boards.greenhouse.io/airbnb` → `airbnb`. You can paste the whole URL.
- **Can I search across every company at once?** No — Greenhouse's public API is per-board, and there is no global endpoint. Run several boards in one `boards` list instead.
- **Does it include salary?** Yes, whenever the company publishes it. Structured pay-transparency ranges come with every run at no extra cost; with `includeDescription` a range written in the description text is read as well. Companies that publish no pay get `null` salary fields: nothing is estimated or invented.
- **Why is `salaryPeriod` null on some rows?** The company gave a range but no period, and the amount alone cannot tell (e.g. PLN). The range is still returned.
- **What if a job closes?** The job drops off the board. In `jobs` mode it just disappears; in `monitor` mode it is reported as `removed`.

### What this does not do

It does not log in, break captchas or access anything behind Greenhouse's Harvest API; it reads only the public board endpoints. It does not guess missing fields (salary when no range is published, department when not published), and it does not search a keyword across all Greenhouse companies.

### Other languages

#### Español

Dale un tablero de Greenhouse — el identificador de la página de empleo de una empresa, por ejemplo `airbnb` — y obtén cada vacante abierta como filas: puesto, empresa, ubicación, departamento, fecha de publicación, el enlace para postularse y, cuando la empresa lo publica, el rango salarial en números (mínimo, máximo, moneda ISO y periodo). Para usarlo, rellena `boards` (por ejemplo `["airbnb"]`) y pulsa Start; pagas solo por cada empleo entregado.

#### Deutsch

Gib ein Greenhouse-Board an — den Slug der Karriereseite eines Unternehmens, zum Beispiel `airbnb` — und erhalte jede offene Stelle als Zeilen: Titel, Firma, Ort, Abteilung, Veröffentlichungsdatum, Bewerbungslink und, wenn das Unternehmen es veröffentlicht, die Gehaltsspanne als Zahlen (Minimum, Maximum, ISO-Währung, Zeitraum). Fülle dafür `boards` (zum Beispiel `["airbnb"]`) aus und klicke auf Start; du zahlst nur pro gelieferter Stelle.

#### Français

Donnez-lui un board Greenhouse — le slug de la page carrière d'une entreprise, par exemple `airbnb` — et obtenez chaque offre ouverte sous forme de lignes : intitulé, entreprise, lieu, département, date de publication, lien de candidature et, quand l'entreprise la publie, la fourchette de salaire en chiffres (minimum, maximum, devise ISO, période). Remplissez `boards` (par exemple `["airbnb"]`) puis cliquez sur Start ; vous ne payez que pour chaque offre livrée.

#### Português

Informe um board do Greenhouse — o slug da página de vagas de uma empresa, por exemplo `airbnb` — e receba cada vaga aberta como linhas: título, empresa, local, departamento, data de publicação, link de candidatura e, quando a empresa publica, a faixa salarial em números (mínimo, máximo, moeda ISO e período). Preencha `boards` (por exemplo `["airbnb"]`) e clique em Start; você paga apenas por vaga entregue.

Sibling Actors: **Ashby Jobs Scraper with Salaries** and **Lever Jobs Scraper with Salaries** (same row shape for the other two big startup ATSs) and **Google Jobs API Scraper**.

### Use it from AI agents (MCP) and Python

**AI agents.** Add this Actor as a tool in Claude, Cursor, VS Code or any MCP client through the Apify MCP server. The agent reads the input schema, so a plain request is enough.

```json
{
  "mcpServers": {
    "greenhouse-jobs-monitor": {
      "url": "https://mcp.apify.com?tools=redomic/greenhouse-jobs-monitor",
      "headers": {
        "Authorization": "Bearer <YOUR_APIFY_TOKEN>"
      }
    }
  }
}
```

**Python.** `pip install apify-client`, then:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("redomic/greenhouse-jobs-monitor").call(run_input={"boards": ["airbnb"], "mode": "jobs"})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

### More from us

- [Ashby Jobs Scraper with Salaries](https://apify.com/redomic/ashby-jobs-monitor): Ashby jobs scraper for any startup careers board (OpenAI, Ramp, Notion and thousands more), with salary ranges as numbers: min, max, currency, period, plus equity and bonus flags
- [ATS Jobs Search: Greenhouse, Lever & Ashby Jobs API](https://apify.com/redomic/ats-jobs-search): Search 328,000+ open jobs from 11,000+ companies on Greenhouse, Lever and Ashby in one run
- [Google Jobs Scraper - Job Postings API](https://apify.com/redomic/google-jobs): Google Jobs and job postings scraper: get job title, company, location, posted date, salary, full description and every apply link for any search
- [Google Trends API: Trending Now & Interest Over Time](https://apify.com/redomic/google-trends): Trending-now searches and new-trends alerts/monitoring per country with the news behind them, plus interest over time, interest by region, and related and rising queries for any keyword
- [Lever Jobs Scraper with Salaries](https://apify.com/redomic/lever-jobs-monitor): Lever jobs scraper for any company's careers page (Palantir, Spotify, Zoox, Shield AI and thousands more): flat rows with title, team, location, workplace, commitment, apply link, salary as numbers and the full description merged

# Changelog

This Actor's version history is a separate document: https://apify.com/redomic/greenhouse-jobs-monitor/changelog.md

# Actor input Schema

## `boards` (type: `array`):

Company career-page slugs on Greenhouse. The part after boards.greenhouse.io/ — for example airbnb, stripe, greenhouse. You can also paste full board or job URLs; only the slug is kept.

## `mode` (type: `string`):

jobs lists every open job; detail fetches full descriptions for specific jobIds; monitor reports only new, changed and removed jobs since the last run.

## `jobIds` (type: `array`):

For mode 'detail': the Greenhouse job ids to fetch full detail for.

## `search` (type: `string`):

Keep only jobs whose title, location or department (or description, when included) contains this text. Case-insensitive. Leave empty for every open job.

## `postedSinceDays` (type: `integer`):

Keep only jobs first published within the last N days (postedAt at or after the run time minus N days). Set 7 for 'new this week', 30 for 'new this month'. Leave empty for every open job. Works in jobs, detail and monitor modes.

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

Keep only jobs whose location contains one of these values (case-insensitive, OR within the list). Leave empty for every location. AND-combined with the other filters.

## `workplaceTypes` (type: `array`):

Keep only jobs with one of these workplace types (Remote, Hybrid, Onsite; case-insensitive, OR within the list). Remote also matches when the location says remote. Leave empty for every workplace type. Jobs with no workplace type are excluded only when this filter is set.

## `employmentTypes` (type: `array`):

Keep only jobs with one of these employment types (Full-time, Part-time, Contract, Internship, Apprenticeship, Temporary; case-insensitive, OR within the list). Leave empty for every employment type. Jobs with no employment type are excluded only when this filter is set.

## `minSalary` (type: `number`):

Keep only jobs whose highest published pay (salaryMax), converted to a yearly amount (year x1, month x12, hour x2080), is at least this value. Jobs with no published salary are excluded. Leave empty for no minimum.

## `salaryCurrency` (type: `string`):

ISO 4217 code (for example CAD, USD, EUR) that salary must be in when minSalary is set. Leave empty for any currency.

## `hasSalaryOnly` (type: `boolean`):

Keep only jobs with a published salary range. Leave off for every job, including those with null salary.

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

Add the full job description (plain text and HTML), department and offices to every row, and read pay ranges written in the description when the company did not publish a structured one. Off by default. Costs no extra request: the board list is fetched once either way, just with descriptions included.

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

Upper limit on job rows per board (and so on what you pay): 3 boards x 100 = at most 300 rows. Default 100, maximum 1000.

## Actor input object example

```json
{
  "boards": [
    "airbnb"
  ],
  "mode": "jobs",
  "jobIds": [],
  "search": "",
  "locations": [],
  "workplaceTypes": [],
  "employmentTypes": [],
  "salaryCurrency": "",
  "hasSalaryOnly": false,
  "includeDescription": false,
  "maxItems": 20
}
```

# Actor output Schema

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

All result rows from the default 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 = {
    "boards": [
        "airbnb"
    ],
    "maxItems": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("redomic/greenhouse-jobs-monitor").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 = {
    "boards": ["airbnb"],
    "maxItems": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("redomic/greenhouse-jobs-monitor").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 '{
  "boards": [
    "airbnb"
  ],
  "maxItems": 20
}' |
apify call redomic/greenhouse-jobs-monitor --silent --output-dataset

```

## MCP server setup

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

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/nEV1O5H5OxeUamuo2/builds/T3d6grQzR26jSJqmy/openapi.json
