# Hiring Change Monitor — Track New & Removed Jobs (`coregent/hiring-change-monitor`) Actor

Monitor public company career pages and ATS boards (Greenhouse, Lever, Ashby, Workday-style sites) for hiring changes. Detect new, removed, reopened, and changed job posts as clean, CSV-friendly hiring-signal events.

- **URL**: https://apify.com/coregent/hiring-change-monitor.md
- **Developed by:** [Delowar Munna](https://apify.com/coregent) (community)
- **Categories:** Jobs, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, NaN bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.80 / 1,000 hiring-change-results

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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

## Hiring Change Monitor — Track New & Removed Jobs

![Hiring Change Monitor](https://raw.githubusercontent.com/coregentdevspace/hiring-change-monitor-assets/main/thumbnail-hiring-change-monitor.png)

Track public company **career pages and ATS boards** and see **what changed** between runs — every job tagged **new**, **removed**, **reopened**, **changed**, or **unchanged** — from **Greenhouse**, **Lever**, **Ashby**, Workday-style sites, and generic career pages. Clean, flat, CSV-ready rows with a transparent **hiring-signal score**. Built for **sales, recruiting, staffing, investors, and market-intelligence teams**.

**No login, no cookies, no paid data APIs.** The actor uses each platform's **free public endpoints** over HTTP and compares each run against a stored snapshot.

By default (**Output mode = All current jobs**) every run returns the full current list of jobs, each tagged with its `change_type`, plus any jobs that were removed — so output is never empty. Switch to **Changes only** to return just the deltas (new/removed/reopened/changed) for lean scheduled monitoring.

### ✨ Why this monitor

- **Knows what changed** — every row carries a `change_type` (`new` / `removed` / `reopened` / `changed` / `unchanged`) and reason tags, so you see hiring momentum at a glance.
- **Two output modes** — _All current jobs_ (full list each run, tagged) or _Changes only_ (deltas vs the previous run). Pick per use case.
- **Multi-source** — Greenhouse, Lever, and Ashby public JSON APIs are first-class; Workday-style sites and generic career pages are supported best-effort, including lightweight `companyDomains` discovery.
- **Scheduled-run friendly** — set a `monitorId`, schedule the actor, and each run compares against the previous snapshot stored in the Apify key-value store.
- **38 flat fields** — change metadata, company/source, job details, compensation (when public), snapshot timing, and previous values for changed jobs. Drops straight into Sheets/Excel/CRMs.
- **Transparent hiring-signal score** — rule-based (no AI), explained below.

---

### 🧭 Output modes & first-run behavior

**Output mode** controls what each run returns:

- **All current jobs (default)** — returns every job currently on the board, each tagged with its `change_type` (`new` / `changed` / `reopened` / `unchanged`), plus jobs that were `removed` since the last run. Output is never empty, and you can always see the full picture with the changes highlighted.
- **Changes only** — returns just the differences versus the previous run (`new` / `removed` / `reopened` / `changed`) and suppresses `unchanged` jobs. Leaner output, ideal for scheduled alerting.

**First run:** for a given `monitorId` there is no prior snapshot, so **every current job is reported as `new`** and `is_first_snapshot` is `true`. The actor stores a snapshot at the end of the run. Reuse the same `monitorId` across scheduled runs so each run compares against the last.

---

### 🚀 Quick start — sample inputs

#### Example 1 — watch a set of ATS boards (full list each run)

```json
{
    "startUrls": [
        "https://boards.greenhouse.io/stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/notion"
    ],
    "monitorId": "core-saas-watchlist",
    "outputMode": "all",
    "maxChanges": 250,
    "keywordFilter": ["data", "security", "enterprise", "ai"],
    "excludeKeywordFilter": ["intern"],
    "includeCompensation": true,
    "proxyConfiguration": { "useApifyProxy": true }
}
````

#### Example 2 — changes-only alerting + domain discovery + your own proxy

```json
{
    "companyDomains": ["canva.com"],
    "startUrls": ["https://jobs.ashbyhq.com/ramp"],
    "monitorId": "fintech-watch",
    "outputMode": "changes",
    "remoteOnly": true,
    "departmentFilter": ["engineering", "product"],
    "proxyConfiguration": {
        "useApifyProxy": false,
        "proxyUrls": ["http://user:pass@proxy.iproyal.com:12321"]
    }
}
```

> Provide **at least one** of `startUrls` or `companyDomains`. Leave `monitorId` empty to derive a stable one from your inputs, or set your own so scheduled runs share a snapshot.

> The actor blocks Apify Residential proxy; if you need residential routing, supply your own provider via `proxyConfiguration.proxyUrls` as shown. See **🚦 Proxy policy** below.

***

### 📦 Output

The dataset has one view: **Hiring change events** — a 38-column flat table.

![Hiring Change Monitor — Hiring change events table view](https://raw.githubusercontent.com/coregentdevspace/hiring-change-monitor-assets/main/hiring-change-monitor-output-hiring-change-events-table-view.png)

#### Output fields (38)

`monitor_id`, `change_type`, `change_reason_tags`, `hiring_signal_score`, `hiring_signal_label`, `company_name`, `company_domain`, `source_type`, `source_url`, `job_id`, `job_fingerprint`, `job_title`, `department`, `location`, `remote_status`, `employment_type`, `seniority`, `job_url`, `apply_url`, `description_text`, `compensation_min`, `compensation_max`, `compensation_currency`, `compensation_period`, `posted_at`, `first_seen_at`, `last_seen_at`, `previous_job_title`, `previous_location`, `previous_department`, `previous_remote_status`, `previous_compensation_min`, `previous_compensation_max`, `snapshot_previous_at`, `snapshot_current_at`, `is_first_snapshot`, `input_value`, `scraped_at`.

#### Sample record — Hiring change event

Real run output (default **All current jobs** mode, on a scheduled re-run). This row is tagged `unchanged` with `snapshot_previous_at` filled in — on the **first** run the same job would be `new` with `is_first_snapshot: true`. The `description_text` is truncated here for readability.

```json
{
    "monitor_id": "core-saas-watchlist",
    "change_type": "unchanged",
    "change_reason_tags": "unchanged|senior_role|technical_role|public_compensation|remote_role",
    "hiring_signal_score": 60,
    "hiring_signal_label": "high",
    "company_name": "Databricks",
    "company_domain": "databricks.com",
    "source_type": "greenhouse",
    "source_url": "https://boards.greenhouse.io/databricks",
    "job_id": "8367021002",
    "job_fingerprint": "greenhouse:8367021002",
    "job_title": "Staff Backend Software Engineer- (AI Platform)",
    "department": "Engineering",
    "location": "San Francisco, California",
    "remote_status": "remote",
    "employment_type": "",
    "seniority": "lead",
    "job_url": "https://databricks.com/company/careers/open-positions/job?gh_jid=8367021002",
    "apply_url": "https://databricks.com/company/careers/open-positions/job?gh_jid=8367021002",
    "description_text": "At Databricks, we are passionate about enabling data teams to solve the world's toughest problems — from making the next mode of transportation a reality to...",
    "compensation_min": 192000,
    "compensation_max": 260000,
    "compensation_currency": "USD",
    "compensation_period": "year",
    "posted_at": "2026-04-09T20:51:23.000Z",
    "first_seen_at": "2026-06-05T01:34:38.356Z",
    "last_seen_at": "2026-06-05T04:19:54.506Z",
    "previous_job_title": "",
    "previous_location": "",
    "previous_department": "",
    "previous_remote_status": "",
    "previous_compensation_min": null,
    "previous_compensation_max": null,
    "snapshot_previous_at": "2026-06-05T04:18:32.768Z",
    "snapshot_current_at": "2026-06-05T04:19:54.506Z",
    "is_first_snapshot": false,
    "input_value": "https://boards.greenhouse.io/databricks",
    "scraped_at": "2026-06-05T04:19:54.506Z"
}
```

For a `new` job, `change_type` is `new` and `change_reason_tags` starts with `new_job`. For a `changed` job, the relevant `previous_*` fields are populated (e.g. `previous_job_title`) and `change_reason_tags` includes tags like `title_changed`, `location_changed`, `department_changed`, `remote_status_changed`, `description_changed`, or `compensation_changed`.

***

### 🎯 Hiring-signal score

Transparent rule-based score (0–100) computed from visible fields — no AI, no external enrichment.

**Base score by change type:** `reopened` 65 · `new` 55 · `removed` 40 · `changed` 35 · `unchanged` 5.

| Modifier                                                           | Points |
| ------------------------------------------------------------------ | -----: |
| Senior / lead / manager / director / executive role                |    +15 |
| Revenue/growth department (sales, marketing, partnerships, CS)     |    +10 |
| Technical department (engineering, data, product, security, AI)    |    +10 |
| Public compensation present                                        |    +10 |
| Remote / hybrid                                                    |    +10 |
| Multiple jobs newly opened at the same company this run            |    +10 |
| Expansion-oriented title (founding, launch, growth, enterprise, …) |    +10 |
| Internship / temporary role                                        |    −10 |

Score is floored at 0 and capped at 100.

**Labels**: `very_high` (80–100) · `high` (60–79) · `medium` (30–59) · `low` (0–29).

`change_reason_tags` is a pipe-separated list combining the change reasons and the signal reasons — e.g. `new_job|senior_role|technical_role|remote_role|public_compensation`.

***

### 💰 Pricing

**Pay-Per-Event**. One flat event per **saved record** (final per-event price is configured on the Apify console):

| Event                  | Charged when                                                                      |
| ---------------------- | --------------------------------------------------------------------------------- |
| `hiring-change-result` | Once per row that passed all filters and was successfully written to the dataset. |

So your bill is simply `results_saved × price_per_event`. **Choose your mode to control cost:**

- **All current jobs** (default) — you're charged for **every job each run** (including `unchanged` ones), because the full current list is saved. Best when you always want the complete, change-tagged picture.
- **Changes only** — you're charged **only for the deltas** (new/removed/reopened/changed). A run with no changes saves and charges nothing. Best for lean scheduled monitoring.

The actor honors the user-configured per-run spending cap (Apify `eventChargeLimitReached`): it caps how many rows it will save up-front to what the limit can pay for, and stops cleanly the moment the cap is reached.

Not charged in either mode:

- Duplicates (deduplicated by source+`job_id`, canonical URL, then title/company/location keys).
- Rows filtered out by keyword / department / location / remote / change-type filters.
- `unchanged` jobs when **Changes only** mode is selected.
- Failed source fetches, invalid inputs, and snapshot storage.

#### 🚦 Proxy policy

Use **Apify Datacenter** proxy or **no proxy** for normal runs — both work reliably for public ATS APIs and career pages at this actor's conservative concurrency.

**Apify Residential proxy is not supported.** The actor will fail at startup if `proxyConfiguration.apifyProxyGroups` includes `RESIDENTIAL`. Reason: in pay-per-event actors, residential bandwidth (~/GB) is billed to the developer, not the run user, so a single bandwidth-heavy run could exceed the per-result event revenue.

If you genuinely need residential routing, supply your own residential provider via the proxy editor's **Custom proxy URLs** field — that traffic goes through your provider, not Apify, and is unaffected:

```
http://user:pass@proxy.iproyal.com:12321
http://user:pass@proxy.brightdata.com:22225
http://user:pass@proxy.oxylabs.io:7777
```

***

### 📊 Run summary

After each run, a `RUN_SUMMARY` entry is written to the default key-value store:

```json
{
    "monitor_id": "core-saas-watchlist",
    "inputs_total": 3,
    "successful_inputs": 3,
    "failed_inputs": 0,
    "sources_detected": { "greenhouse": 1, "lever": 1, "ashby": 1 },
    "raw_results_found": 1260,
    "current_jobs_processed": 1000,
    "changes_found": 118,
    "results_saved": 100,
    "new_jobs": 72,
    "removed_jobs": 18,
    "reopened_jobs": 4,
    "changed_jobs": 24,
    "unchanged_jobs": 882,
    "duplicates_removed": 37,
    "filtered_out": 18,
    "charged_events": 100,
    "blocked_requests": 0,
    "retry_count": 9,
    "snapshot_previous_at": "2026-06-01T10:00:00.000Z",
    "snapshot_current_at": "2026-06-02T10:00:00.000Z",
    "is_first_snapshot": false,
    "runtime_seconds": 184,
    "scraped_at": "2026-06-02T10:03:04.000Z"
}
```

`charged_events` equals the number of successfully saved records. Snapshots and the cross-run history index are stored in a **named** key-value store (`hiring-change-monitor-state`) so they survive between scheduled runs.

***

### ⚙️ Filters

| Filter                                   | Effect                                                                                                 |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `outputMode`                             | `all` (every current job, tagged — default) or `changes` (deltas only, `unchanged` suppressed).        |
| `changeTypes`                            | Optional. Restrict output to specific change types. Empty = use the Output mode default.               |
| `keywordFilter` / `excludeKeywordFilter` | Case-insensitive match across title, department, location, description, company, tags. Exclusion wins. |
| `departmentFilter`                       | Keep only matching departments/teams. Missing department fails when set.                               |
| `locationFilter`                         | Keep only matching locations. Missing location fails when set.                                         |
| `remoteOnly`                             | Keep only remote/hybrid postings.                                                                      |
| `deduplicate`                            | Drop duplicate jobs across inputs (recommended ON).                                                    |

Filters are applied **after** change detection but **before** any dataset push or event charge.

***

### 🚧 Limitations (V1)

- **Public data only**: no login, cookies, or member-only content; no candidate/applicant data.
- **Core ATS are first-class**: Greenhouse, Lever, and Ashby use stable public JSON APIs. **Workday-style sites and generic career pages are best-effort** — when a public listing cannot be reached, the input is marked failed/unsupported and the run continues with the others.
- **`companyDomains`** does lightweight discovery only (homepage + common careers paths, following links to supported ATS boards). It does not crawl the whole site.
- **Compensation** is included only when the public source exposes it (e.g. Lever salary ranges, Ashby compensation, or amounts visible in the description). No paid enrichment.
- **Reopened** detection relies on a compact history index kept per `monitorId`; a job that returns after being absent is `reopened`, otherwise `new`.
- `maxResults` caps raw current jobs processed; `maxChanges` caps saved records per run (changes in *Changes only* mode, all rows in *All current jobs* mode).
- No webhooks/alerts in V1 — schedule the actor and consume the dataset/API.

***

### ❓ FAQ

**Do I need an account, cookies, or API keys?**
No. The actor only uses each platform's free public endpoints.

**Why is everything `new` on my first run?**
There is no prior snapshot yet, so every current job is reported as `new` and `is_first_snapshot` is `true`. Run again later with the same `monitorId` to see real changes.

**My second run returned nothing / only a few rows — is it broken?**
No. In **Changes only** mode a run with no changes correctly returns nothing (there were no new/removed/reopened/changed jobs since the last run). If you'd rather always get the full current list with each job's status, use the default **All current jobs** mode — every run returns every job tagged `new` / `changed` / `unchanged` / etc.

**Will I be charged for unchanged jobs?**
Only in **All current jobs** mode, where the full list (including `unchanged`) is saved and charged each run. In **Changes only** mode, `unchanged` jobs are suppressed and never charged.

**How do I track the same companies over time?**
Set a stable `monitorId` and schedule the actor. Each run compares against the snapshot from the previous run under that ID.

**What counts as a `changed` job?**
A material change in a tracked visible field — title, location, department, remote status, compensation, or description — for a job that exists in both snapshots. The `previous_*` fields show the old values.

**Can I export to CSV?**
Yes — every field is flat (no nested objects). Use Apify's CSV / Excel export, or call the dataset API with `format=csv`.

***

### 🛠️ Technical notes

- **Stack**: Node.js 22 · Apify SDK 3 · Crawlee `HttpCrawler` · Cheerio (career-page HTML only). No browser.
- **Endpoints**: Greenhouse Job Board API, Lever Postings API, Ashby Job Posting API (all free/public); Workday CxS and career-page HTML best-effort.
- **State**: snapshot + history diff in a named Apify key-value store, namespaced by `monitorId`.
- **Concurrency**: `min=1`, `max=5` (conservative; tune after real runs).
- **Memory**: 1 GB min · 2 GB default · 4 GB max.
- **Proxy**: Apify Proxy enabled by default; custom configs accepted; Apify Residential rejected at startup.

# Actor input Schema

## `startUrls` (type: `array`):

Company career pages and ATS board URLs to monitor — Greenhouse (boards.greenhouse.io/{company}), Lever (jobs.lever.co/{company}), Ashby (jobs.ashbyhq.com/{company}), Workday-style career sites, or generic career pages. Provide at least one of Career-board URLs or Company domains.

## `companyDomains` (type: `array`):

Optional. Company domains (e.g. "canva.com") to discover careers pages for when the exact board URL is unknown. The actor probes the homepage and common careers paths and follows links to supported ATS boards only — no deep crawling.

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

Stable namespace for the snapshot used across scheduled runs. Reuse the same value to keep comparing against prior runs. Leave empty to derive a deterministic ID from your inputs.

## `maxResults` (type: `integer`):

Maximum number of current job records to process across all sources before change comparison. Range 1–10000.

## `maxChanges` (type: `integer`):

Maximum number of records saved to the dataset (and charged) per run. In 'All current jobs' mode this caps the total rows; in 'Changes only' mode it caps the change events. Range 1–10000.

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

What each run returns. 'All current jobs' returns every job currently on the board, each tagged with change\_type (new/changed/reopened/unchanged), plus jobs that were removed since the last run — so output is never empty and you are charged per saved record. 'Changes only' returns just the differences versus the previous run (new/removed/reopened/changed) and suppresses unchanged jobs — leaner and ideal for scheduled monitoring.

## `changeTypes` (type: `array`):

Optionally restrict output to specific change types. Leave empty to use the Output mode default (all types in 'All current jobs' mode; new/removed/reopened/changed in 'Changes only' mode). Unknown values are ignored.

## `keywordFilter` (type: `array`):

Keep only jobs whose title, department, location, description, company, or reason tags contain one of these terms (case-insensitive). Leave empty to keep all.

## `excludeKeywordFilter` (type: `array`):

Exclude jobs matching any of these terms (case-insensitive), for example "intern". Exclusion wins over inclusion.

## `departmentFilter` (type: `array`):

Keep only jobs in matching departments/teams (case-insensitive). A job with no department is dropped when this filter is set.

## `locationFilter` (type: `array`):

Keep only jobs in matching locations (case-insensitive). A job with no location is dropped when this filter is set.

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

Keep only remote or hybrid-remote postings.

## `includeDescriptionText` (type: `boolean`):

Include the normalized plain-text job description. Turning this off makes runs leaner; descriptions are still used for change fingerprinting when cheaply available.

## `includeCompensation` (type: `boolean`):

Include public compensation fields when the source exposes them (e.g. Lever salary ranges, Ashby compensation). No paid enrichment.

## `saveSnapshot` (type: `boolean`):

Store the current snapshot for the next run. Turn off for a dry comparison that does not update stored state.

## `deduplicate` (type: `boolean`):

Remove duplicate jobs across inputs by job ID, canonical URL, then normalized title/company/location/department.

## `proxyConfiguration` (type: `object`):

Apify Proxy configuration. Defaults to Apify Proxy enabled. Apify Residential is NOT supported and will fail the run at startup; if you need residential routing, supply your own provider via Custom proxy URLs (proxyUrls).

## Actor input object example

```json
{
  "startUrls": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.lever.co/airbnb",
    "https://jobs.ashbyhq.com/notion"
  ],
  "companyDomains": [],
  "monitorId": "core-saas-watchlist",
  "maxResults": 1000,
  "maxChanges": 1000,
  "outputMode": "all",
  "changeTypes": [],
  "keywordFilter": [
    "data",
    "security",
    "enterprise",
    "ai"
  ],
  "excludeKeywordFilter": [
    "intern"
  ],
  "departmentFilter": [],
  "locationFilter": [],
  "remoteOnly": false,
  "includeDescriptionText": true,
  "includeCompensation": true,
  "saveSnapshot": true,
  "deduplicate": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

Flat table of every change event pushed to the dataset — change type, reason tags, hiring-signal score, company/source, job details, compensation, snapshot timing, and previous values for changed jobs.

# 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 = {
    "startUrls": [
        "https://boards.greenhouse.io/stripe",
        "https://jobs.lever.co/airbnb",
        "https://jobs.ashbyhq.com/notion"
    ],
    "companyDomains": [],
    "monitorId": "core-saas-watchlist",
    "keywordFilter": [
        "data",
        "security",
        "enterprise",
        "ai"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("coregent/hiring-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 = {
    "startUrls": [
        "https://boards.greenhouse.io/stripe",
        "https://jobs.lever.co/airbnb",
        "https://jobs.ashbyhq.com/notion",
    ],
    "companyDomains": [],
    "monitorId": "core-saas-watchlist",
    "keywordFilter": [
        "data",
        "security",
        "enterprise",
        "ai",
    ],
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("coregent/hiring-change-monitor").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "startUrls": [
    "https://boards.greenhouse.io/stripe",
    "https://jobs.lever.co/airbnb",
    "https://jobs.ashbyhq.com/notion"
  ],
  "companyDomains": [],
  "monitorId": "core-saas-watchlist",
  "keywordFilter": [
    "data",
    "security",
    "enterprise",
    "ai"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call coregent/hiring-change-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=coregent/hiring-change-monitor",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Hiring Change Monitor — Track New & Removed Jobs",
        "description": "Monitor public company career pages and ATS boards (Greenhouse, Lever, Ashby, Workday-style sites) for hiring changes. Detect new, removed, reopened, and changed job posts as clean, CSV-friendly hiring-signal events.",
        "version": "1.0",
        "x-build-id": "RK5iPpkM3a04X4MDa"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/coregent~hiring-change-monitor/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-coregent-hiring-change-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/coregent~hiring-change-monitor/runs": {
            "post": {
                "operationId": "runs-sync-coregent-hiring-change-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/coregent~hiring-change-monitor/run-sync": {
            "post": {
                "operationId": "run-sync-coregent-hiring-change-monitor",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "startUrls": {
                        "title": "Career-board URLs",
                        "type": "array",
                        "description": "Company career pages and ATS board URLs to monitor — Greenhouse (boards.greenhouse.io/{company}), Lever (jobs.lever.co/{company}), Ashby (jobs.ashbyhq.com/{company}), Workday-style career sites, or generic career pages. Provide at least one of Career-board URLs or Company domains.",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "companyDomains": {
                        "title": "Company domains",
                        "type": "array",
                        "description": "Optional. Company domains (e.g. \"canva.com\") to discover careers pages for when the exact board URL is unknown. The actor probes the homepage and common careers paths and follows links to supported ATS boards only — no deep crawling.",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "monitorId": {
                        "title": "Monitor ID",
                        "type": "string",
                        "description": "Stable namespace for the snapshot used across scheduled runs. Reuse the same value to keep comparing against prior runs. Leave empty to derive a deterministic ID from your inputs.",
                        "default": ""
                    },
                    "maxResults": {
                        "title": "Max current jobs",
                        "minimum": 1,
                        "maximum": 10000,
                        "type": "integer",
                        "description": "Maximum number of current job records to process across all sources before change comparison. Range 1–10000.",
                        "default": 1000
                    },
                    "maxChanges": {
                        "title": "Max saved records",
                        "minimum": 1,
                        "maximum": 10000,
                        "type": "integer",
                        "description": "Maximum number of records saved to the dataset (and charged) per run. In 'All current jobs' mode this caps the total rows; in 'Changes only' mode it caps the change events. Range 1–10000.",
                        "default": 1000
                    },
                    "outputMode": {
                        "title": "Output mode",
                        "enum": [
                            "all",
                            "changes"
                        ],
                        "type": "string",
                        "description": "What each run returns. 'All current jobs' returns every job currently on the board, each tagged with change_type (new/changed/reopened/unchanged), plus jobs that were removed since the last run — so output is never empty and you are charged per saved record. 'Changes only' returns just the differences versus the previous run (new/removed/reopened/changed) and suppresses unchanged jobs — leaner and ideal for scheduled monitoring.",
                        "default": "all"
                    },
                    "changeTypes": {
                        "title": "Change types (optional filter)",
                        "type": "array",
                        "description": "Optionally restrict output to specific change types. Leave empty to use the Output mode default (all types in 'All current jobs' mode; new/removed/reopened/changed in 'Changes only' mode). Unknown values are ignored.",
                        "items": {
                            "type": "string",
                            "enum": [
                                "new",
                                "removed",
                                "reopened",
                                "changed",
                                "unchanged"
                            ],
                            "enumTitles": [
                                "New",
                                "Removed",
                                "Reopened",
                                "Changed",
                                "Unchanged"
                            ]
                        },
                        "default": []
                    },
                    "keywordFilter": {
                        "title": "Keyword filter (include)",
                        "type": "array",
                        "description": "Keep only jobs whose title, department, location, description, company, or reason tags contain one of these terms (case-insensitive). Leave empty to keep all.",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "excludeKeywordFilter": {
                        "title": "Keyword filter (exclude)",
                        "type": "array",
                        "description": "Exclude jobs matching any of these terms (case-insensitive), for example \"intern\". Exclusion wins over inclusion.",
                        "default": [
                            "intern"
                        ],
                        "items": {
                            "type": "string"
                        }
                    },
                    "departmentFilter": {
                        "title": "Department filter",
                        "type": "array",
                        "description": "Keep only jobs in matching departments/teams (case-insensitive). A job with no department is dropped when this filter is set.",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "locationFilter": {
                        "title": "Location filter",
                        "type": "array",
                        "description": "Keep only jobs in matching locations (case-insensitive). A job with no location is dropped when this filter is set.",
                        "default": [],
                        "items": {
                            "type": "string"
                        }
                    },
                    "remoteOnly": {
                        "title": "Remote only",
                        "type": "boolean",
                        "description": "Keep only remote or hybrid-remote postings.",
                        "default": false
                    },
                    "includeDescriptionText": {
                        "title": "Include job descriptions",
                        "type": "boolean",
                        "description": "Include the normalized plain-text job description. Turning this off makes runs leaner; descriptions are still used for change fingerprinting when cheaply available.",
                        "default": true
                    },
                    "includeCompensation": {
                        "title": "Include compensation",
                        "type": "boolean",
                        "description": "Include public compensation fields when the source exposes them (e.g. Lever salary ranges, Ashby compensation). No paid enrichment.",
                        "default": true
                    },
                    "saveSnapshot": {
                        "title": "Save snapshot",
                        "type": "boolean",
                        "description": "Store the current snapshot for the next run. Turn off for a dry comparison that does not update stored state.",
                        "default": true
                    },
                    "deduplicate": {
                        "title": "Deduplicate jobs",
                        "type": "boolean",
                        "description": "Remove duplicate jobs across inputs by job ID, canonical URL, then normalized title/company/location/department.",
                        "default": true
                    },
                    "proxyConfiguration": {
                        "title": "Proxy configuration",
                        "type": "object",
                        "description": "Apify Proxy configuration. Defaults to Apify Proxy enabled. Apify Residential is NOT supported and will fail the run at startup; if you need residential routing, supply your own provider via Custom proxy URLs (proxyUrls).",
                        "default": {
                            "useApifyProxy": true
                        }
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
