# Company hiring signals - who opened or closed the most jobs (`retrainmapdata/company-hiring-signals`) Actor

Which companies opened and which closed the most job postings since your last run, across 2,099 verified Greenhouse, Lever, Ashby and Workable boards. One row per company: open now, added, closed, net change, and the titles that moved. The first run records a baseline; schedule it weekly.

- **URL**: https://apify.com/retrainmapdata/company-hiring-signals.md
- **Developed by:** [RetrainMap Data](https://apify.com/retrainmapdata) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 records

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

## Company hiring signals — who opened or closed the most jobs

Which companies are **opening** postings and which are **closing** them, read from
2,099 verified company career sites on **Greenhouse, Lever, Ashby and Workable**. One row per
company — not per posting:

`company · ats · board · open_now · baseline_open · added · closed · net_change ·
added_titles[] · closed_titles[] · baseline_at · is_first_run · error · fetched_at`

Rows come back biggest-mover first. Run it empty and it walks the bundled board index; no
board tokens to look up.

### How the comparison works, and why the first run is different

Every ATS API publishes only what is **open right now**. None of them publish "closed last
week." So a change signal has to come from a snapshot you took earlier — there is no other
honest source for it.

Each run records, per board, the set of open posting IDs into a **named key-value store** in
your account (`baseline_store`, default `company-hiring-signals-baseline`). That store
survives between runs. The next run diffs against it.

**Therefore the first run cannot report a delta, and this Actor will not invent one.** It
returns `open_now` with `added`, `closed` and `net_change` set to `null`, `is_first_run: true`,
and says so in the run status. **Schedule it** — weekly is the natural cadence — and every run
from the second onwards carries the signal.

If you are feeding a pipeline that must never read a `null` as a `0`, set **`require_baseline`**
and a baseline-less run fails loudly instead.

### What a signal does and does not mean

- **`added`** — posting IDs on the board now that were not there at the baseline. A company
  opened those roles in the window.
- **`closed`** — posting IDs that were there and are gone. The role was filled, cancelled, or
  expired. **The ATS does not say which, so neither does this Actor.**
- **`net_change`** — added minus closed. Sustained negatives across a sector are the
  interesting shape; a single company's swing is usually one team's hiring round.

**An unreadable board is not a hiring freeze.** If a board 404s or times out, the row carries
`error`, its deltas are null, and **its baseline is left untouched** — so the next successful
run still compares against real data, and an outage is never published as a collapse.

### Input

| Field | Meaning |
|---|---|
| `boards` | Optional explicit watchlist: `greenhouse/stripe`, `lever/spotify`, `ashby/ramp`, `workable/netguru` |
| `max_boards` | How many boards to read (default 100). Keep it stable once scheduled — the comparison is only meaningful over the same set |
| `ats` | Limit to some of the four platforms |
| `company_contains` | Restrict the index walk to matching companies |
| `changed_only` | Drop companies that did not move |
| `min_change` | Report a company only if it moved by at least N postings |
| `baseline_store` | Name the store — one per watchlist, so two schedules do not overwrite each other |
| `require_baseline` | Fail rather than return null deltas |
| `update_baseline` | Off = an ad-hoc query that does not reset your comparison window |

### Who uses this

Recruiting and staffing teams looking for accounts that just opened headcount; sales teams
timing outreach to a hiring ramp; analysts tracking sector-level hiring; anyone who wants
"who started hiring this week" from employers' own boards rather than a listings aggregator.

### The board index

`data/boards.csv`, shipped inside the image: company, ATS, board slug, job count at
verification, verification timestamp. Every board in it answered with at least one live
posting when checked — the RetrainMap ATS index behind
[retrainmap.com/data](https://retrainmap.com/data/), not a guessed list of slugs.

### When a run fails

If an ATS answers in a shape it does not document, the run stops with a `SourceError` naming
the endpoint rather than emitting guessed rows. One unreadable board is recorded and the run
continues; only every-board-failed is fatal.

### Politeness and identification

One request per board, at most one per `request_interval_ms` (250 ms floor), two retries with
back-off on 429/5xx. Every request identifies the operator by product-token `User-Agent` and
carries a contact address in the standard `From:` header (RFC 9110 §10.1.2).

### Pricing

Pay per event: **$0.10** per run start + **$0.005** per company row.

***

Related: **Remote jobs feed** (remote postings across the same index) and **ATS jobs** (full
postings from boards you name). The full 109,000-posting weekly dataset is at
[retrainmap.com/data](https://retrainmap.com/data/).

By Steelyard Ventures LLC (brand: RetrainMap) · info@steelyardclinical.com

# Actor input Schema

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

Optional. Entries look like 'greenhouse/stripe', 'lever/spotify', 'ashby/ramp', 'workable/netguru' - the ATS, then the slug from the public board URL. Leave empty to walk the bundled index instead.

## `max_boards` (type: `integer`):

How many boards this run reads (one request each). Watching the same set every run is what makes the comparison meaningful, so keep this stable once you schedule it.

## `ats` (type: `array`):

Empty = all four. The index holds 983 Greenhouse, 655 Ashby, 317 Lever and 144 Workable boards.

## `company_contains` (type: `string`):

Restrict the walk to boards whose company name or slug contains this text. Ignored when you supply an explicit board list.

## `changed_only` (type: `boolean`):

Drop companies with no opened and no closed postings since the baseline. Has no effect on a first run, where no company has a comparison yet.

## `min_change` (type: `integer`):

Keep a company only when it moved by at least this many postings. 0 = report every company read.

## `baseline_store` (type: `string`):

The named key-value store in your account that holds the previous snapshot. Use a different name per watchlist so two schedules do not overwrite each other's baseline.

## `require_baseline` (type: `boolean`):

For pipelines that must never read a null delta as a zero: with this on, a run with no previous snapshot fails instead of returning open counts only.

## `update_baseline` (type: `boolean`):

On by default. Turn it off for an ad-hoc query you do not want to reset the comparison window (boards that could not be read never overwrite their baseline either way).

## `request_interval_ms` (type: `integer`):

Politeness delay towards the ATS hosts. One request per board; the floor is 250 ms.

## `contact_email` (type: `string`):

Sent in the standard From: request header so the data owner can reach the operator (RFC 9110 s10.1.2).

## Actor input object example

```json
{
  "max_boards": 40,
  "changed_only": false,
  "min_change": 0,
  "baseline_store": "company-hiring-signals-baseline",
  "require_baseline": false,
  "update_baseline": true,
  "request_interval_ms": 400,
  "contact_email": "info@steelyardclinical.com"
}
```

# Actor output Schema

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

One row per company, biggest mover first.

## `results_csv` (type: `string`):

The same rows as a CSV file.

# 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 = {
    "max_boards": 40
};

// Run the Actor and wait for it to finish
const run = await client.actor("retrainmapdata/company-hiring-signals").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 = { "max_boards": 40 }

# Run the Actor and wait for it to finish
run = client.actor("retrainmapdata/company-hiring-signals").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 '{
  "max_boards": 40
}' |
apify call retrainmapdata/company-hiring-signals --silent --output-dataset

```

## MCP server setup

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

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/XbaWHzTEEAOsWtlzd/builds/WCyJb6I9EdXqAHvDE/openapi.json
