# Hiring Change Feed - Greenhouse, Lever, Workday, Ashby (`blooming_gator/hiring-change-feed`) Actor

Scrapes Greenhouse, Lever, Workday, Ashby, SmartRecruiters, Recruitee, Personio, JazzHR and Rippling and returns only what CHANGED - the roles a company opened and closed since your last run. A retitled posting is never sold as a closure. Ships with 8,220 measured events so run one returns data.

- **URL**: https://apify.com/blooming\_gator/hiring-change-feed.md
- **Developed by:** [Kaleb Confer](https://apify.com/blooming_gator) (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

$1.50 / 1,000 hiring change events

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

## Hiring Change Feed - Jobs Opened and Closed

Not a list of job postings. A list of what **changed**: which requisitions a
company opened since your last run, which it closed, and which moved.

There are nine good ways to buy job postings and the price only goes down. A
posting that is true today was true last month. The change is true once, and
the closing half of it is the half nobody publishes: an opening is announced by
the employer and a closure is silent, so it has to be inferred from an absence.
Inferring it correctly is the whole product.

### What you get

| Field | Meaning |
|---|---|
| `event` | `opened`, `closed` or `moved` |
| `title` | Role title |
| `location` | Location as the employer wrote it |
| `department` | Team, where the board exposes one |
| `url` | The employer's own apply URL |
| `requisition` | The vendor's own posting id - stable across edits |
| `ats` / `board_token` | Which board it came from |
| `since` | Start of the window this row covers |
| `observed_at` | The day the change was seen |
| `source` | `your run` for a comparison this account made, `reference index` for one already recorded |
| `grade` | `high` for a comparison between two of your own runs, `recorded` for a measured event that shipped with the image. Both are billed; `coverage` and baselines are not. |

### The three ways a change feed lies, and what this does instead

**A retitle is not a closure.** Identity here is the vendor's own posting id,
never a hash of the title. A recruiter changing "Engineer II" to "Engineer,
Platform" changes nothing about the requisition. Measured against a live
re-fetch of 25 Greenhouse boards on 2026-09-03: keying on the title made
**17.3%** of high-confidence closures false. Keying on the vendor's id made it
**0.0%** - 131 of 131 confirmed gone.

**A move is not a closure, and not twenty openings.** One requisition can be
advertised in twenty states and the vendor expands it into twenty rows under
one id: Rippling's own board carries 694 postings under 354 ids. Keying on the
id alone would collapse 49% of that board, so the key is the id **plus** the
location, and a genuine relocation is reported once, as `moved`.

**A failed fetch is not a layoff.** The expensive bug here is a board that
rate-limits or comes back empty being sold as every role at that company
closing at once. A board that errors emits no closures. A board that answers
with zero postings where it held roles is graded `coverage`, reported in the
provenance record, and emits no closures either - an empty board and an
unreachable one look identical from outside and only one of them is news.

### How it works

1. Give it company names, or boards as `ats/token` (for example
   `greenhouse/stripe`, `ashby/openai`, `workday/wd5/target/targetcareers`).
2. The **first run on a board returns the changes already measured for it** -
   8,220 high-confidence events across 646 boards, recorded
   2026-08-30 to 2026-09-04 - and takes that board's baseline snapshot in the same run.
   Every row says `source: reference index` so you always know which
   observations are ours and which are yours.
3. Every run after that reports what changed since **your** previous run, marked
   `source: your run`. Schedule it daily and the feed is daily; run it weekly
   and each row says the window it covers, in `since`.

`maxResults` defaults to **500**, so a first run is a small known cost rather
than a surprise. Capping loses nothing: every board's snapshot is written before
the cap is applied, so the next run continues from the right place.

Set `includeRecordedHistory` to false if you want only what your own runs see.
Then a first run delivers nothing and charges nothing, which is the honest floor
for a change feed with no history behind it.

The recorded window is fixed when the image is built and **ages until it is
rebuilt** - `recorded_to` in the provenance record is the last day it covers.
Only high-confidence events ship: a board's first day in the index reads exactly
like opening every role at once, and that has never been counted as hiring.

State lives in a named key-value store called `hiring-change-feed-state` **on
your own account**, so the history is yours, not rented back to you. Delete a
record there and that board starts over from a fresh baseline.

### Coverage

A verified directory of **12,784 live boards** across nine ATS platforms is
baked in, so a company name resolves without guessing a slug. Guessing has a
measured 0.77% hit rate; the directory does not guess.

Greenhouse, Ashby, Lever, Workable, Workday, SmartRecruiters, Recruitee,
Rippling and Personio. Both of Lever's API hosts are checked - Lever runs a
separate EU data-residency deployment at `api.eu.lever.co`, and a single-host
adapter reads every European board as though it closed.

The free address directory is public and needs no account:
https://kalebconfer-sys.github.io/job-board-directory/

### Pricing

Per change event delivered. Baselines and boards graded `coverage` are not
events and are not charged.

### Limits, stated rather than discovered

- A posting served from an employer's own white-label domain with no vendor id
  in the URL is left out of the diff rather than matched on its title. That is
  484 of 347,934 observed rows in the reference index.
- Recruitee's identity is its URL slug, because that is what its board exposes;
  a Recruitee retitle that also changes the slug reads as a close and an open.
- The window is however long it has been since your last run on that board, not
  a fixed day. `since` on every row says exactly what it is.
- Nothing here is behind a login and no access control is circumvented. These
  are the vendors' own public board APIs, and the crawl delays they declare are
  honored.

Need a slice this Actor does not cut, a scheduled delivery into your own
warehouse, or a coverage comparison against the source you use now?
**kaleb@revivacatch.com**

# Actor input Schema

## `companies` (type: `array`):

Companies to watch. Resolved against the verified directory of 12,784 boards - never guessed.

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

Exact boards, if you know them. Write the platform first: greenhouse/stripe, ashby/openai, workday/wd5/target/targetcareers.

## `includeRecordedHistory` (type: `boolean`):

On a board's first run there is no previous snapshot to compare against. With this on, the run returns the changes the reference index already measured for that board (marked source=reference index) so the first run is useful. Turn it off to receive only what your own runs observe.

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

Hard cap on rows delivered and charged. Left at 500 so a first run is a known, small cost. Set 0 for no cap. Capping never loses anything: every board's snapshot is still written, so the next run continues correctly.

## `maxBoards` (type: `integer`):

How many boards to compare in one run.

## Actor input object example

```json
{
  "companies": [
    "Stripe",
    "Figma",
    "Anthropic"
  ],
  "boards": [],
  "includeRecordedHistory": true,
  "maxResults": 500,
  "maxBoards": 50
}
```

# Actor output Schema

## `events` (type: `string`):

opened / closed / moved, one row each.

## `provenance` (type: `string`):

Per board: postings before and after, events emitted, and why a board was graded coverage instead of billed.

# 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 = {
    "companies": [
        "Stripe",
        "Figma",
        "Anthropic"
    ],
    "boards": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("blooming_gator/hiring-change-feed").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 = {
    "companies": [
        "Stripe",
        "Figma",
        "Anthropic",
    ],
    "boards": [],
}

# Run the Actor and wait for it to finish
run = client.actor("blooming_gator/hiring-change-feed").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 '{
  "companies": [
    "Stripe",
    "Figma",
    "Anthropic"
  ],
  "boards": []
}' |
apify call blooming_gator/hiring-change-feed --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,blooming_gator/hiring-change-feed"
        }
    }
}

```

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/AWr0IHDQTIS5ajgek/builds/0V0cImydkLsGcvRb4/openapi.json
