# ATS Job & Salary Change Monitor (`longish_whistle/ats-job-salary-change-monitor`) Actor

Track Greenhouse, Lever and Ashby jobs in one feed. Compare salary, location and remote-policy changes with before/after evidence. Export JSON or spreadsheet-safe CSV.

- **URL**: https://apify.com/longish\_whistle/ats-job-salary-change-monitor.md
- **Developed by:** [Tender Delta](https://apify.com/longish_whistle) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 delivered jobs

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — 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

## ATS Job & Salary Change Monitor

**Know what changed in an employer's job board without rebuilding three API integrations.**

Turn public Greenhouse, Lever and Ashby postings into one consistent feed. Compare a previous run to see exactly which title, location, remote condition or published salary range changed. Each result includes the original job link and observation time.

### What you can do with it

- **Job-board operators:** refresh a selected employer list with normalized records instead of maintaining three connectors.
- **Recruiting researchers:** review newly observed roles and location changes in one spreadsheet.
- **Compensation analysts:** compare published salary ranges with the previous observation, preserving currency and pay period.

These are public hiring signals, not verified purchasing intent or personal contact leads. No candidate records are collected.

### Try a small run

```json
{"boards":["ashby:Ashby"],"maxJobsPerBoard":5,"maxResults":5}
```

Five delivered jobs cost **$0.015**, plus the displayed Actor start charge. Source data and results can change. No source account, proxy subscription or separate API key is required.

Use board URLs such as https://jobs.lever.co/spotify, https://jobs.ashbyhq.com/Ashby or https://job-boards.greenhouse.io/stripe. Lever's EU region is supported with `lever-eu:board`. Use board identifiers exactly as the employer publishes them.

### Compare two runs

1. Run once to create a baseline. The run stores a `SNAPSHOT` file in its default key-value store.
2. Copy that run's key-value store ID into `previousSnapshotStoreId`. Keep the boards and filters the same.
3. Enable `changesOnly` to deliver only new or changed observations. Your scheduled workflow must pass the preceding run's store ID each time; this is not an automatic daily subscription.

A salary change appears as a structured before/after entry in `changes`, with `changeType: "changed"` and `changedFields: ["salaryComponents"]`. This describes a comparison feature, not a claim that a particular employer changed pay.

**New means first seen in the supplied snapshot, not posted today.** Missing jobs are never labeled closed. A capped baseline can make previously unseen jobs appear new on a later run. Use sufficient caps and read SUMMARY before interpreting coverage.

### Output you can use immediately

| Field | Meaning |
|---|---|
| title, employerBoard, location | Source-backed job identity and location |
| salaryComponents | Published salary amounts, currency and interval; an empty array means unavailable |
| remoteExplicit | true / false / null; no guessing from the title |
| sourceUrl, applyUrl | Employer's original public links |
| changeType, changes | New, changed or unchanged; field-level before/after values |
| firstObservedAt, observedAt | This workflow's observation times |
| warnings | Missing fields and publication-date caveats |

The dataset contains delivered results. `OUTPUT-CSV` is UTF-8 and spreadsheet-formula-safe; JSON retains original text. `SUMMARY` reports sources, caps, errors and coverage. `DELIVERY` reports export completeness. `SNAPSHOT` supports your next comparison.

### Pricing and limits

**$3 per 1,000 delivered jobs ($0.003 each)**, plus the displayed Actor start charge. This is pay per event; platform usage is included in this Actor's price. There is no monthly rental. Set Apify's maximum charge and the input caps before starting. Unchanged jobs suppressed by changesOnly are not charged as delivered jobs. The start charge may apply even when no jobs match.

Up to 20 boards, 500 delivered jobs per board and 1,000 delivered jobs per run. Default: up to 20 per board / 60 total. Collection is bounded to about 90 seconds, with explicit partial results when limits or source errors intervene. Snapshots retain up to 10,000 recent records. A source request is limited to 15 MB; unsupported or changed schemas fail explicitly.

Structured salary is available only when the employer publishes supported fields in Ashby or Lever. Greenhouse salaries embedded in descriptions are not extracted. Ashby's date means last publication, not original creation. No job descriptions, applicants, email addresses or salary estimates are delivered. Access errors and rate limits are respected; there is no login or anti-bot bypass.

### Automation and source documentation

Run through Apify Console, API, schedules or your existing workflow. Each run fetches current source data; the Actor does not maintain a global daily dataset. Retention follows your Apify account settings. You control reuse of the public data and must respect source terms and applicable rights.

Sources: [Greenhouse Job Board API](https://docs.greenhouse.io/job-board.html), [Lever Postings API](https://github.com/lever/postings-api), [Ashby public job posting API](https://developers.ashbyhq.com/docs/public-job-posting-api). Independent tool by **Tender Delta**; not affiliated with those providers.

For a reproducible issue, use this Actor's Issues tab with the input and a redacted run link. Never include credentials or applicant data.

# Actor input Schema

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

Up to 20 board URLs or provider:board identifiers. Supports Greenhouse, Lever (US/EU), and Ashby. Public job postings only.

## `keywords` (type: `array`):

Optional case-insensitive OR filter. Empty includes all titles.

## `locations` (type: `array`):

Optional case-insensitive OR filter; combined with title filter.

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

Unknown and hybrid roles are excluded; remote status is never guessed.

## `salaryOnly` (type: `boolean`):

Only published salary ranges from Ashby or Lever. No extraction from descriptions and no estimates.

## `maxJobsPerBoard` (type: `integer`):

Bounded sample; check SUMMARY for capped boards.

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

Caps total delivered rows and charges.

## `changesOnly` (type: `boolean`):

Compare with the selected previous run snapshot. The first run considers every result new.

## `previousSnapshotStoreId` (type: `string`):

Optional store from your own previous run containing SNAPSHOT. Your workflow must pass the preceding run defaultKeyValueStoreId each time. No automatic shared state.

## Actor input object example

```json
{
  "boards": [
    "ashby:Ashby",
    "greenhouse:stripe",
    "lever:spotify"
  ],
  "keywords": [],
  "locations": [],
  "remoteOnly": false,
  "salaryOnly": false,
  "maxJobsPerBoard": 20,
  "maxResults": 60,
  "changesOnly": false
}
```

# Actor output Schema

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

Structured results with source evidence and optional changes.

## `csv` (type: `string`):

Only delivered rows; review DELIVERY and SUMMARY for completeness.

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

Counts, errors, caps and stop reason.

## `files` (type: `string`):

SNAPSHOT for your next comparison; DELIVERY and diagnostics when applicable.

# 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("longish_whistle/ats-job-salary-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 = {}

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,longish_whistle/ats-job-salary-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/HdG9cH1fH2yZfO4Vi/builds/tV4l8StzshW80dorf/openapi.json
