# Greenhouse Jobs Scraper & Change Monitor (`luminar/greenhouse-jobs-change-monitor`) Actor

Export Greenhouse jobs with descriptions, locations and apply links. Track new jobs by company and filters, with stable keys for spreadsheets and automation.

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

## Pricing

from $0.68 / 1,000 verified job records

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

Turn the Greenhouse career pages you follow into a useful job export and a repeatable hiring watch list.

Get current roles with full descriptions, departments and apply links; read selected job details; or compare a saved list for new, updated and safely closed postings.

### 🚀 Start in 60 seconds

1. Keep **Collect company jobs** and enter one company board slug, such as `airbnb`.
2. Start with 100 jobs and inspect both job rows and coverage.
3. Export the Dataset as CSV, JSON or Excel.

```json
{"companyBoards":["airbnb"],"maxJobs":100}
```

### 🎯 Choose the right workflow

**Collect company jobs** returns current matching postings across your selected boards. **Read selected job details** accepts a public Greenhouse job URL or `board/job-id` and ignores company-board filters. **Monitor hiring changes** delivers current matching jobs plus NEW, UPDATED, CLOSED or LEFT\_FILTER change records from sequential runs of the same watch list.

The first monitor run seeds a baseline by default and returns job rows without change rows. Choose **Return current jobs as new** to receive NEW change rows for that initial set too. Every checked and delivered job is billed, including unchanged jobs. NEW and UPDATED change rows are included without another charge; verified CLOSED and LEFT\_FILTER observations are billed as absences.

### 📦 What you get

Job rows include job ID, company board and name, title, source location, departments, offices, description HTML and plain text, publication/update dates and apply links. Source metadata remains nullable. Optional public application-question definitions and pay ranges can be included; no applicant data is collected.

Change rows add `changeKind`, `changeId` and the previous observation fingerprint. A CLOSED event means the posting disappeared from a complete checked board; it does not establish that someone was hired. LEFT\_FILTER means the job still exists on the checked board but no longer matches your filters. A job entering your watch list is NEW, which can include an older posting newly matching the filter. CLOSED and LEFT\_FILTER rows retain earlier identity and summary fields; long descriptions, questions, metadata and pay ranges are null in those rows.

Every Dataset contains a `recordType` column: `job`, `change` or `coverage`. The table view contains all row types, so downstream workflows should filter on that column.

![Greenhouse job output sample](https://api.apify.com/v2/key-value-stores/DJgPR6wwS2tIaLDQW/records/greenhouse_jobs_change_monitor--a54935dca98b9ce3-output.png)

### 🎛️ Input guide

Use up to 50 Greenhouse company board URLs or slugs. Regional EU boards use their EU board URL or `eu:company`. This tool discovers jobs inside known boards; it does not discover every employer on Greenhouse.

Combine department, source-location, keyword and inclusive publication-date filters. Keywords match title or description literally, not semantically. The remote toggle only matches the word remote in the location label. Details mode reads exactly the specified board-scoped job targets without those filters.

Repeat runs compare the current matching jobs with the saved watch list and return no change rows when nothing changed. For monitoring, keep the watch-list name, targets, filters and result limits unchanged. A change to those settings starts a separate baseline. Run one watch list sequentially; concurrent writes to the same watch list are not supported. Collect and details leave monitoring state unchanged.

### 💰 Pricing

Each verified collection costs **$0.009**, plus the price below for every checked and delivered current job or verified absence. A collection fee applies once per run that verifies at least one requested board or job, including confirmed empty results.

| Your Apify pricing tier | Per job or absence | Collection with 100 jobs |
|---|---:|---:|
| Free | $0.0018 | $0.189 |
| Bronze | $0.00135 | $0.144 |
| Silver | $0.0009 | $0.099 |
| Gold | $0.000675 | $0.0765 |

The table covers the four standard discount tiers. This Actor currently uses the Gold rate for Platinum and Diamond as well; no separate enterprise discount is offered. Check the active Apify pricing panel for your account.

Unchanged jobs and the initial baseline are paid verified information. NEW and UPDATED rows are included in the corresponding current-job charge. CLOSED and LEFT\_FILTER are paid verified absence records; a job cannot be billed as both current and absent in one comparison. Coverage rows are free. A fully failed or blocked collection is free. Source-published optional details are included in the job price.

Set the input spending limit and the Apify run limit. The Actor refuses work when the configured maximum current jobs plus possible absences from the saved baseline could exceed either limit. Included NEW/UPDATED rows can make the Dataset larger than its paid record count.

### ✅ Coverage you can trust

COMPLETE or EMPTY\_CONFIRMED means the requested board was verified and the matching result was fully checked. CAPPED means a result limit was reached. FAILED means a source could not be verified; NOT\_VISITED means the run limit prevented visiting it. The summary reports PARTIAL when verified and failed targets coexist.

No closures are inferred from capped, failed or unvisited boards. Those boards keep earlier unseen baseline entries. Missing optional fields stay null, never fabricated. A board changes over time; results are not an atomic snapshot of all employers.

### 🔌 API and automation

Use your Apify credentials to run the Actor and export its Dataset to a spreadsheet, database or job board. Filter by `recordType`. Keep `stableId` as your job key and `changeId` as your change-event key. Recovery must use the original interrupted run when a paid delivery is unresolved; starting a new run against that watch list fails safely.

For a future n8n workflow, run this saved watch list on a schedule, fetch the Dataset, and select `recordType = change` with `changeKind = NEW`. Upsert the current job table by `stableId` and an event-history table by `changeId`. These keys make reprocessing the same successful run safe. This Actor does not create or activate an n8n workflow for you.

```json
{
  "workflow": "monitor",
  "companyBoards": ["airbnb", "stripe"],
  "keywords": ["engineer"],
  "location": "Remote",
  "maxJobs": 500,
  "maxJobsPerBoard": 500,
  "stateNamespace": "remote-engineering",
  "firstRunBehavior": "seed_only"
}
```

### ⚠️ Not yet supported

Recorded source checks cover Airbnb, Stripe and GitLab, plus the EU-hosted Overstory board. Coverage is board-specific; this is not a guarantee for every employer. There is no global employer index, application submission, private hiring pipeline, candidate information, salary inference or AI classification.

Limits are 50 targets, 10,000 current jobs and 10,000 saved identities, plus a collection budget of up to 60 seconds. Delivery and shutdown take additional time. Large or detail-heavy requests can return honest partial coverage; inspect it before relying on absence. Requested detail failure makes that target untrusted and free. Very large descriptions or exports can reach a size limit and return incomplete coverage; no content is silently truncated.

### ❓ FAQ and support

**Why did a job disappear without a CLOSED event?** A capped or partial comparison cannot prove closure. Use a narrower complete watch list.

**Where is the salary?** Only source-published pay transparency is returned when requested. Absence is null, not an estimate.

**How do I report a problem?** Open an issue with the public board URL, your input and run ID. Do not include credentials or applicant information.

# Actor input Schema

## `workflow` (type: `string`):

Collect exports current matching jobs. Details reads named job URLs. Monitor compares sequential runs of one watch list and delivers matching jobs plus new, updated, closed or left-filter changes.

## `companyBoards` (type: `array`):

For collect and monitor: paste Greenhouse board URLs or company slugs, such as airbnb. EU boards use an EU board URL or eu:slug. This is not a global company search.

## `jobTargets` (type: `array`):

For details only: paste a Greenhouse job URL or board/job-id, such as airbnb/12345. Bare job IDs are not enough. Company board input is ignored in details mode.

## `department` (type: `string`):

Case-insensitive text match against any department name. Applies to collect and monitor.

## `location` (type: `string`):

Case-insensitive match against the source location label. No geocoding or radius inference.

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

Return jobs matching any keyword as a literal substring of title or description. Up to 20 terms; combined with other filters.

## `firstPublishedAfter` (type: `string`):

Optional inclusive UTC date in YYYY-MM-DD form. Jobs with no published date are excluded when enabled.

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

Keep only jobs whose location label explicitly contains remote. This does not infer a remote-work policy from job text.

## `includeContent` (type: `boolean`):

Deliver source description HTML and normalized plain text. Filtering may still inspect description when this is off.

## `includeQuestions` (type: `boolean`):

Reads public question definitions, never applicant answers. May increase work; a job whose requested details cannot be verified is not charged.

## `includePayRanges` (type: `boolean`):

Return source-published pay transparency ranges when available. Never estimates salary. May increase work and can cause a bounded partial result.

## `stateNamespace` (type: `string`):

Monitor runs with the same targets, filters, limits and name share a baseline. Run sequentially. Changing those settings starts a separate comparison.

## `firstRunBehavior` (type: `string`):

Seed only delivers paid verified jobs and saves a baseline without change rows. Return current jobs as new also includes free NEW change rows.

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

Hard limit across all company boards. Capped coverage cannot prove a missing job closed.

## `maxJobsPerBoard` (type: `integer`):

Per-board matched-job limit, also bounded by the whole-run limit.

## `maxRuntimeSecs` (type: `integer`):

Stops new collection work with room to finish delivery. The platform timeout should be at least 60 seconds greater.

## `maxBuyerChargeUsd` (type: `integer`):

Refuses to start if the maximum possible charge for this input and saved baseline exceeds this amount. The Apify run spending limit is checked separately.

## Actor input object example

```json
{
  "workflow": "collect",
  "companyBoards": [
    "airbnb"
  ],
  "jobTargets": [],
  "department": "",
  "location": "",
  "keywords": [],
  "firstPublishedAfter": "",
  "remoteOnly": false,
  "includeContent": true,
  "includeQuestions": false,
  "includePayRanges": false,
  "stateNamespace": "default",
  "firstRunBehavior": "seed_only",
  "maxJobs": 100,
  "maxJobsPerBoard": 500,
  "maxRuntimeSecs": 60,
  "maxBuyerChargeUsd": 5
}
```

# Actor output Schema

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

Dataset containing current jobs, changes and coverage rows.

## `summary` (type: `string`):

Counts, coverage by target and the calculated buyer charge for this collection.

# 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 = {
    "workflow": "collect",
    "companyBoards": [
        "airbnb"
    ],
    "jobTargets": [],
    "department": "",
    "location": "",
    "keywords": [],
    "firstPublishedAfter": "",
    "remoteOnly": false,
    "includeContent": true,
    "includeQuestions": false,
    "includePayRanges": false,
    "stateNamespace": "default",
    "firstRunBehavior": "seed_only",
    "maxJobs": 100,
    "maxJobsPerBoard": 500,
    "maxRuntimeSecs": 60,
    "maxBuyerChargeUsd": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("luminar/greenhouse-jobs-change-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 = {
    "workflow": "collect",
    "companyBoards": ["airbnb"],
    "jobTargets": [],
    "department": "",
    "location": "",
    "keywords": [],
    "firstPublishedAfter": "",
    "remoteOnly": False,
    "includeContent": True,
    "includeQuestions": False,
    "includePayRanges": False,
    "stateNamespace": "default",
    "firstRunBehavior": "seed_only",
    "maxJobs": 100,
    "maxJobsPerBoard": 500,
    "maxRuntimeSecs": 60,
    "maxBuyerChargeUsd": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("luminar/greenhouse-jobs-change-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 '{
  "workflow": "collect",
  "companyBoards": [
    "airbnb"
  ],
  "jobTargets": [],
  "department": "",
  "location": "",
  "keywords": [],
  "firstPublishedAfter": "",
  "remoteOnly": false,
  "includeContent": true,
  "includeQuestions": false,
  "includePayRanges": false,
  "stateNamespace": "default",
  "firstRunBehavior": "seed_only",
  "maxJobs": 100,
  "maxJobsPerBoard": 500,
  "maxRuntimeSecs": 60,
  "maxBuyerChargeUsd": 5
}' |
apify call luminar/greenhouse-jobs-change-monitor --silent --output-dataset

```

## MCP server setup

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