# ATS Job Monitor — Track New, Closed & Changed Jobs (`dev_web_col/ats-job-monitor`) Actor

Job alerts from company career pages: new, closed and edited roles since your last run. Recruiting and hiring signals without re-downloading the board.

- **URL**: https://apify.com/dev\_web\_col/ats-job-monitor.md
- **Developed by:** [Diseño Web de Colombia](https://apify.com/dev_web_col) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 change detecteds

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#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 Monitor — Track New, Closed & Changed Jobs

**Stop re-downloading the same jobs. Get only what changed since your last run.**

### What is the ATS Job Monitor?

This Actor watches company job boards on Greenhouse, Lever, Ashby and Workable, and reports the difference against the previous run: roles that opened, roles that closed, and roles whose title, location or description changed.

A scraper is something you run once. A monitor is something you schedule and forget. Point it at a set of companies, put it on a daily or hourly schedule, and it tells you what moved.

### Why a diff beats a dump

Dumping a whole board every day means paying for thousands of rows to find the three that are new. This Actor keeps a compressed snapshot in a named key-value store in **your own account**, compares against it, and returns only the delta.

Two consequences worth knowing:

- **The first run is a baseline.** It does not charge per result and returns only a small sample, because billing you for a full initial snapshot would be a nasty first invoice.
- **Each customer's history is isolated**, because the store lives in the account running the Actor. Your baseline is yours.

Hiring changes are also a timing signal. A role that opens this morning is worth more than one you find next week, which is why hourly scheduling is supported.

### What you can use it for

| What you want | How to ask for it |
|---|---|
| **Job alerts** — be first to a new posting | Schedule it daily or hourly, read `new` changes |
| **Recruiting intelligence** — competitor reqs | `companyTokens` for your competitive set |
| **Hiring signals for sales** — accounts staffing up | Count `new` changes per company |
| **Detecting a freeze or layoff** | Watch `closed` changes cluster |
| **Tracking a role you want** | `searchKeyword` + `location`, alert on `new` |
| **Salary or title changes** | Read `modified` changes and the fields that moved |
| **Market timing** — when a company opens a team | Hourly schedule; the timing is the product |
| **Independent watchlists** | A different `monitorKey` per list |

Typical jobs: **job alerts**, **recruiting** intelligence, **hiring data** for
sales triggers, and competitor headcount tracking.

### What data you get

For each change: the change type (new, closed or modified), the job title, company, location, department, remote flag, the apply URL, the posting date, and for modifications, which fields changed. Plus the underlying job record.

### Input

Every field is optional unless marked required.

| Field | Type | Description |
|---|---|---|
| `oneShot` | boolean | Return the CURRENT jobs instead of what changed. Use this for a single lookup or when calling from an AI agent — the monitor needs two runs to have anything to compare, one-shot answers immediately. It never touches your saved history. |
| `monitorKey` | string | Keep this the same across runs — it is how the actor remembers what it already saw. Use a different name to run several independent monitors (e.g. "engineering" and "sales"). |
| `searchKeyword` | string | Only watch jobs whose title contains this, e.g. "backend engineer". Leave empty to watch every job. |
| `searchScope` | string | Job title only is precise. Adding the description finds more, but also matches jobs that merely mention the keyword. |
| `trackChangeTypes` | array | Leave empty to get all three. New = a role just opened. Closed = it is gone, usually filled or cancelled. Modified = title, location, salary, department or employment type changed. |
| `location` | string | Free text match against the job location, e.g. "New York", "London", "Germany". |
| `remoteOnly` | boolean | Only watch jobs the company flags as remote. |
| `department` | string | Free text match against the department or team, e.g. "Engineering", "Sales". |
| `atsPlatforms` | array | Leave empty to watch all of them. |
| `companyTokens` | array | Only if you want to watch specific companies instead of the built-in index of 872 companies. Accepts "greenhouse:stripe" or a job board URL. |
| `maxCompanies` | integer | Keep this stable across runs. Changing it changes which companies are watched, which shows up as a burst of new and closed jobs. |
| `maxChanges` | integer | Safety cap. You are charged per change returned. |
| `resetBaseline` | boolean | Forget everything seen before and take a fresh baseline. The baseline run is never charged per result. |

#### Input sample

```json
{
  "searchKeyword": "backend engineer",
  "companyTokens": [
    "greenhouse:stripe",
    "lever:ro",
    "ashby:ramp"
  ]
}
```

### Output sample

One real row from an actual run:

```json
{
  "changeType": "baseline",
  "detectedAt": "2026-08-03T02:39:18.466Z",
  "changedFields": null,
  "id": "greenhouse:stripe:7826765",
  "ats": "greenhouse",
  "companyToken": "stripe",
  "companyName": "Stripe",
  "externalId": "7826765",
  "title": "Backend Engineer, AI Security",
  "department": "8614 Office of the CISO & Partnership",
  "location": "New York, San Francisco, Seattle, or Remote (US/Canada)",
  "isRemote": true,
  "employmentType": null,
  "compensationText": null,
  "postedAt": "2026-04-20T17:56:42.000Z",
  "applyUrl": "https://stripe.com/jobs/search?gh_jid=7826765",
  "descriptionText": "Who we are\n\n About Stripe\n\n Stripe is a financial infrastructure platform for businesses. Millions of companies—from the world's largest enterprises to the most ambitious startups—use Stripe to accept payments, grow t..."
}
```

### Who uses this

- **Recruiters** — know the moment a competitor opens a req
- **Sales teams** — hiring changes are buying signals with timing attached
- **Candidates** — be first to a new posting, not the hundredth applicant
- **Analysts** — track headcount direction across a set of companies over time
- **Anyone running a job alert product** who needs a clean change feed

### Honest limits

These are real and none of them is fixable by any tool. They are here so you know what you are buying before you run it.

- **The first run establishes a baseline** and deliberately returns only a sample. Real change detection starts on the second run.
- **Only Greenhouse, Lever, Ashby and Workable.** Companies on other systems are not covered.
- **A closed role means it left the board**, which usually means filled or cancelled — the ATS does not say which.
- **Change detection depends on your schedule.** If a role opens and closes between two runs, you will not see it.
- **Workable rate-limits harder** than the others and is queried more slowly.

### FAQ

#### Where is the baseline stored?

In a named key-value store inside your own Apify account, keyed by the `monitorKey` you choose. It never leaves your account, and different keys keep separate histories so you can run several independent monitors.

#### Why did my first run return almost nothing?

Because it is the baseline. There is nothing to compare against yet, so it records the current state and returns a small sample without charging per result. The second run is where changes appear.

#### How often should I schedule it?

Daily is enough for most tracking. Hourly makes sense when being first matters — recruiting and sales both benefit. The cost scales with changes found, not with how often you check.

#### What counts as a modified job?

A posting that stayed on the board but whose title, location, department or description changed. The row tells you which fields moved.

#### Can I reset the baseline?

Yes, set `resetBaseline` to true for one run and it starts over from the current state.

#### Can I just get a full dump instead?

Use the ATS Job Scraper Actor for that. Same sources, no diffing, returns everything matching your filters.

#### Do I need an account or an API key for the source?

No. This Actor only reads public pages and public endpoints, the same ones any
logged-out visitor sees. You never give it credentials, and nothing is done on
your behalf.

#### Is it legal to scrape this data?

Scraping publicly available information is broadly accepted as legal, and this
Actor only ever touches pages that require no login. That said, what you may do
with the data afterwards is a separate question — personal data in particular is
regulated by GDPR and CCPA. If you plan to process personal data, take advice
first. Apify has a good primer: [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/)

#### How much will a run cost me?

You are charged per result, so the cost scales with what you actually get back.
The pricing is shown on this page above the input form. Start with a small run to
see real numbers before you scale up.

#### Can I export to Excel, CSV or Google Sheets?

Yes. Every run's dataset can be downloaded as CSV, Excel, JSON, XML or HTML from
the **Storage** tab, and Apify integrates directly with Google Sheets, Airtable,
Zapier, Make and Slack.

#### Can I run this on a schedule?

Yes. Open the **Schedules** tab and pick an interval — hourly, daily, weekly.
Each run only costs you what it returns.

#### Can I call it from my own code instead of the console?

Yes. Every Actor on Apify is also an API endpoint. There are official clients for
[Python](https://docs.apify.com/api/client/python/) and
[JavaScript](https://docs.apify.com/api/client/js/), plus a plain REST API. The
**API** tab on this page shows ready-made snippets with your input already filled in.

#### Something came back empty. Is it broken?

Usually not. An empty result normally means the filters were too narrow, or the
source genuinely has nothing to return for that query. This Actor is written to
finish successfully and tell you what happened rather than fail — check the run
log, it says which step returned nothing.

### Related Actors

- **ATS Job Scraper** — the full dump version of the same sources
- **LinkedIn Jobs Scraper + Direct Apply Links** — LinkedIn postings with the original
- **Company Signals** — competitor changes beyond hiring

### Where the data comes from

The public JSON endpoints of Greenhouse, Lever, Ashby and Workable. No login, no API key. Snapshots are stored in your own Apify account.

# Actor input Schema

## `oneShot` (type: `boolean`):

Return the CURRENT jobs instead of what changed. Use this for a single lookup or when calling from an AI agent — the monitor needs two runs to have anything to compare, one-shot answers immediately. It never touches your saved history.

## `monitorKey` (type: `string`):

Keep this the same across runs — it is how the actor remembers what it already saw. Use a different name to run several independent monitors (e.g. "engineering" and "sales").

## `searchKeyword` (type: `string`):

Only watch jobs whose title contains this, e.g. "backend engineer". Leave empty to watch every job.

## `searchScope` (type: `string`):

Job title only is precise. Adding the description finds more, but also matches jobs that merely mention the keyword.

## `trackChangeTypes` (type: `array`):

Leave empty to get all three. New = a role just opened. Closed = it is gone, usually filled or cancelled. Modified = title, location, salary, department or employment type changed.

## `location` (type: `string`):

Free text match against the job location, e.g. "New York", "London", "Germany".

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

Only watch jobs the company flags as remote.

## `department` (type: `string`):

Free text match against the department or team, e.g. "Engineering", "Sales".

## `atsPlatforms` (type: `array`):

Leave empty to watch all of them.

## `companyTokens` (type: `array`):

Only if you want to watch specific companies instead of the built-in index of 872 companies. Accepts "greenhouse:stripe" or a job board URL.

## `maxCompanies` (type: `integer`):

Keep this stable across runs. Changing it changes which companies are watched, which shows up as a burst of new and closed jobs.

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

Safety cap. You are charged per change returned.

## `resetBaseline` (type: `boolean`):

Forget everything seen before and take a fresh baseline. The baseline run is never charged per result.

## Actor input object example

```json
{
  "oneShot": false,
  "monitorKey": "default",
  "searchKeyword": "backend engineer",
  "searchScope": "title",
  "trackChangeTypes": [],
  "remoteOnly": false,
  "atsPlatforms": [],
  "companyTokens": [
    "greenhouse:stripe",
    "lever:ro",
    "ashby:ramp"
  ],
  "maxCompanies": 200,
  "maxChanges": 5000,
  "resetBaseline": false
}
```

# Actor output Schema

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

One dataset item per change, tagged with changeType.

# 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 = {
    "searchKeyword": "backend engineer",
    "companyTokens": [
        "greenhouse:stripe",
        "lever:ro",
        "ashby:ramp"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("dev_web_col/ats-job-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 = {
    "searchKeyword": "backend engineer",
    "companyTokens": [
        "greenhouse:stripe",
        "lever:ro",
        "ashby:ramp",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("dev_web_col/ats-job-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 '{
  "searchKeyword": "backend engineer",
  "companyTokens": [
    "greenhouse:stripe",
    "lever:ro",
    "ashby:ramp"
  ]
}' |
apify call dev_web_col/ats-job-monitor --silent --output-dataset

```

## MCP server setup

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