# Greenhouse Job Board Change Monitor (`relaygrove/greenhouse-job-board-change-monitor`) Actor

Track new, changed, removed and reappearing Greenhouse job posts. Automatic history, before/after field changes, guarded removal checks and JSON/CSV exports for hiring research and recruiting workflows.

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

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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 Job Board Change Monitor

Track changes to public Greenhouse job boards without downloading and comparing the entire board yourself. RelayGrove records a baseline on the first run, remembers it automatically, and reports what changed on later runs.

Useful for hiring-trend research, recruiting operations and job-data workflows that need observed changes rather than another full export. This tool is independently developed by RelayGrove and is not affiliated with Greenhouse or the companies being monitored.

**Public beta — no RelayGrove fee.** You pay only for the Apify platform resources used in your own account. This is a short beta while we evaluate recurring use and future pricing. No end date has been set. Any introduction of a RelayGrove fee will be announced in advance under Apify's pricing-change rules.

### Start in three steps

1. Enter one to ten Greenhouse board tokens. For `https://boards.greenhouse.io/stripe`, the token is `stripe`.
2. Run the Actor once to save your first baseline. Check `SUMMARY` for a successful complete observation.
3. Run it again with the same input, or schedule it in Apify. A daily schedule is a reasonable starting point when you do not need intraday changes.

```json
{
  "boards": ["stripe"],
  "monitorName": "daily-hiring",
  "confirmMissing": 2,
  "minimumConfirmationHours": 1
}
```

The first run produces `baseline` job events for existing listings, not proof of newly posted jobs. Each successfully fetched, validated and compared board also produces one `board_check` row. An unchanged repeat has zero job events but still has a check row for each successful board. A genuinely empty, valid board also produces a check row with `currentCount: 0`.

Automatic history lives in a named key-value store in your own Apify account. The Actor uses Limited permissions. Each account, Actor and monitor name has separate history. You do not need to copy snapshots between runs or provide a Greenhouse login.

Reuse the monitor name for the same board collection and removal rules. To change that configuration or start a fresh baseline, use a new monitor name. Two simultaneous runs of the same monitor are prevented from comparing or overwriting the same history; the second run fails clearly so it can be retried later.

### What the events mean

| Event | Meaning |
| --- | --- |
| `baseline` | Present at the first successful observation. |
| `new` | First observed after the baseline. This is not a guaranteed original posting date. |
| `changed` | Title, location, URL, department, office or description fingerprint changed. |
| `removed_from_board` | Absent in at least two complete observations, separated by the configured minimum time. This does not establish that a job was filled. |
| `reappeared` | Visible again after a previously confirmed removal. |

Timestamp-only source updates are ignored. Description changes are detected using a normalized text fingerprint; the full description and a text diff are not included. Changed structured fields include their before and after values.

Every job event includes `eventId`, `type`, `board`, `jobId`, `observedAt` and a `job` object containing the title, location, public URL, departments, offices, source update timestamp and description fingerprint. Export the dataset as JSON or CSV, or connect it to your own workflow through Apify.

The same dataset includes comparison summaries with `type: "board_check"`. These contain `monitorName`, `board`, `observedAt`, `ok: true`, `baseline`, `currentCount`, `trackedCount`, `missingPendingCount`, `removedCount`, `eventCount` and per-event `counts`. They do not have `eventId`, `jobId` or `job`. To process job events only, filter rows with `type !== "board_check"`.

A check row means that a complete board response was validated and compared. Rows are written before history is saved, so the row does **not** prove that the whole run succeeded or its history was committed. Use the final run status and `SUMMARY.stateCommitted` for those conclusions.

### Saved outputs

- **Dataset:** Job events with stable event IDs, plus one `board_check` row per successful board comparison. The existing output link remains named `events` for compatibility and returns both record types.
- **SUMMARY:** Per-board health, errors and `stateCommitted`, plus `eventCount` (job events only), `checkCount` (successful board comparisons) and `datasetRowCount` (both kinds of rows). An unchanged fully successful run has `eventCount: 0`, one check per selected board and the same number of dataset rows as checks.
- **NEXT_SNAPSHOTS:** A portable backup of the comparison history. Ordinary runs load history automatically.

An unavailable board, incomplete response or invalid data never becomes an empty successful observation and gets no successful `board_check` row. Failed boards retain their earlier history. Successful comparisons can still have rows and saved history in a partially failed run; the run remains failed. Inspect `SUMMARY` before retrying.

Job events and check rows are written before history advances. If a run is interrupted between those operations, a later run can repeat events. Deduplicate job events by `eventId` when combining outputs. Check rows summarize individual comparisons and have no event ID; do not count them as job changes. Exactly-once delivery is not promised. A timed-out run can hold its monitor lock for up to five minutes; start a new run after the lock expires. Runtime is capped at two minutes as a safety bound.

### Pricing and schedules

During this beta, RelayGrove charges no product fee. Apify calls this **pay per usage**: the compute, storage and other platform resources used by your runs are billed to your own Apify account or deducted from its available credits. Platform usage is not unlimited or guaranteed free. Consult the Actor's Pricing tab and your account limits before running or scheduling it.

Future pricing and the beta end date are undecided. The current no-developer-fee offer is temporary. Any paid transition will be announced in advance and follow Apify's applicable pricing-change rules; no automatic conversion date is set.

A schedule runs only when you create it in Apify. This Actor does not create schedules, send messages or enable external integrations itself. Storage persists between runs, but checking a board still requires a run. Named storage continues to occupy space until you remove it; ordinary platform limits apply.

### Scope and limits

- Supports public boards on the main `boards-api.greenhouse.io` endpoint. EU-region boards, other hiring platforms, arbitrary URLs and automatic company discovery are not supported.
- Between 1 and 10 board tokens per monitor; maximum 5,000 current posts per board and 10,000 tracked posts per board. Limits fail visibly rather than silently truncating data.
- Each source response is limited to 10 MiB with bounded retries. A large or slow batch may need to be split across separate monitors to fit the two-minute run limit.
- History is bounded but not automatically pruned. If tracked history reaches its limit, preserve the exported backup and start a new monitor; the new monitor establishes a fresh baseline.
- Description normalization handles ordinary HTML and entities but is not a full HTML parser. Some formatting changes can still produce a fingerprint change.
- These are source observations, not verified hiring outcomes or business intentions. Briefly removed listings, board migrations and publisher edits can affect results.
- Public job metadata only. No applicant information, private Greenhouse access, contact enrichment, proxy service or AI API is used.

### Migrating an earlier manual pilot

On a monitor's first run only, an optional `previousSnapshots` array can import a saved `NEXT_SNAPSHOTS` backup. Once automatic history exists, that saved history wins and the seed is ignored. Leave the field empty for normal use.

### Support

Use the Actor's Apify Issues tab when available. Include the run ID, board token, approximate time and a description of the problem. Do not include passwords, API tokens or applicant information. For a source error, inspect `SUMMARY`; for a busy monitor, wait for the other run or its lock to finish and start a new run.

For beta feedback, tell us what you use the results for, whether you have run the same monitor on more than one day, and one thing that would make it more useful. Scheduled checks are welcome. We do not add a separate usage tracker or send your monitored jobs to a RelayGrove server; Apify provides its own developer analytics.

# Actor input Schema

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

One to ten board tokens, such as stripe. URLs and other ATS platforms are not accepted.

## `monitorName` (type: `string`):

A name for this collection of boards, such as daily-hiring. Reuse it for future runs. Use a new name when changing boards or removal rules, or to start a fresh baseline. Different names keep separate histories.

## `confirmMissing` (type: `integer`):

At least two complete checks must show the post missing. This never means that the job was filled.

## `minimumConfirmationHours` (type: `number`):

Confirm removal only after this much time has passed since the first complete missing observation.

## `previousSnapshots` (type: `array`):

Migration option: supply a NEXT_SNAPSHOTS array from an earlier manual run. Leave empty for ordinary use. Imported history never replaces an existing automatic baseline.

## Actor input object example

```json
{
  "boards": [
    "stripe"
  ],
  "monitorName": "default",
  "confirmMissing": 2,
  "minimumConfirmationHours": 1,
  "previousSnapshots": []
}
```

# Actor output Schema

## `events` (type: `string`):

One board_check per complete successful comparison, plus baseline/new/changed/removed/reappeared job events. Filter type != board_check for job events. Check rows do not confirm whole-run success or saved history; inspect SUMMARY and run status.

## `nextSnapshots` (type: `string`):

A portable copy of observation history. Automatic runs load their own saved history; no copying is needed. Failed checks preserve previous snapshots.

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

Per-board results, eventCount excluding check rows, checkCount, total datasetRowCount, errors and stateCommitted. Inspect this record and run status to confirm success and saved history.

# 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": [
        "stripe"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("relaygrove/greenhouse-job-board-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 = { "boards": ["stripe"] }

# Run the Actor and wait for it to finish
run = client.actor("relaygrove/greenhouse-job-board-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 '{
  "boards": [
    "stripe"
  ]
}' |
apify call relaygrove/greenhouse-job-board-change-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,relaygrove/greenhouse-job-board-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/xFK8K63OlvvzQFLme/builds/fqO2m9oGK8N4dPbBo/openapi.json
