# Greenhouse Job Change Tracker (`sharp_remnant/greenhouse-job-change-tracker`) Actor

Track job description, salary-text and location edits with before/after evidence. Confirm removals across complete snapshots and retain state on source failures.

- **URL**: https://apify.com/sharp\_remnant/greenhouse-job-change-tracker.md
- **Developed by:** [Morgan Crozier](https://apify.com/sharp_remnant) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$50.00 / 1,000 successful board checks

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 Job Change Tracker

See what changed in a Greenhouse job posting: description text, title, location, department, office or application URL. Track known employers over repeated runs and export structured evidence for a recruiting workflow or job board.

Description changes include salary, requirements and remote-work wording when those details appear in the source. This Actor reports text differences; it does not classify compensation or infer company hiring intent.

### Try it

Run the default synthetic demo first:

```json
{ "mode": "demo" }
```

The demo emits six example job rows, including a salary/policy edit, a new posting and a confirmed absence. Every row is marked `isDemo: true`. It makes no source requests, changes no watch state and incurs no Actor event charge.

For live tracking:

```json
{
  "mode": "live",
  "boards": ["airbnb", "stripe"],
  "watchName": "hiring-watch",
  "outputMode": "changes"
}
```

Use the employer's Greenhouse board token, typically the name in `boards.greenhouse.io/TOKEN` or `job-boards.greenhouse.io/TOKEN`. Custom careers sites may not expose the token. This Actor does not discover it. Supply 1–10 tokens, not URLs.

Save an Apify task and schedule repeated runs yourself, for example daily. Keep the same watch name and board list. A different list requires a new watch name and starts a new baseline. Schedule at least an hour apart; avoid overlapping runs. The Actor runs once per invocation and does not schedule itself.

### Price

**$0.05 per successfully checked board ($50 per 1,000), with no start or per-job event fee.** One complete empty or unchanged board still counts as a successful check. Ten boards checked daily for 30 days cost $15 in Actor event charges. Failed boards and the synthetic demo have no Actor event charge. Export, storage, integration or account costs may apply under your Apify plan. A successful source result is delivered before its event charge; failed checkpoint persistence can still follow a charge, as explained below.

Set a maximum charge for each live run: $0.05 for one board, $0.50 for ten. Once the remaining event budget cannot cover another board, remaining boards are skipped and reported. Billing is in whole board checks, not job rows. Local execution simulates billing without charging money.

### Read the output

The default dataset contains two types of row. Filter `recordType: job` for job events; always inspect `recordType: board` for coverage and errors.

| Job status | Meaning |
| --- | --- |
| `baseline` | Present on the first successful observation; not claimed newly posted. |
| `added` | A new job-post ID since the prior complete observation. |
| `changed` | One or more tracked fields changed. |
| `unchanged` | Included only with `outputMode: all`. |
| `missing_pending` | Absent on a complete observation; removal not yet confirmed. |
| `removed` | Absent on a second complete observation at least one hour after the first absence. |
| `reappeared` | A previously absent posting returned while retained in watch history. |

Rows include `jobId`, `board`, `title`, `location`, `url`, normalized `description`, `observedAt`, `sourceUpdatedAt`, `changedFields`, `changes`, `eventId` and last-seen information. Description evidence contains removed/added text plus hashes; excerpts over 4,000 characters or expensive diffs are explicitly marked `truncated`. Use JSON for nested evidence; CSV is convenient for flat fields. Markup, whitespace and source-update timestamps alone do not create changes. Link-target-only edits inside descriptions are not tracked.

`OUTPUT` in the run key-value store records successful, failed and skipped boards and whether checkpoints were saved. `ERROR` provides fatal error details. `METRICS` contains local process observations, not a platform invoice. A run with one healthy board and one source failure completes with `status: partial`; inspect the summary even when Apify marks the run succeeded.

### State and reliability

Each actor/watch combination uses an account-local named key-value store and request queue; `OUTPUT` gives their identifiers. Previous job snapshots persist between runs. Source failures, malformed responses, duplicate IDs or a mismatch with `meta.total` cannot overwrite the prior snapshot or trigger removals. “Removed” means repeatedly absent from this API, not proof the employer closed or filled the position. Two coherent but incorrect API responses can still produce a false removal.

Concurrent runs for one watch are rejected using a platform request lock. A crashed run can hold that lock for ten minutes. Different watches are independent. Storage, billing and output are not one transaction: after a failed run, inspect delivered output and charges, then start a **new** run. Do not resurrect old runs. A charge may succeed while its checkpoint fails; the next run can repeat evidence. Use `eventId` to deduplicate retried change events; it is not a universal exactly-once guarantee across fresh baselines or independent watches.

Limits: 1,000 current jobs and 8 MiB per board response; 250,000 source characters per job; 2,000 retained entries and 8 MiB of state per board; 512 MB memory; 240-second hard live runtime. Large boards fail explicitly. Confirmed absences are retained for 30 days of observations; after expiry, a returning ID is treated as added. Time between observations determines detection latency. Empty output is not evidence that a failed board has no jobs.

### Data and support

Reads the unauthenticated public [Greenhouse Job Board API](https://docs.greenhouse.io/job-board.html). No browser, proxies, application submission, candidate data, external AI API or enrichment service. Employer descriptions may contain public contact details. Data stays in your Apify output and named watch stores; it is not sent to a separate analytics service. Runs do not delete old datasets. Remove unneeded run data and both watch storages through Apify after stopping schedules; deleting a watch starts a fresh baseline next time. Verify your right to reuse employer content, especially when republishing descriptions.

Greenhouse only; no historical backfill, multi-ATS discovery, salary parsing or built-in notifications. Not affiliated with Greenhouse or monitored employers. Report failures through the Actor's Issues tab with a run ID and board token, omitting credentials and confidential information.

### Local development

Use Node 22 or later and Bun. Run `bun install --frozen-lockfile`, `bun run test` and `bun run demo`. For an SDK demo, run `bun start`. Local live input goes in `storage/key_value_stores/default/INPUT.json`; repeated runs retain named watch state. `bun run benchmark` performs public API reads for Airbnb and Stripe. `bun run verify:container` needs the local Docker image built from `.actor/Dockerfile`; it performs public API reads as well. `bun run stress` uses synthetic data only.

# Changelog

This Actor's version history is a separate document: https://apify.com/sharp\_remnant/greenhouse-job-change-tracker/changelog.md

# Actor input Schema

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

Demo uses synthetic data, is free, and does not change a watch. Choose live to check your boards.

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

1–10 board tokens in live mode, such as airbnb or stripe. Use a new watch name if you change this list.

## `watchName` (type: `string`):

Repeated runs with the same name share snapshots in your account. Use a distinct name for each board list.

## `outputMode` (type: `string`):

Changes emits all jobs at baseline, then changes and board coverage summaries. All also emits unchanged jobs. Both cost the same per checked board.

## Actor input object example

```json
{
  "mode": "demo",
  "watchName": "default",
  "outputMode": "changes"
}
```

# Actor output Schema

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

Job rows plus board rows. Filter recordType=job for jobs; inspect board error rows before interpreting absence.

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

Completed, failed and skipped boards, checkpoints and billing mode. Available for completed live and demo runs.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("sharp_remnant/greenhouse-job-change-tracker").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("sharp_remnant/greenhouse-job-change-tracker").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 '{}' |
apify call sharp_remnant/greenhouse-job-change-tracker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sharp_remnant/greenhouse-job-change-tracker"
        }
    }
}
```

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/sLvj02gb8xlRCtb9P/builds/40F729aClO27sHsyA/openapi.json
