# Hiring Signals | $0.02/company | Buying Triggers (`om_kh/company-hiring-signals`) Actor

Buying triggers from hiring data. Paste your target list and get back the companies that changed since last run: first sales hire, started hiring, new office, executive hire, accelerating, hiring freeze. Each scored 0-100 with the reasons. $0.02 per company looked up, first 5 free on every run.

- **URL**: https://apify.com/om\_kh/company-hiring-signals.md
- **Developed by:** [omar khandji](https://apify.com/om_kh) (community)
- **Categories:** Jobs, Lead generation, MCP servers
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 company profiles

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

## Company Hiring Signals — Buying Triggers From Hiring Data

> **"These 12 accounts just posted their first sales role."**
>
> Paste your target list. Get back the short list that changed, scored 0-100,
> with the reason for every point. **5 companies free on every run.**

Every other jobs Actor on this Store sells you *rows*. This one sells you the
**event**: who started hiring, who opened an office, who hired their first VP,
who froze. That is the thing a revenue team acts on.

### The change events

Run it once to set a baseline. Every run after that reports what moved:

| Trigger | What it means | Score |
| --- | --- | --- |
| `first_sales_hire` | Sales roles appear where there were none — the company decided to go to market | +30 |
| `started_hiring` | Was dormant, now hiring | +25 |
| `exec_hire` | A VP, Head of, Director or C-level title appears | +20 |
| `hiring_accelerating` | Posting at least 50% faster than last run | +20 |
| `new_location` | A city that was not there before — an office opening | +15 |
| `scaling_fast` | 10+ roles opened in 30 days and not slowing | +15 |
| `first_engineering_hire` | Engineering roles appear where there were none | +10 |
| `hiring_decelerating` | Posting at least 50% slower | −15 |
| `hiring_freeze` | Was hiring, now zero open roles | −30 |

A real run, on `vercel.com`:

```json
{
  "domain": "vercel.com",
  "intent_score": 100,
  "triggers": ["first_sales_hire", "new_location", "hiring_accelerating", "scaling_fast"],
  "roles_delta": 40,
  "roles_delta_pct": 100.0,
  "velocity_delta": 299.4,
  "new_functions": ["Sales"],
  "new_locations": ["Hybrid - London", "Remote - United States"],
  "intent_reasons": {
    "first_sales_hire": 30, "recent_activity": 25, "hiring_accelerating": 20,
    "new_location": 15, "scaling_fast": 15, "hiring_sales": 10,
    "open_roles": 8, "hiring_engineering": 5
  }
}
```

**`intent_reasons` is not decoration.** A score your sales team cannot explain is
a score they will not act on, so every point is attributed to a named component
and the total is exactly their sum.

Set **Only companies that changed** to get just the short list, and
**Minimum intent score** to raise the bar.

### Three things this Actor refuses to fake

**The first run invents nothing.** No history means every company comes back as
`is_new` with an empty `triggers` list. Telling a sales team that 200 accounts
all started hiring on the day they first ran it would destroy the product's
credibility in one run.

**A new city only counts as an office opening if the company was already
hiring.** A company going from zero roles to its first roles has "new" locations
by definition. That is not an expansion, and calling it one would be a lie.

**A company we cannot date is unknown, not zero.** Some ATS providers publish no
timestamps, so `roles_posted_30d` comes back `null` rather than `0` and no
velocity trigger fires. Reporting a hiring company as dormant is a false
negative you would act on.

### The problem this solves

A company's hiring is the cleanest public signal of what it is doing. A team
that opened nine engineering roles this month is building something. A company
that just posted its first three sales roles has decided to go to market — the
classic buying trigger.

That signal is already public, in every company's applicant tracking system. It
is just spread across nine different systems, none of which you can query
without first knowing which one a given company uses.

You give a domain. This Actor does the rest.

### Not another job scraper

If you want the jobs themselves, use the
[ATS Jobs API](https://apify.com/om_kh/ats-jobs-api) — same data, one row per
opening, $0.002 each.

This Actor is for the other question. Prospecting 500 accounts through a job
scraper means 40,000 rows you have to aggregate yourself. Here it is 500 rows,
already aggregated, already ranked.

### What one row looks like

```json
{
  "domain": "stripe.com",
  "ats": "greenhouse",
  "is_hiring": true,
  "total_open_roles": 545,
  "roles_posted_7d": 31,
  "roles_posted_30d": 118,
  "roles_posted_90d": 402,
  "roles_per_week_30d": 29.5,
  "latest_posting": "2026-08-03",
  "by_function": {"Engineering": 210, "Sales": 96, "Other": 239},
  "by_seniority": {"Senior": 288, "Mid": 190, "Junior": 67},
  "top_locations": {"New York": 88, "Remote - US": 74, "Dublin": 51},
  "top_titles": {"Account Executive": 9, "Software Engineer": 8},
  "hiring_engineering": true,
  "hiring_sales": true,
  "hiring_marketing": true,
  "hiring_support": false,
  "checked_at": "2026-08-04"
}
```

| Field | What it holds |
| --- | --- |
| `domain` | the company you asked about |
| `ats` | which system it publishes from, or `null` if it runs its own careers page |
| `is_hiring` | whether any role is currently open |
| `total_open_roles` | every open role right now |
| `roles_posted_7d` / `30d` / `90d` | how many opened in that window |
| `roles_per_week_30d` | hiring velocity — what the ranking sorts on |
| `by_function` / `by_seniority` | the shape of the hiring |
| `top_locations` / `top_titles` | where, and for what |
| `hiring_engineering` / `_sales` / `_marketing` / `_support` | quick booleans to filter on |
| `latest_posting` | date of the most recent opening |

### Two things this Actor refuses to fake

**A company we cannot date is reported as unknown, not as zero.** Some ATS
providers publish no timestamps. Reporting `roles_posted_30d: 0` for them would
mark a company that is actively hiring as dormant — a false negative you would
act on. Those companies come back with `null` and `dated_roles: 0`, and the
ranking places them below companies we could actually measure rather than
dropping them.

**A company with no public ATS still gets a row.** `is_hiring: false`,
`ats: null`. For a prospecting list, "we looked and they publish nothing" is a
usable answer; a missing row just looks like the lookup failed.

### Free tier — 5 companies on every run

**The first 5 companies of every run are free**, on every run you ever make, not
just the first. Point it at your own target list and see the real output before
a cent is billed.

The free allowance never covers more than half of a run, so a run of 6 companies
gets 3 free rather than 5 — that is what stops it becoming an unlimited free plan
for anyone willing to split one list into many tiny runs.

### Pricing — $0.02 per company looked up

**You are billed per company checked, not per row returned.** If you scan 200
companies and ask for only those that changed, you may get 12 rows back, and you
pay for the 200 lookups that produced them.

That is deliberate and it is the honest way round. The lookup is the work: we
resolve the ATS, read the board and compare it to last run whether or not the
answer turns out to be interesting. Billing per returned row would mean charging
you nothing for a scan that correctly told you "none of your accounts moved this
week" — which is a useful answer, and one you would stop trusting if we had an
incentive to invent movement.

Companies that turn out not to be hiring are billed for the same reason: the
negatives are only worth having because we actually looked.

Set **Maximum charge (USD)** to cap any single run.

### Where the data comes from

Each company's own applicant tracking system, through the public endpoint that
system already publishes: **Greenhouse, Lever, Ashby, Workday, SmartRecruiters,
Rippling, Breezy HR, Personio, BambooHR**.

No cookies, no logins, no proxies, no third-party aggregator. Nothing here can
be blocked, because none of it is scraping — it is the employers' own public
job feeds, read as published.

### Limits

- **200 companies per run.** Each one costs at least two HTTP round trips.
- **25 companies per request** in HTTP mode, so a request finishes inside
  Apify's five-minute ceiling.
- Velocity is computed over dated roles only.

### Questions

#### Which companies are hiring right now?

Pass your target list and read `is_hiring` and `roles_posted_30d`. Companies with
no public applicant tracking system come back with `ats: null` and
`is_hiring: false` rather than being dropped, so a prospecting list stays the
length you gave it.

#### How do I turn hiring data into a sales trigger?

Run it on a schedule against the same list. From the second run on, `triggers`
carries the events — `first_sales_hire`, `started_hiring`, `new_location`,
`exec_hire`, `hiring_accelerating`. Set **Only companies that changed** and you
get just the accounts worth a call this week.

#### Can I use this for ICP scoring?

Yes, and the score is meant to be argued with. `intent_score` is 0-100 and
`intent_reasons` breaks out every point by name, so you can drop the components
you disagree with and re-score on the raw fields — open roles, roles posted in
7/30/90 days, roles per week, functions, locations, titles.

#### What is a hiring signal worth as buying intent?

A company posting its first sales role has decided to go to market. One opening
an office in a new city has budget. One that just hired a VP has a new decision
maker. Those are the three events this Actor weights highest, and they are public
information every company publishes itself.

#### How is this different from a job scraper?

A job scraper gives you rows. Scanning 500 accounts through one means 40,000 job
rows you have to aggregate yourself. This returns 500 rows, one per company,
already aggregated and ranked — or just the dozen that changed.

#### Does it work without a list of companies?

No. This Actor scores companies you name. If you want to discover jobs rather
than score accounts, use the
[ATS Jobs API](https://apify.com/om_kh/ats-jobs-api).

#### How fresh is the data?

It is read live from each company's own applicant tracking system at the moment
you run it — not from an index built earlier. 25 companies answer in about 25
seconds.

### Use it from an AI agent (MCP)

This Actor is a **Model Context Protocol server**. Point any MCP client at it and
the tool appears with its full input schema — no wrapper code:

```
https://om-kh--company-hiring-signals.apify.actor/mcp
```

```json
{
  "mcpServers": {
    "company_hiring_signals": {
      "url": "https://om-kh--company-hiring-signals.apify.actor/mcp",
      "headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
    }
  }
}
```

One tool, `company_hiring_signals`. It takes the same arguments as the Start form and returns the
same rows, so an agent and a `curl` user can never get different answers.

### Related scrapers

Same data, other sources — all with the same free tier, the same clean schema and the same pay-only-for-what-you-get billing:

| Actor |
|---|
| [ATS Jobs API | $2/1K | Any Company Domain, 50 Free](https://apify.com/om_kh/ats-jobs-api) |
| [Careers Page Scraper | $2/1K | Domain to Open Jobs](https://apify.com/om_kh/careers-page-scraper) |
| [Indeed Jobs Scraper - Listings, Companies & Salary](https://apify.com/om_kh/vigia-indeed-hiring-monitor) |
| [LinkedIn Jobs Scraper - Listings & Companies](https://apify.com/om_kh/vigia-linkedin-jobs-monitor) |
| [Glassdoor Reviews Scraper - Employer Ratings](https://apify.com/om_kh/vigia-glassdoor-review-monitor) |
| [LinkedIn Company Scraper - Followers & Headcount](https://apify.com/om_kh/vigia-linkedin-company-monitor) |

# Actor input Schema

## `companyDomains` (type: `array`):

The companies to score. Just the domain - stripe.com, figma.com. Companies with no public ATS still get a row saying so. Max 200 per run.

## `onlyTriggers` (type: `boolean`):

Return only companies with at least one change event since your last run - first sales hire, started hiring, new location, accelerating, executive hire. This is the short list a sales team works from. You are still charged per company looked up, because the lookup is the work.

## `minIntentScore` (type: `integer`):

Drop companies scoring below this (0-100). Every score comes with the reasons that produced it, so you can justify the cut-off.

## `trackChanges` (type: `boolean`):

Remember this run so the next one can report what changed. The first run on a list is a baseline: every company is reported as new, with no change events invented.

## `trackingId` (type: `string`):

Pin the history to a name of your choosing. Use it when your target list changes between runs but you want the same companies compared. Leave empty and the history follows the list itself.

## `maxTotalChargeUsd` (type: `number`):

Hard ceiling on what this run can bill you. The run stops delivering paid rows once it is reached.

## Actor input object example

```json
{
  "companyDomains": [
    "stripe.com",
    "figma.com",
    "ramp.com",
    "notion.so"
  ],
  "onlyTriggers": false,
  "minIntentScore": 0,
  "trackChanges": true,
  "maxTotalChargeUsd": 1
}
```

# Actor output Schema

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

Every row this run delivered, as JSON. You are billed only for rows that reach this dataset.

## `runSummary` (type: `string`):

What the run actually did: rows delivered, rows charged, free results granted, the exact amount billed, and any source that failed.

# 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 = {
    "companyDomains": [
        "stripe.com",
        "figma.com",
        "ramp.com",
        "notion.so"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("om_kh/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 = { "companyDomains": [
        "stripe.com",
        "figma.com",
        "ramp.com",
        "notion.so",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("om_kh/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 '{
  "companyDomains": [
    "stripe.com",
    "figma.com",
    "ramp.com",
    "notion.so"
  ]
}' |
apify call om_kh/company-hiring-signals --silent --output-dataset

```

## MCP server setup

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