# Greenhouse Jobs Tracker: Export & Change Log (`downy_sublimity/greenhouse-watchlist-export`) Actor

Track job listings across up to 20 Greenhouse company boards. Get CSV/JSON exports plus a log of first-observed, edited and no-longer-listed postings using your previous snapshot. $0.05 per completed watchlist.

- **URL**: https://apify.com/downy_sublimity/greenhouse-watchlist-export.md
- **Developed by:** [Victor Hugo Bernardo da Costa](https://apify.com/downy_sublimity) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.05 / complete watchlist

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 Tracker: Export & Change Log

Enter up to 20 Greenhouse board names and get a clean CSV/JSON of their current job listings, plus a log of what changed since your last check. Built for recruiting teams, job aggregators and researchers who already know which companies they want to follow.

### Example export

Three rows from this Actor's real Airbnb export on **8 October 2026**. These are historical sample records; current availability may differ.

| board | title | location | url |
|---|---|---|---|
| airbnb | Senior Staff Machine Learning Engineer, Data & Eval | United States | [View listing](https://careers.airbnb.com/positions/6757302?gh_jid=6757302) |
| airbnb | Support Specialist, Premium  | Canada | [View listing](https://careers.airbnb.com/positions/6852954?gh_jid=6852954) |
| airbnb | Senior Staff Data Scientist, Guest & Host Marketplace AI | Remote | [View listing](https://careers.airbnb.com/positions/7107054?gh_jid=7107054) |

### Quick start

1. [Open the ready-made Airbnb example](https://apify.com/downy_sublimity/greenhouse-watchlist-export/examples/export-airbnb-job-listings-to-csv-and-json), or enter 1–20 company board identifiers in `boardSlugs` (for example, `airbnb`). An Apify account is enough; no separate installation or n8n account is required.
2. Check the active price and maximum charge, then start the Actor. Require run status `SUCCEEDED` and `SUMMARY.complete: true` before using its outputs.
3. Open the Jobs dataset or download `JOBS.csv` / `JOBS.json` from the run's storage. Your first export is ready.

**US$0.05 per completed watchlist of 1–20 boards**, including run processing. Valid empty or unchanged completed checks also count. This is an independent tool, with no affiliation to Airbnb or Greenhouse.

### What you receive

- Current job listings with board, title, location and original job URL.
- CSV/JSON exports for a job feed, spreadsheet, recruiting workflow or hiring-data research.
- Coverage information in `SUMMARY`.
- A change log when comparing observations; setup is described below.

This Greenhouse job scraper checks the company careers pages you select. It does not discover companies, access applicant records or submit applications. If you already handle exports and history yourself, Greenhouse's public API may be sufficient.

### Advanced: tracking changes

Each run checks your boards once. To compare against the last check, save the full `SNAPSHOT` output and pass it as `previousSnapshot` with the same board list on your next run. History and alerts are not managed automatically by this Actor.

The first successful observation is a baseline. A `new` event means first observed, rather than proof that a role was just published. A `noLongerListed` event requires two valid absences and does not prove that a role was filled.

#### Example change sequence

Illustrative sequence below; these are explanations, not live job records or customer results. Every comparison passes the last complete snapshot for the same board list.

| Observation | What the board shows | Output interpretation |
|---|---|---|
| First check | Posts A and B | Baseline. Both have `new` events with `baseline: true`; neither is claimed to be newly published. |
| Second check | A, B and a first-observed post C | C has `new` with `baseline: false`. A and B generate no events if unchanged. |
| Third check | A and C; B absent | B is missing once. No confirmed-disappearance event yet. |
| Fourth check | A and C; B still absent | B has `noLongerListed`; this does not establish whether the role was filled. |

#### Input

| Field | Required | Meaning |
|---|---|---|
| `boardSlugs` | Yes | Between 1 and 20 Greenhouse board identifiers. Supply identifiers, not entire careers-page URLs. |
| `previousSnapshot` | No | The full JSON object returned as `SNAPSHOT` by the previous run in this watchlist sequence. Omit it to start a new baseline. |

For a board URL such as `https://job-boards.greenhouse.io/your-company`, the identifier is `your-company`. Replace the placeholder below with a real public board identifier:

```json
{
  "boardSlugs": ["your-company"]
}
```

For a later run, use the same board set and add `previousSnapshot` with the complete downloaded object. Do not substitute a run ID, URL, path, `null` or abbreviated object. Do not edit timestamps or job entries in the snapshot. A new watchlist needs a new baseline. Snapshots are caller-supplied, unsigned data: validation checks structure and consistency, not authenticity or origin.

#### Outputs

| Output | Contents |
|---|---|
| Default dataset | Jobs successfully observed in this run. |
| `JOBS.json` / `JOBS.csv` | Current-job exports. |
| `CHANGES.json` / `CHANGES.csv` | Observed changes relative to the supplied snapshot. |
| `SNAPSHOT` | History to pass into the next run. |
| `SUMMARY` | Coverage and processing summary. Require run status `SUCCEEDED` and `SUMMARY.complete: true` before consuming any output. |
| `BILLING` | When paid pricing is enabled, the completion-charge acknowledgement. An `unknown` or `pending` status needs investigation before any further payment action. |

Current-job rows contain:

| Field | Meaning |
|---|---|
| `board` | Board identifier supplied in the input. |
| `jobKey` | Stable key combining the board and job-post identifier. |
| `id` | Job-post identifier from Greenhouse. |
| `title` | Job title supplied by the board. |
| `location` | Location text supplied by the board; not a verified work-eligibility or remote-work classification. |
| `url` | Link to the job post. |
| `sourceUrl` | Source used to obtain the listing. |
| `sourceUpdatedAt` | Valid update timestamp required from the source. It is not interpreted as a publication date; a missing or invalid value rejects the board response. |
| `observedAt` | When this run observed the listing. |

Change rows add `eventId`, `eventType`, `baseline`, `firstSeenAt`, `lastSeenAt`, `changedFields` and `previous`. The `previous` value contains the earlier row when relevant; `changedFields` lists the tracked fields that differ.

#### Reading the change log

| `eventType` | Interpretation |
|---|---|
| `new`, `baseline: true` | First valid observation for this board. These jobs may have existed before monitoring began. |
| `new`, `baseline: false` | A job-post identifier first observed after this board's baseline. |
| `changed` | Tracked fields differ from the earlier observation. |
| `noLongerListed` | A previously observed post was absent from two valid observations. This does not establish why it disappeared or whether the role was filled. |
| `reappeared` | A post marked no longer listed appeared again. If its fields also changed, the same event includes those changes. |

The first successful run produces a baseline and `new` events with `baseline: true`. An unchanged valid observation produces no change events for that board. If the first run fails, start a fresh run without adopting its output as a baseline.

For `noLongerListed`, the job details are the last known listing facts. `observedAt` is the time the absence was confirmed; `lastSeenAt` is the last observation in which the post was present. None of these timestamps establishes the original publication or closing time.

#### Repeated monitoring

Each run is a single check. To monitor repeatedly, your workflow must retrieve `SNAPSHOT`, store it durably, then supply it as `previousSnapshot` to the next run. Require both run status `SUCCEEDED` and `SUMMARY.complete: true` before replacing your saved snapshot.

Run one sequence at a time for a watchlist. Concurrent runs from the same previous snapshot create separate branches of history. A schedule that repeatedly submits a fixed input does not advance the snapshot automatically. The Actor does not send email, WhatsApp, Slack or other notifications.

### Coverage and failure handling

The Actor requires every requested board to be fully verified. If any board fails or returns an invalid/incomplete response, the whole run is rejected before a new dataset or snapshot is published. Keep your last successful snapshot. An error is not treated as an empty board or a missing job.

Storage writes across files and the dataset are not a single platform transaction. If a run is interrupted during output writing, partial files may remain. Disregard them unless the run is `SUCCEEDED` and `SUMMARY.complete` is `true`. Start a **new** run with the last valid snapshot; do not resurrect an interrupted partial run. The adapter rejects resuming an incomplete write sequence.

The Actor supports a bounded history rather than an unlimited archive. It accepts at most 10,000 historical jobs globally and 5 MiB of total JSON input, including `boardSlugs` and `previousSnapshot`. It also checks that the next snapshot fits a future input. These limits apply across the whole watchlist, not separately to each board. It does not silently trim history to fit. An invalid or incompatible snapshot must be corrected, or a new baseline started deliberately. Download any exports you need to keep first.

Use the supplied `JOBS.csv` and `CHANGES.csv` for spreadsheets: they escape cells that could otherwise be interpreted as formulas. JSON retains factual text. A CSV exported separately from the default dataset does not inherit this file-specific escaping.

### Limitations

- Greenhouse public boards only; no Lever, Ashby, Workday or private boards.
- Only the board identifiers you supply; no global job search.
- Tracked listing metadata only; no full job-description history, salary inference, applicant data or hiring-intent scoring.
- Changes are observed between checks. Brief changes that occur and revert between checks may be missed.
- Upstream errors or changes to the source API can affect coverage.
- A disappeared post does not prove a filled, canceled or closed role.
- No built-in scheduler, cross-run account storage, notification service or application submission.

### Pricing and completed checks

The price is **US$0.05 per completed run covering the full list of 1–20 boards**. Check the Store's Pricing tab for the current price before you run the Actor.

The charge is one `watchlist-completed` event after every requested board and all outputs have been verified. A successful empty or unchanged watchlist still counts as a completed check. Failed collection or incomplete storage does not issue this event. The price covers the list, not each board or each job; it includes no separate Actor start or dataset-item fee.

The run's maximum charge must cover one completion event. A restart of an already completed run does not repeat collection or charging. If the payment acknowledgement is uncertain, the Actor preserves a `BILLING` marker and does not retry; contact the developer through the Actor's issue channel with the run ID before taking further payment action. Apify storage retention and any separate platform operations after the run are governed by your Apify plan and platform pricing.

### Source and permitted use

Data comes from the [Greenhouse Job Board API](https://docs.greenhouse.io/job-board.html). Its GET endpoints expose public board data without authentication. Use results consistently with the source and job publisher's applicable terms and rights. Public availability should not be treated as a blanket license to republish third-party content. This is an independent tool and is not endorsed by Greenhouse.

### Reporting a problem

Include the run ID, the affected board identifier, the expected result and the observed result. If history is relevant, provide a minimal reproducible example through an appropriate private channel. Never post an Apify token, account credentials or private workflow data in a public issue.

# Actor input Schema

## `boardSlugs` (type: `array`):

Use the board identifier from the company's Greenhouse careers URL, for example airbnb. Supply 1–20 different slugs, not URLs. Only public Greenhouse boards are supported.

## `previousSnapshot` (type: `object`):

Paste the SNAPSHOT JSON from your last successful run, or have your automation pass it here. Keep the same board list. Leave this field absent for a fresh baseline. Reusing an older snapshot creates a separate comparison, not an accumulated history. Maximum total input: 5 MiB.

## Actor input object example

```json
{
  "boardSlugs": [
    "airbnb"
  ]
}
```

# Actor output Schema

## `jobs` (type: `string`):

No description

## `jobsCsv` (type: `string`):

No description

## `changes` (type: `string`):

No description

## `changesCsv` (type: `string`):

No description

## `snapshot` (type: `string`):

No description

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

No description

# 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 = {
    "boardSlugs": [
        "airbnb"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("downy_sublimity/greenhouse-watchlist-export").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 = { "boardSlugs": ["airbnb"] }

# Run the Actor and wait for it to finish
run = client.actor("downy_sublimity/greenhouse-watchlist-export").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 '{
  "boardSlugs": [
    "airbnb"
  ]
}' |
apify call downy_sublimity/greenhouse-watchlist-export --silent --output-dataset

```

## MCP server setup

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

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/hKiF2epI17OkpTRdh/builds/rvb9Zf9KkXJnXdMYL/openapi.json
