# Hiring Signals: Who Is Hiring in Your Niche (`brightknot/hiring-signals`) Actor

Returns one row per company with its open roles that match your niche keywords, new roles in the last N days, change since the last run and a 0 to 100 hiring score. Reads Workday, Greenhouse, Lever, Ashby, Workable, SmartRecruiters and Recruitee career pages.

- **URL**: https://apify.com/brightknot/hiring-signals.md
- **Developed by:** [Brightknot](https://apify.com/brightknot) (community)
- **Categories:** Lead generation, Jobs, Business
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 40.7% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 company analyseds

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

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

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

Give it a list of companies and a few keywords that describe your niche. It reads each company's public careers page and returns **one row per company**: how many open roles match your niche, how many of those are new, how the count changed since your last run, and a **hiring score from 0 to 100** so you can sort the list.

Supported job boards: **Workday, Greenhouse, Lever, Ashby, Workable, SmartRecruiters and Recruitee**.

### For AI agents

Use this Actor when you have a list of target companies and want to know which of them are hiring for a skill, team or tool right now, ranked by hiring intent. Not for individual job rows. Use ATS Jobs Monitor (brightknot/ats-jobs-monitor) for one row per job.

- **Actor id:** `brightknot/hiring-signals`
- **Required input:** `companies`
- **Call:** MCP `call-actor` with the input below, then `get-dataset-items`. Over HTTP: `POST https://api.apify.com/v2/acts/brightknot~hiring-signals/run-sync-get-dataset-items` with the input as the JSON body.
- **Cap the spend** with `maxTotalChargeUsd` in the call options, not in the input.

#### Example input

```json
{
  "companies": ["https://boards.greenhouse.io/airbnb", "https://jobs.ashbyhq.com/ramp", "lever:palantir"],
  "keywords": ["security", "data engineer"],
  "countries": ["US", "GB", "REMOTE"],
  "lookbackDays": 30,
  "minOpenRoles": 0,
  "monitorMode": false
}
```

#### Example output row

Trimmed to the main fields. See the Output schema tab for every field.

```json
{
  "company": "Airbnb",
  "ats": "greenhouse",
  "careers_url": "https://job-boards.greenhouse.io/airbnb",
  "total_open_roles": 2,
  "matching_roles": 1,
  "new_matching_roles_in_lookback": 1,
  "hiring_score": 23,
  "top_departments": ["Engineering (1)"],
  "top_locations": ["Remote, US (1)"],
  "sample_titles": ["Senior Security Engineer"],
  "signal_summary": "Airbnb has 1 open role matching security or engineer out of 2 in total, 1 posted in the last 30 days, and this is the first run, so there is no growth figure yet."
}
```

Rows are sorted by `hiring_score`, highest first.

#### Cost per call

| Event | Price | When |
|---|---|---|
| Company analysed | USD 0.01 | per company row returned |
| Actor start | USD 0.005 | once per run |

The example input costs at most USD 0.035 (3 companies plus the start).

#### Limits

- Plain company names are auto-detected on 6 ATS. Workday needs the careers URL.
- Companies below `minOpenRoles` (default 1) are left out and not charged. Set 0 to get a row for every company found.
- Growth needs at least two runs with the same `monitorName`; on the first run the change fields are empty.
- Usually finishes in under a minute for 10 companies. `matchDescription` on Workday is slower.
- No credentials needed. Runs with limited permissions.

### Who it is for

- **Sales and business development teams** who sell to companies that are building a certain kind of team. A company that opens 12 security roles in a month is likely to buy security tools. A company hiring Salesforce admins is likely to buy Salesforce services.
- **Account-based marketing:** rank your target account list by hiring activity in your area each week.
- **Recruiting agencies:** see which clients and prospects are hiring in the roles you place.
- **Investors and analysts:** track hiring at a list of companies over time.

### Input

| Field | What it does |
|---|---|
| Companies | One per line. A careers URL (`https://boards.greenhouse.io/airbnb`, `https://jobs.lever.co/palantir`, `https://jobs.ashbyhq.com/ramp`, `https://apply.workable.com/blueground`, `https://bunq.recruitee.com`, `https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite`), `ats:slug` (`lever:palantir`, `workday:nvidia/wd5/NVIDIAExternalCareerSite`), or a company name to auto-detect. Workday needs the careers URL. |
| Niche keywords | Words that define your niche, for example `security`, `salesforce`, `data engineer`. A role matches when a keyword appears as a whole word in its title, department or team. Plurals match. Leave empty to count all roles. |
| Exclude title words | Roles with these words in the title are not counted (for example `intern`). |
| Countries | Only count roles in these countries (`US`, `GB`, `AE`). Add `REMOTE` to also count remote roles anywhere. |
| Also match keywords in job descriptions | Also count a role when a keyword is in its description. Useful for tools such as Salesforce or Snowflake that often appear only in the description. |
| Lookback window (days) | Matching roles posted within this many days count as new. Default 30. |
| Minimum matching roles | Companies with fewer matching roles are left out of the results and not charged. Default 1. |
| Monitor mode | Remember each company's count and report the change on the next run. On by default. |
| Monitor name | A separate memory for each list or schedule. |
| Max jobs read per company | Read at most this many jobs per board. 0 means all. |

Example input:

```json
{
  "companies": [
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "https://boards.greenhouse.io/airbnb",
    "https://jobs.ashbyhq.com/ramp",
    "lever:palantir",
    "careem"
  ],
  "keywords": ["security", "soc analyst", "iam"],
  "excludeKeywords": ["intern"],
  "lookbackDays": 30,
  "minOpenRoles": 2,
  "monitorName": "security-accounts"
}
```

### Output (one row per company)

```json
{
  "company": "Example Co",
  "ats": "greenhouse",
  "careers_url": "https://job-boards.greenhouse.io/exampleco",
  "total_open_roles": 120,
  "matching_roles": 14,
  "new_matching_roles_in_lookback": 6,
  "lookback_days": 30,
  "previous_matching_roles": 9,
  "matching_roles_change": 5,
  "score_volume": 35.6,
  "score_recency": 35.0,
  "score_growth": 13.9,
  "hiring_score": 84,
  "top_departments": ["Security (9)", "Engineering (4)", "IT (1)"],
  "top_locations": ["London, GB (6)", "Remote, US (5)", "Dubai, AE (3)"],
  "sample_titles": ["Senior Security Engineer", "SOC Analyst", "IAM Engineer", "Security Architect", "Detection Engineer"],
  "signal_summary": "Example Co has 14 open roles matching security, soc analyst or iam out of 120 in total, 6 posted in the last 30 days, up 5 since 2026-10-04.",
  "keywords": ["security", "soc analyst", "iam"],
  "first_seen": "2026-09-06T07:00:00Z",
  "last_run": "2026-10-11T07:00:00Z",
  "previous_run": "2026-10-04T07:00:00Z",
  "input": "https://boards.greenhouse.io/exampleco"
}
```

This example is illustrative. Rows are sorted by `hiring_score`, highest first.

| Field | Meaning |
|---|---|
| `total_open_roles` | All open roles on the board (before your keywords and countries). |
| `matching_roles` | Open roles that match your niche. |
| `new_matching_roles_in_lookback` | Matching roles posted within the lookback window. |
| `previous_matching_roles`, `matching_roles_change` | The count from the last run of the same monitor and the difference. Empty on the first run. |
| `top_departments`, `top_locations` | The most common departments and locations among matching roles, with counts. |
| `sample_titles` | Up to 5 matching titles, newest first. |
| `first_seen` | When this monitor first analysed the company. |
| `last_run`, `previous_run` | When this row was produced, and when the previous count was taken. |

A run summary, including companies that were not found or were below the minimum, is saved as `SUMMARY` in the run's key-value store.

### How the hiring score works

The score is the sum of three parts, rounded to a whole number between 0 and 100.

| Part | Points | Formula | In words |
|---|---|---|---|
| Volume | up to 40 | `40 x min(1, ln(1 + matching_roles) / ln(21))` | More matching roles score higher. Full marks at 20 or more. The first few roles count the most. |
| Recency | up to 35 | `35 x min(1, ln(1 + new_matching_roles_in_lookback) / ln(6))` | Roles posted inside the lookback window. Full marks at 5 or more. |
| Growth | up to 25 | `25 x clamp((matching_roles - previous) / max(previous, 5), 0, 1)` | Increase since the last run. Full marks when the count doubles (or grows by 5 when it was 5 or fewer). No points on the first run, when the count fell, or after you change the keywords or countries. |

Some reference points: 1 matching role posted this week scores about 23 on a first run. 20 roles with 5 new scores 75 on a first run, and 100 if the count doubled since the last run.

### Pricing

$0.01 per company analysed, plus $0.005 per run.

You are charged for each company row in the results. Companies that are not found, or that have fewer matching roles than your minimum, are not returned and not charged. A weekly check of 100 target accounts costs at most $1.005.

### How it works

1. Each company is matched to its job board. URLs and `ats:slug` entries are used as given. Plain names are tried on Greenhouse, Lever, Ashby, Workable, Recruitee and SmartRecruiters in turn.
2. The Actor reads the public job feed that the job board provides for the company's own careers page. On Workday it reads the job list 20 at a time and opens a job's page only when it needs the description, the country or the exact date.
3. Roles are matched against your keywords, counted and scored.
4. In monitor mode, the matching count for each company is saved in a named key-value store (`hiring-signals-state`) and compared on the next run.

### Data and privacy

It only reads job postings that companies publish on their own careers pages. No login, no cookies, no proxies. It does not collect names, emails or any other personal data, and it does not read hiring manager or recruiter details.

### Notes and limits

- Some companies use a custom domain for their careers page. If a name does not auto-detect, paste the job board link from an "Apply" button instead.
- Workday shows at most 2,000 jobs per board, so very large Workday employers are counted up to 2,000. Workday does not publish departments, so `top_departments` is empty for Workday companies. Workday gives dates as "Posted 3 Days Ago", so they have day precision.
- A role that has no posting date is counted in `matching_roles` but not as new.
- Keyword matching is on whole words. `engineer` matches "Engineers" but not "Engineering"; add both if you want both.

# Actor input Schema

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

Companies to check, one per item. Use a careers page URL (https://boards.greenhouse.io/airbnb, https://jobs.lever.co/palantir, https://jobs.ashbyhq.com/ramp, https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite), 'ats:slug' (lever:palantir, workday:nvidia/wd5/NVIDIAExternalCareerSite) or a company name to auto-detect the ATS. Workday needs the careers URL.

## `keywords` (type: `array`):

Words that define your niche, e.g. security, salesforce, data engineer. A role matches when any of them appears as a whole word in its title, department or team (plurals match too, so 'engineer' also finds 'Engineers'). Leave empty to count every open role.

## `excludeKeywords` (type: `array`):

Roles whose title contains any of these words are not counted, e.g. intern, recruiter.

## `countries` (type: `array`):

Only count roles in these countries. 2-letter codes, e.g. US, GB, AE. Add REMOTE to also count remote roles anywhere. Leave empty for all countries.

## `matchDescription` (type: `boolean`):

Count a role when a keyword appears in its description, not only its title. Finds roles that use a tool (e.g. Salesforce) without naming it in the title. Slower on Workday, where each job page is read separately. SmartRecruiters lists have no descriptions, so this has no effect there.

## `lookbackDays` (type: `integer`):

Matching roles posted within this many days count as new. Feeds new_matching_roles_in_lookback and the recency part of the score.

## `minOpenRoles` (type: `integer`):

Companies with fewer matching open roles are left out of the results and are not charged. Set 0 to get a row for every company that was found.

## `monitorMode` (type: `boolean`):

Remembers each company's matching role count between runs and reports the change. The growth part of the score needs this. Schedule it weekly to track who is ramping up. Rows are returned either way.

## `monitorName` (type: `string`):

Keeps a separate memory for each list or schedule. Use a different name for each watch list. If you change the keywords or countries under the same name, growth starts again from the next run.

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

Read at most this many jobs from each company's board. 0 means all. Workday boards show at most 2,000 jobs. Counts are based on the jobs read.

## `maxConcurrency` (type: `integer`):

How many requests to run at the same time across all companies.

## Actor input object example

```json
{
  "companies": [
    "https://boards.greenhouse.io/airbnb",
    "https://jobs.ashbyhq.com/ramp",
    "lever:palantir",
    "greenhouse:stripe"
  ],
  "keywords": [
    "engineer"
  ],
  "excludeKeywords": [
    "intern",
    "recruiter"
  ],
  "countries": [
    "US",
    "GB",
    "REMOTE"
  ],
  "matchDescription": false,
  "lookbackDays": 30,
  "minOpenRoles": 0,
  "monitorMode": false,
  "monitorName": "default",
  "maxJobsPerCompany": 500,
  "maxConcurrency": 8
}
```

# Actor output Schema

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

One row per company with at least minOpenRoles matching roles, highest hiring score first. Read the rows with GET on this URL.

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

Every company with its status, including boards not found and companies below the minimum.

# 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/airbnb",
        "https://jobs.ashbyhq.com/ramp",
        "lever:palantir",
        "greenhouse:stripe"
    ],
    "keywords": [
        "engineer"
    ],
    "minOpenRoles": 0,
    "monitorMode": false,
    "maxJobsPerCompany": 500
};

// Run the Actor and wait for it to finish
const run = await client.actor("brightknot/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 = {
    "companies": [
        "https://boards.greenhouse.io/airbnb",
        "https://jobs.ashbyhq.com/ramp",
        "lever:palantir",
        "greenhouse:stripe",
    ],
    "keywords": ["engineer"],
    "minOpenRoles": 0,
    "monitorMode": False,
    "maxJobsPerCompany": 500,
}

# Run the Actor and wait for it to finish
run = client.actor("brightknot/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 '{
  "companies": [
    "https://boards.greenhouse.io/airbnb",
    "https://jobs.ashbyhq.com/ramp",
    "lever:palantir",
    "greenhouse:stripe"
  ],
  "keywords": [
    "engineer"
  ],
  "minOpenRoles": 0,
  "monitorMode": false,
  "maxJobsPerCompany": 500
}' |
apify call brightknot/hiring-signals --silent --output-dataset

```

## MCP server setup

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