# Greenhouse Hiring Signal Monitor (`fccailin/greenhouse-hiring-signal-monitor`) Actor

Tracks hiring expansion, contraction, and job changes across public Greenhouse job boards.

- **URL**: https://apify.com/fccailin/greenhouse-hiring-signal-monitor.md
- **Developed by:** [SIZHE GUO](https://apify.com/fccailin) (community)
- **Categories:** Jobs, Business
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-usage

## 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

### What does Greenhouse Hiring Signal Monitor do?

**Greenhouse Hiring Signal Monitor tracks hiring changes across companies that use Greenhouse job boards.** It records a trusted baseline, compares later successful runs, and reports hiring expansion or contraction plus new, closed, updated, and reopened roles.

The Actor reads the public [Greenhouse Job Board API](https://docs.greenhouse.io/job-board.html) directly. It does not render pages or parse job descriptions. On Apify, you can schedule repeated runs, call the Actor through the API, export Dataset records, connect integrations, and monitor run health.

### Why use Greenhouse Hiring Signal Monitor?

The Actor is designed for repeated, evidence-based monitoring by:

- B2B sales teams looking for timely account signals
- recruiting agencies and headhunters tracking role demand
- competitive intelligence teams comparing hiring direction
- investors and researchers observing public workforce signals

It reports deterministic changes in public job postings. It does not claim to measure headcount, financial performance, or future company performance.

### How to monitor Greenhouse hiring changes

1. Open the Actor's **Input** tab.
2. Add one or more Greenhouse board tokens or job board URLs.
3. Run the Actor once to create a baseline.
4. Schedule or manually run it again later.
5. Review `company_summary`, `job_change`, and `error` records in the Dataset.
6. Read the run-wide totals from the `OUTPUT` record.

The first successful run never labels all existing jobs as `NEW`. It creates a baseline and displays: `Baseline created. Run the Actor again later to detect hiring changes.`

### Input

| Field | Type | Required | Default | Description |
|---|---|---:|---:|---|
| `companies` | array of strings | Yes | — | Greenhouse board tokens or common Greenhouse job board URLs |
| `includeCurrentJobs` | boolean | No | `false` | Also emit every currently open job as a `current_job` record |

Tokens and URLs that resolve to the same board are de-duplicated within a run.

```json
{
  "companies": [
    "company-a",
    "https://job-boards.greenhouse.io/company-b"
  ],
  "includeCurrentJobs": false
}
```

### Output

Each successfully processed company emits its `company_summary` first, followed by any `job_change` records. When `includeCurrentJobs` is enabled, current jobs follow the changes. Failed companies emit a structured `error` record without stopping other companies.

```json
{
  "record_type": "company_summary",
  "company": "company-a",
  "board_token": "company-a",
  "baseline_created": false,
  "previous_job_count": 40,
  "current_job_count": 46,
  "net_change": 6,
  "change_percent": 15.0,
  "new_jobs": 7,
  "closed_jobs": 1,
  "updated_jobs": 2,
  "reopened_jobs": 0,
  "hiring_signal": "EXPANDING",
  "run_at": "2026-01-15T12:00:00Z"
}
```

```json
{
  "record_type": "job_change",
  "company": "company-a",
  "board_token": "company-a",
  "job_id": "123456",
  "change_type": "UPDATED",
  "title": "Senior Platform Engineer",
  "department": "Engineering",
  "location": "Remote - US",
  "changed_fields": ["location"],
  "detected_at": "2026-01-15T12:00:00Z"
}
```

You can download the Dataset in formats such as JSON, HTML, CSV, or Excel.

### Output data table

| Record / field | Meaning |
|---|---|
| `company_summary` | Counts, department movement, and hiring signal for one successful company fetch |
| `job_change` | A `NEW`, `CLOSED`, `UPDATED`, or `REOPENED` job event |
| `current_job` | Optional normalized open job, including first and last seen timestamps |
| `error` | Safe company-level failure details |
| `department_changes` | Current, new, and closed job counts grouped by department; missing departments use `Unknown` |
| `OUTPUT` | Run totals, timestamps, success/failure counts, and compact company summaries |

Job changes use the stable Greenhouse job ID: `NEW` means an ID has never appeared in this state scope, `CLOSED` means a previously open ID is absent, `UPDATED` means only its title, department, or location changed, and `REOPENED` means a previously closed ID appeared again. Description-only edits do not create `UPDATED` records.

### Hiring signals

Signals use the net job-count growth rate relative to the previous successful run:

| Rule | Signal |
|---|---|
| First successful run | `BASELINE` |
| Growth greater than 30% | `STRONG_EXPANSION` |
| Growth greater than 10% and at most 30% | `EXPANDING` |
| Growth from -10% through 10% | `STABLE` |
| Growth from -30% up to but not including -10% | `CONTRACTING` |
| Growth below -30% | `STRONG_CONTRACTION` |

When the previous count is zero, a positive current count is `STRONG_EXPANSION`; zero to zero is `STABLE`. `change_percent` is `null` when the previous count is zero.

### How repeated monitoring works

The Actor stores each board's state in the named Apify Key-Value Store `greenhouse-hiring-signal-monitor-v1-state`. A named store is required because every Actor run receives a different default Key-Value Store. `current_jobs` represents the last successful open snapshot. `known_jobs` retains closed history, allowing a previously closed job ID to be classified as `REOPENED`. The run-wide `OUTPUT` summary remains in each run's default Key-Value Store.

State belongs to the Apify account that starts the run, so different Apify users do not share monitoring history. Within one account, direct Actor runs share one state scope. Runs of the same saved Actor Task share another task-specific scope, while different Tasks are isolated from each other. Each scope uses a separate deterministic key per normalized board token.

State is updated only after Greenhouse returns a successful HTTP response with valid JSON and a consistent job count. Timeouts, rate limits, HTTP errors, invalid JSON, and malformed responses do not replace the last valid state, so a failed fetch cannot close every previously open role.

For each company, complete Dataset records are written before the new state is committed in one Key-Value Store operation. If the final state write fails, the run reports an error and a later run can repeat the same change records instead of silently losing them.

For one state scope and board token, runs should not overlap. Apify Key-Value Store records use last-write-wins semantics, so two concurrent runs that read the same old snapshot can overwrite each other in completion order. Use one non-overlapping schedule per Task (or serialize direct runs) for the same board.

### Pricing and cost estimation

This is a lightweight HTTP Actor: it normally makes one Greenhouse API request per unique company. Cost depends on the number of companies, run frequency, and Apify platform plan. Start with a small company list and inspect actual run usage before increasing schedule frequency. Apify's free tier may be sufficient for small validation runs; current platform limits and prices are controlled by Apify.

### Tips

- Run at a consistent interval, such as daily or weekly, so changes are comparable.
- Keep `includeCurrentJobs` disabled when you only need changes and summaries.
- Avoid overlapping runs for the same company and state scope.
- Treat board tokens as company identifiers; a company may operate more than one Greenhouse board.
- Investigate repeated `error` records before drawing conclusions from missing data.

### Limitations, privacy, and support

- V1 supports Greenhouse only. It does not support Lever, Ashby, Workday, LinkedIn, notifications, dashboards, or AI analysis.
- `UPDATED` compares only title, department, and location. Description edits are intentionally ignored.
- Greenhouse controls public API availability and job board contents.
- Deleting or renaming the Actor's `greenhouse-hiring-signal-monitor-v1-state` Key-Value Store resets all baselines.
- Concurrent runs for the same board and state scope are not locked; the last completed state write wins.
- The Actor reads public job board data and does not submit applications or collect candidate data.
- Public job postings are hiring signals, not proof of hiring, layoffs, or company performance.

Use this Actor responsibly and follow applicable laws, Greenhouse terms, and the target company's policies. For reproducible bugs or feature requests, open an issue in the [GitHub repository](https://github.com/Ailin0712/greenhouse-hiring-signal-monitor/issues).

# Actor input Schema

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

Greenhouse board tokens or public Greenhouse job board URLs. Duplicate tokens and URLs are monitored once.

## `includeCurrentJobs` (type: `boolean`):

Also emit every currently open job. By default, the Dataset contains summaries and changes only.

## Actor input object example

```json
{
  "includeCurrentJobs": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

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

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("fccailin/greenhouse-hiring-signal-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("fccailin/greenhouse-hiring-signal-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 fccailin/greenhouse-hiring-signal-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,fccailin/greenhouse-hiring-signal-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/bksffJwfBYqVENaMJ/builds/61vgevzSYKsImLw9M/openapi.json
