# Greenhouse, Lever & Ashby Jobs Scraper + Change Monitor (`eiaserinnys/greenhouse-job-change-monitor`) Actor

Export jobs and track new, updated and closed postings across Greenhouse, Lever and Ashby. Get CSV changes, before/after fields and saved history for daily hiring monitoring.

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

## Pricing

from $3.50 / 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, Lever & Ashby Jobs Scraper + Change Monitor

**See which jobs appeared, changed or closed since your last check.** Export current job listings and a downloadable changes CSV from Greenhouse, Lever (global/EU), and Ashby. Saved history makes the next run a comparison automatically.

Use it to keep a niche job feed current, track hiring at target companies, or review new roles before recruiting research. Updated events include the exact field values before and after an edit. Only public job postings are collected; no applicant data or ATS login is needed.

**Current price, before 2026-10-19 04:40:54.985 UTC:** $0.001 per successful complete board check, plus a $0.00005 start event at the default 256 MB. Current-job rows are included.

**Scheduled price, from 2026-10-19 04:40:54.985 UTC:** $0.0035 per successful complete board check + $0.00001 per current-job row exported to the default dataset + the unchanged $0.00005 start event. Every full export includes unchanged jobs in its row count. With `writeCurrentJobs:false`, no dataset-row charge applies. Failed/incomplete or budget-skipped boards have no board-check charge; the start event still applies. Check the Pricing tab for the active price used by your run.

### Try one real board

1. Click **Try for free** / **Start** in Apify and keep the prefilled Brave board, or replace it with one company you follow.
2. Keep **Remember previous results** enabled and start the run. An Apify account and available credit are required; the Actor is pay per event.
3. Open **Current jobs** to inspect job titles, locations and links. Open **Download changes CSV** for a spreadsheet, or **Change events** for JSON.
4. Run the same input again later. New job IDs produce `added`, edited fields produce `updated`, and a missing job produces `closed` after two successful checks find it absent.

```json
{
  "boards": ["https://boards.greenhouse.io/brave"],
  "persistState": true
}
```

The first run establishes a baseline: existing jobs appear as `added`. This does not mean they were posted today. A later unchanged run returns an empty changes list and a CSV with headers only; it still performs and charges for a successful board check.

### Small real output sample

These two rows were collected from Brave on **2026-10-04 at 17:59 UTC** during an owner test. This is a historical excerpt, not a promise that these roles are still open. It is not customer usage evidence.

| Job | Location | Stable ID |
| --- | --- | --- |
| [Senior DevOps Engineer (browser CI/CD + AWS)](https://job-boards.greenhouse.io/brave/jobs/1287724) | Anywhere | `greenhouse:brave:1287724` |
| [Product Marketing Manager - Brave Search APIs](https://job-boards.greenhouse.io/brave/jobs/7603697) | USA and EU | `greenhouse:brave:7603697` |

### What you receive

| Output | Contents | Use it for |
| --- | --- | --- |
| **Current jobs** (`jobs`) | Default dataset: title, location, job URL, description, source board, `stableJobId`, check time and provider-specific fields | JSON/CSV export; database upserts and job feeds |
| **Download changes CSV** (`changesCsv`) | `CHANGES.csv`: event type, job title/link, stable ID, changed field, before and after values | Reviewing changes in Excel or importing into a spreadsheet |
| **Change events** (`changes`) | `EVENTS`: `added`, `updated`, `closed`, with `eventId` and field-level before/after values | n8n/Make routing and downstream deduplication |
| **Run summary** (`summary`) | `OUTPUT`: per-board status, errors, events and carry-over snapshots | Checking whether each source completed successfully |

CSV has one row per added/closed job and one row per changed field on updates. Arrays and objects are JSON within the cell. Spreadsheet formula-like text is prefixed with an apostrophe; the JSON event retains the original value. The CSV is written at the end of a normally completed run; use `OUTPUT`/`EVENTS` to inspect partial results after an interruption.

### Greenhouse fields and meaningful changes

When present in the board-list response, Greenhouse `first_published`, `requisition_id`, `metadata`, and `application_deadline` are preserved as `firstPublishedAt`, `requisitionId`, `metadata`, and `applicationDeadline`. Missing fields stay absent; the Actor does not make extra per-job detail requests or infer deadlines. The listing API does not guarantee every detail field for every board.

Greenhouse description transport escaping is decoded once; already-HTML content retains its inner entities. Continue sanitizing employer HTML before displaying it. `updatedAt` is retained in job records but timestamp-only changes do not produce `updated` events. Title, description, location, deadlines and other content changes still do. Existing saved snapshots are migrated on the next successful check without treating newly available fields or HTML decoding alone as an employer edit.

### Lower-write change monitoring (optional)

Set **Export all current jobs** (`writeCurrentJobs`) to `false` if you only consume changes. This skips current-job dataset writes, while `EVENTS`, `CHANGES.csv`, `OUTPUT`, comparison and saved history continue. `OUTPUT.datasetMode` reports `omitted`; the current-jobs dataset is empty in this mode. Default `true` preserves the full dataset contract. All boards are still fetched and checked completely; customer board-check prices are unchanged. This reduces dataset-write costs, not all platform costs or the price charged to the user.

```json
{
  "boards": ["https://boards.greenhouse.io/brave"],
  "persistState": true,
  "monitorId": "daily-hiring-watch",
  "writeCurrentJobs": false
}
```

### Supported boards

Paste **1–10 hosted company board URLs** in one run. You supply the companies; this Actor does not discover all companies or search the entire web.

| Provider | Supported input |
| --- | --- |
| Greenhouse | `https://boards.greenhouse.io/brave` or a `job-boards.greenhouse.io` company URL |
| Lever | Your company's `https://jobs.lever.co/` board URL |
| Lever EU | Your company's `https://jobs.eu.lever.co/` board URL |
| Ashby | Your company's `https://jobs.ashbyhq.com/` board URL |

Hosted job-detail URLs are also accepted. Custom company career domains, Workday, LinkedIn and Indeed are not supported. Lever pages are fully collected before comparison. Failed or incomplete responses preserve saved history and cannot close jobs.

### Repeat daily with the same history

Save an Apify **Task** with the input below, substituting your own board URLs. Keep the same monitor name and board set on every run. For a different board set, create a different monitor name.

```json
{
  "boards": ["https://boards.greenhouse.io/brave"],
  "persistState": true,
  "monitorId": "daily-hiring-watch"
}
```

Create a Schedule for that Task: **daily at 09:00 UTC**, cron **`0 9 * * *`**, timezone **`Etc/UTC`**. Run each monitor sequentially and avoid overlapping manual runs. This is a configuration example; using this Actor does not create a schedule automatically.

For n8n or Make: trigger your workflow on Apify run success, read that run's `EVENTS`, route on `type`, and deduplicate by `eventId`. Upsert current jobs by `stableJobId`. Check `OUTPUT.results` as a successful run may still contain failed boards. Delivery to Slack, email or another destination belongs in your workflow; the Actor itself sends no messages.

### Spending limits and complete output

The Actor checks the remaining budget before fetching a new board, then checks the exact cost of its complete export before writing any rows. It uses the run's actual pricing, including a dataset-row event if one is configured; the Pricing tab determines the active price used by that run, including during the announced notice period. A board that does not fit is `skipped` with `reason: "spending-limit"`: no rows, new events or history advancement for that board. Earlier complete boards remain available. No board is silently shortened to fit a budget.

Check `OUTPUT.complete` as well as the run status. It is `true` only when every requested board has a complete result; a normally finished run can contain skipped or failed boards. Same-run resurrection preserves committed events and suppresses repeated row exports and board charges. If a dataset write has an uncertain outcome before a checkpoint is committed, the same run stops instead of retrying that export. Start a new run and inspect the failed run's partial dataset separately.

Large changes are checked before export: 20 MiB for OUTPUT, 10 MiB each for EVENTS and CHANGES.csv, and a conservative 256 MiB checkpoint-write allowance per execution attempt. The existing 10 MiB snapshot limit remains. When an output would exceed these limits, that whole board and the remaining new boards are skipped with `reason: "output-limit"`; existing history is retained. Split large batches. A single board whose complete output exceeds a limit cannot be processed by reducing the batch alone. There is no silent field, description, change or row truncation. These limits can reject large workloads accepted by older releases.

Per-board history checkpoints remain durable before board charging. Identical KV records and the redundant final checkpoint are not rewritten; a small export guard is stored before each dataset export. Storage, charging and dataset writes are still not one atomic transaction, so a failure can leave complete work uncharged or an explicitly failed run with partial rows.

### Pricing and limits

Before **2026-10-19 04:40:54.985 UTC**, each initial or unchanged successful board check remains $0.001, with job rows included. Ten boards once daily for 30 days at this rate cost $0.3015 in event charges, including 30 starts.

From that effective time, the approved scheduled prices are:

| Event | Price | When it applies |
| --- | --- | --- |
| Successful board check (`board-sync`) | $0.0035 | Each complete baseline or repeat check, including unchanged or empty boards |
| Exported current job (`apify-default-dataset-item`) | $0.00001 | Every current-job row written to the default dataset; none in omitted-dataset mode |
| Actor start (`apify-actor-start`) | $0.00005 | Once at the default 256 MB |

After the change, one board with 100 exported jobs costs **$0.00455**, and one with 1,000 exported jobs costs **$0.01355**, including one start. One board in omitted-dataset mode costs **$0.00355**, including the start. Change-event JSON, changes CSV and comparison history have no separate event price. Ten boards with 100 jobs each, once daily for 30 days entirely at the new rate, cost **$1.3515** in event charges, assuming every check succeeds.

The start event is charged once up to 1 GB, then once per additional GB. Platform usage during the run is included in the event pricing; post-run storage and API use follow Apify's platform pricing. The scheduled increase preserves a notice period of at least 14 days; existing pricing remains active until the stated effective time. The run's pricing and the Pricing tab are authoritative.

Limits: 10 boards/run, 1,000 jobs/board, 8 MB/response and 10 MB carry-over state. Default memory is 256 MB, maximum 512 MB. No paid proxy or external LLM is used.

Saved history uses an account-owned named key-value store. Storage retention, deleted history, or a changed board set can require a new baseline. `previousSnapshot` can seed a new monitor; saved history takes precedence. Set `persistState:false` only when managing snapshots yourself.

Persisted observation IDs suppress replay charges, but saving and charging are not atomic: an interruption may leave completed work uncharged. Exactly-once delivery/billing and concurrent monitor writes are not guaranteed. Replay protection retains the latest 100 observation IDs; do not resurrect old runs after newer observations. Sanitize employer-provided description HTML before displaying it.

# Actor input Schema

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

Paste official Greenhouse, Lever (global/EU), or Ashby board or job URLs; the provider is detected automatically. Try the Brave example, then replace it with your companies. No ATS login or API key is required.

## `persistState` (type: `boolean`):

Automatically save and load history between runs. Run each monitor sequentially; overlapping runs are not supported.

## `monitorId` (type: `string`):

Optional name for an independent monitor, such as daily-hiring-watch. Reuse this name and the same boards on a daily schedule. Leave empty to identify history by board set.

## `previousSnapshot` (type: `array`):

Optional migration seed for a new monitor. Existing saved monitor state takes priority. In stateless mode, pass the preceding OUTPUT snapshot manually.

## `writeCurrentJobs` (type: `boolean`):

Keep enabled for the full current-jobs dataset. Disable only for change monitoring: EVENTS, CHANGES.csv, summary and saved history still work; the dataset is empty. Board-check pricing is unchanged.

## Actor input object example

```json
{
  "boards": [
    "https://boards.greenhouse.io/brave"
  ],
  "persistState": true,
  "previousSnapshot": [],
  "writeCurrentJobs": true
}
```

# Actor output Schema

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

No description

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

No description

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

No description

## `changesCsv` (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 = {
    "boards": [
        "https://boards.greenhouse.io/brave"
    ]
};

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

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

```

## MCP server setup

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