# Hiring Signal Monitor — new & removed jobs per company (`handa-labs/hiring-signal-monitor`) Actor

Watch any company's career page (Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee) and get open roles classified by function and seniority, plus what changed since the last run. Built for sales prospecting, recruiting intel and AI agents.

- **URL**: https://apify.com/handa-labs/hiring-signal-monitor.md
- **Developed by:** [tatsuya handa](https://apify.com/handa-labs) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 company summaries

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 Signal Monitor — new & removed jobs from company career pages

Watch any list of companies and learn **who is hiring for what, right now, and what changed since you last looked**.
The Actor reads the public job-board APIs of the most common applicant tracking systems (ATS) — no browser, no login, no HTML scraping of the career site — and returns:

- a **per-company summary**: open roles, new / removed since the previous run, breakdown by function (Sales, Engineering, AI/ML, …), seniority and location, remote share, and a one-sentence `llmSummary` that an AI agent can read directly;
- **job records** for every role that is new or removed (or every open role, if you ask for it), each classified by function and seniority, with the real apply URL.

Typical uses:

- **Sales prospecting** — a company that just opened 5 sales roles or an AI-engineering team has budget and a new initiative. Feed the `new` records to your CRM or outreach tool.
- **Recruiting & talent intelligence** — track competitors' hiring velocity by function and location.
- **Investors & analysts** — headcount signals across a portfolio or a watchlist, updated daily.
- **AI agents** — call it as an MCP tool: "Which of these 50 companies started hiring for security this month?"

### Output

Two record types live in the same dataset; use the **Company summaries** and **Job changes** views in the Apify Console, or filter on `type`.

**Company summary** (`type: "company"`)

```json
{
  "type": "company",
  "company": "gitlab",
  "ats": "greenhouse",
  "status": "ok",
  "openJobs": 231,
  "newJobs": 4,
  "removedJobs": 7,
  "netChange": -3,
  "remoteShare": 0.86,
  "byFunction": [{ "name": "Engineering", "count": 85 }, { "name": "Sales", "count": 40 }],
  "bySeniority": [{ "name": "Mid", "count": 120 }, { "name": "Senior", "count": 51 }],
  "byLocation": [{ "name": "Remote, US", "count": 60 }],
  "newByFunction": [{ "name": "Sales", "count": 3 }],
  "baseline": false,
  "previousRunAt": "2026-09-03T04:00:12.000Z",
  "checkedAt": "2026-09-04T04:00:09.000Z",
  "llmSummary": "gitlab (greenhouse) has 231 open roles. Since 2026-09-03: +4 new, -7 removed (net -3). Largest functions: Engineering 85, Sales 40, Other 31. New roles concentrated in: Sales 3. 86% remote-friendly."
}
```

**Job record** (`type: "job"`)

```json
{
  "type": "job",
  "company": "gitlab",
  "ats": "greenhouse",
  "change": "new",
  "id": "8503792002",
  "title": "Account Executive - Italy",
  "function": "Sales",
  "seniority": "Mid",
  "location": "Remote, Italy",
  "department": "EMEA - Commercial",
  "remote": true,
  "employmentType": null,
  "url": "https://job-boards.greenhouse.io/gitlab/jobs/8503792002",
  "postedAt": "2026-08-31T17:56:36-04:00",
  "firstSeen": "2026-09-04T04:00:09.000Z",
  "checkedAt": "2026-09-04T04:00:09.000Z"
}
```

`change` is one of `baseline` (first run), `new`, `removed` (carries `lastSeen`), or `open` (only with `emitUnchangedJobs`).

Functions: Sales, Marketing, Customer Success, Engineering, AI/ML, Data, Product, Design, Operations, Finance, People/HR, Legal, Security, IT, Other.
Seniority: Intern, Junior, Mid, Senior, Lead/Staff, Manager, Director, VP, C-level. Classification is rule-based on the title (deterministic, no LLM), so results are stable between runs.

### Supported sources

| ATS | Accepted inputs | Source |
|---|---|---|
| Greenhouse | `https://boards.greenhouse.io/<token>`, `https://job-boards.greenhouse.io/<token>`, `greenhouse:<token>` | public Job Board API |
| Lever | `https://jobs.lever.co/<slug>`, `lever:<slug>` | public Postings API |
| Ashby | `https://jobs.ashbyhq.com/<slug>`, `ashby:<slug>` | public Job Posting API |
| Workable | `https://apply.workable.com/<slug>`, `workable:<slug>` | public widget API |
| SmartRecruiters | `https://careers.smartrecruiters.com/<CompanyId>`, `smartrecruiters:<CompanyId>` | public Posting API |
| Recruitee | `https://<slug>.recruitee.com`, `recruitee:<slug>` | public Offers API |

You can also pass a **company homepage** (e.g. `https://www.anthropic.com`). The Actor scans the homepage and common career paths (`/careers`, `/jobs`, …) for a supported ATS link, and falls back to guessing the ATS token from the domain. Companies whose ATS cannot be found are reported with `status: "ats_not_found"` and are not charged as summaries.

### Input

```json
{
  "companies": [
    "https://boards.greenhouse.io/gitlab",
    "https://jobs.ashbyhq.com/openai",
    "https://www.anthropic.com",
    "lever:leverdemo"
  ],
  "sinceLastRun": true,
  "emitUnchangedJobs": false,
  "includeDescriptions": false
}
```

| Field | Default | Meaning |
|---|---|---|
| `companies` | — | Homepages, career-page URLs or `ats:slug` references. One per company. |
| `sinceLastRun` | `true` | Emit job records only for roles that are **new** or **removed** since the previous run of this Actor for the same company. The first run is a **baseline** (all roles emitted with `change: "baseline"`). |
| `emitUnchangedJobs` | `false` | Also emit every currently open role (`change: "open"`). |
| `includeDescriptions` | `false` | Add a plain-text `description` (max 4000 chars) to job records. |
| `maxJobsPerCompany` | `2000` | Safety cap. |
| `stateStoreName` | `hiring-signal-state` | Named key-value store that remembers the last snapshot per company. Use a different name per watchlist. |
| `concurrency` | `3` | Companies checked in parallel. |

Schedule the Actor (daily or weekly) with the same input and you get a clean change feed: only what is new or gone.

### Monitor mode, watchlists and the free first run

All handa-labs monitors share the same controls:

| Field | Default | Meaning |
|---|---|---|
| `mode` | `monitor` | `monitor` remembers what was returned and emits only changes on later runs; `snapshot` returns everything matching on every run |
| `watchlistId` | `default` | label for this watchlist; runs with the same ID share memory, different IDs are independent |
| `emitBaseline` | `false` | the first monitor run establishes a **free baseline** (only the Actor-start fee) and returns counts; set `true` to also receive, and pay for, every current item on that first run |

Schedule the Actor with the same input and you get a clean change feed. Nothing is charged for records on the first run unless you ask for them.

### Pricing

Pay-per-event. You are charged per **company summary** and per **job record** emitted. With `sinceLastRun` on, a daily schedule over 100 companies typically emits a handful of job records per day, so the cost is dominated by the 100 summaries.

### Notes & limits

- Only roles that the company publishes on its public job board are visible. Internal or unlisted roles are not.
- Data is fetched from the ATS vendors' public endpoints at low request rates (one request per company for most ATS). No proxies are required.
- The Actor stores only job IDs, titles, locations and URLs in its state store; no personal data is collected.
- Companies using an unsupported ATS (Workday, iCIMS, SAP SuccessFactors, Taleo, …) are reported as `ats_not_found`. Support for more systems is added based on demand — open an issue on the Actor page.

***

### 日本語

企業リストを渡すと、各社の採用ページ(Greenhouse / Lever / Ashby / Workable / SmartRecruiters / Recruitee の公開API)から**今の求人**と\*\*前回からの変化(新規・削除)\*\*を返します。ブラウザもログインも不要で、企業サイトのHTMLは取得しません。

- 会社ごとの要約: 求人数、前回比の新規/削除、職種(営業・エンジニア・AI/ML…)と職位の内訳、リモート比率、AIエージェントがそのまま読める1文の `llmSummary`
- 求人レコード: 新規・削除された求人(希望すれば全件)。職種・職位を分類済み、応募URL付き

**使いどころ**: 営業(「営業を5人採り始めた=予算がある」)、採用競合分析、投資先の人員シグナル、AIエージェントからの呼び出し。

**入力**: 企業のホームページURL、採用ページURL、または `greenhouse:token` 形式。ホームページを渡すと採用ページを自動で探します。
**スケジュール実行**すると「前回以降に増えた・消えた求人だけ」が届きます。初回はベースライン(全件)です。
**分類はルールベース**(LLM不使用)なので、実行のたびに結果が変わることはありません。
**料金**: 会社要約1件ごと、求人レコード1件ごとの従量課金。

# Actor input Schema

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

One entry per company. Accepts a company homepage (https://gitlab.com), a career/ATS page URL (https://boards.greenhouse.io/gitlab, https://jobs.ashbyhq.com/openai, https://jobs.lever.co/leverdemo, https://apply.workable.com/acme, https://careers.smartrecruiters.com/Acme, https://acme.recruitee.com), or an explicit reference like `greenhouse:gitlab`. Homepages are resolved by scanning the site's career pages for a supported ATS.

## `sinceLastRun` (type: `boolean`):

Legacy switch. Prefer "mode". false = snapshot mode.

## `emitUnchangedJobs` (type: `boolean`):

Emit every currently open role on every run (change = `open`), not only new/removed ones. Useful when you want the full snapshot each time.

## `includeDescriptions` (type: `boolean`):

Adds a `description` field (plain text, max 4000 chars) to job records. Increases payload size.

## `mode` (type: `string`):

"monitor" (default): remember what was returned and emit only changes on later runs. "snapshot": return everything matching, every run, no memory.

## `watchlistId` (type: `string`):

Label for this watchlist (e.g. "roofing-wa"). Runs with the same ID share memory; use different IDs for independent watchlists.

## `emitBaseline` (type: `boolean`):

The first monitor run establishes a free baseline and returns only counts. Turn this on to also receive (and pay for) every current item on that first run.

## `maxJobsPerCompany` (type: `integer`):

Safety cap per company.

## `stateStoreName` (type: `string`):

Name of the key-value store used to remember the previous snapshot per company. Use different names to keep independent watchlists.

## `concurrency` (type: `integer`):

How many companies are checked in parallel (1-10).

## Actor input object example

```json
{
  "companies": [
    "https://boards.greenhouse.io/gitlab",
    "https://jobs.ashbyhq.com/openai",
    "https://jobs.lever.co/leverdemo"
  ],
  "sinceLastRun": true,
  "emitUnchangedJobs": false,
  "includeDescriptions": false,
  "mode": "monitor",
  "watchlistId": "default",
  "emitBaseline": false,
  "maxJobsPerCompany": 2000,
  "stateStoreName": "hiring-signal-state",
  "concurrency": 3
}
```

# Actor output Schema

## `results` (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 = {
    "companies": [
        "https://boards.greenhouse.io/gitlab",
        "https://jobs.ashbyhq.com/openai",
        "https://jobs.lever.co/leverdemo"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("handa-labs/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 = { "companies": [
        "https://boards.greenhouse.io/gitlab",
        "https://jobs.ashbyhq.com/openai",
        "https://jobs.lever.co/leverdemo",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("handa-labs/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 '{
  "companies": [
    "https://boards.greenhouse.io/gitlab",
    "https://jobs.ashbyhq.com/openai",
    "https://jobs.lever.co/leverdemo"
  ]
}' |
apify call handa-labs/hiring-signal-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,handa-labs/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/xvfbGI37orXm1CQBo/builds/TbWMWMeYIfZK83YJa/openapi.json
