# Hiring Signal Feed – Company Job Board Tracker (`eligible_fame/hiring-signal-feed`) Actor

Job postings API and hiring-signal scraper for Greenhouse, Lever, Ashby and SmartRecruiters job boards. Per company: open roles by department and seniority, new roles in 7/30 days, growth since last run, salary bands. For sales triggers, Clay, recruiters, VCs and AI agents (MCP).

- **URL**: https://apify.com/eligible_fame/hiring-signal-feed.md
- **Developed by:** [Offladder](https://apify.com/eligible_fame) (community)
- **Categories:** Jobs, Lead generation, Automation
- **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 rows

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

## Hiring Signal Feed: Job Postings API & Hiring Signals from Greenhouse, Lever, Ashby & SmartRecruiters

**Hiring Signal Feed** is a **job postings API and hiring-signals scraper** for sales, recruiting and investing. Give it companies or career-page URLs and it pulls every open role from their **Greenhouse, Lever, Ashby or SmartRecruiters** job board. You get back **one hiring-signal row per company**: open roles by department and seniority, new roles in the last 7 and 30 days, headcount growth since your last run, salary bands, and the ATS they use. Every job can also come back as a normalized row. It reads the official public job-board JSON APIs only (no LinkedIn scraping, no login, no proxies), and it costs **$0.01 per company signal**.

Built for **GTM engineers and SDR teams** (trigger-based outbound: "they're hiring 5 SDRs, so they need X"), **Clay** users who want hiring-signal enrichment, **recruiters and staffing agencies**, **VCs** tracking headcount momentum, and **job-board builders** who need a jobs feed.

### Why this Actor

- **Official public endpoints only.** It uses each ATS's documented public job-board JSON API. No login, no cookies, no proxies, no headless browser, so runs are fast and cheap.
- **Company-level signals, not just rows.** Department and seniority mix, 7- and 30-day new roles, remote share, salary bands.
- **Growth tracking built in.** A named key-value store remembers the previous run, so scheduled runs report `open_roles_change`, `roles_added_since_last_run`, `roles_closed_since_last_run`, and `is_new_since_last_run` on each job.
- **Auto-detects the platform** from a URL or a bare slug.
- **Fair pay-per-event pricing**, and runs stop cleanly at your max-charge limit.

### Input

| Field | Description |
|---|---|
| `companies` (required) | Board URLs (`https://job-boards.greenhouse.io/stripe`, `https://jobs.lever.co/spotify`, `https://jobs.ashbyhq.com/ramp`, `https://jobs.smartrecruiters.com/BoschGroup`), prefixed ids (`greenhouse:stripe`, `lever:palantir`, `ashby:notion`, `smartrecruiters:Experian`), or bare slugs (tried in order: Greenhouse → Lever → Ashby → SmartRecruiters). |
| `outputMode` | `both` (default), `companies`, or `jobs` |
| `titleKeywords` | Optional: keep only matching titles (e.g. `sales`, `SDR`). Signals are computed on the filtered set. |
| `includeDescriptions` | Add plain-text job descriptions (default off) |
| `maxJobsPerCompany` | Cap on job rows per company (0 = all). Company signals always use every job. |
| `snapshotStoreName` | Key-value store for growth tracking. Keep it the same across scheduled runs. |
| `maxConcurrency`, `requestDelaySecs` | Polite pacing (defaults 3 boards and 0.5 s between requests) |

```json
{ "companies": ["greenhouse:stripe", "https://jobs.lever.co/spotify", "ashby:ramp", "smartrecruiters:Experian"], "outputMode": "both" }
```

### Output

**Company row** (`row_type: "company"`)

```json
{
  "row_type": "company", "company": "Ramp", "company_slug": "ramp", "ats": "ashby",
  "open_roles": 167, "new_roles_7d": 12, "new_roles_30d": 47, "remote_roles": 150,
  "roles_by_department": {"Engineering": 60, "Sales": 30},
  "roles_by_seniority": {"mid/unspecified": 72, "manager": 51, "senior": 12},
  "previous_open_roles": 160, "open_roles_change": 7, "open_roles_change_pct": 4.4,
  "roles_added_since_last_run": 11, "roles_closed_since_last_run": 4,
  "jobs_with_salary": 160,
  "salary_bands": [{"currency": "USD", "interval": "year", "count": 150, "min": 90000, "max": 400000}],
  "board_url": "https://jobs.ashbyhq.com/ramp", "scraped_at": "2026-10-18T02:34:40+00:00"
}
```

(Illustrative values. See `samples/` for real output.)

**Job row** (`row_type: "job"`): `company, ats, job_id, title, department, seniority, location, remote, employment_type, posted_at, salary{min,max,currency,interval,source}, url, is_new_since_last_run, description?`

**Error row** (`row_type: "error"`): written for an entry that can't be processed. Error rows are **never charged**. Codes:

- `BOARD_NOT_FOUND`: no public Greenhouse/Lever/Ashby/SmartRecruiters board for this company.
- `INVALID_COMPANY`: malformed entry, an unsupported ATS (Workday, iCIMS, Workable, BambooHR) or a non-board URL. The `message` says how to fix it.
- `INVALID_INPUT`: the whole input is unusable (e.g. empty `companies`, unknown `outputMode`). The run stops before any result is charged.
- `INTERNAL_ERROR`: unexpected failure for one company; the rest of the run continues.

### Pricing (pay per event)

| Event | Price |
|---|---|
| Actor start | $0.02 per run at the default 1 GB memory (charged by Apify per GB of memory) |
| Job row | $0.0015 ($1.50 / 1,000) |
| Company hiring-signal row | $0.01 |

Tip: use `outputMode: "companies"` to watch 1,000 companies for about $10 per run.

### Data notes and limitations

- **Coverage:** Greenhouse, Lever (US and EU), Ashby, SmartRecruiters. Workday, iCIMS and others are not supported yet.
- **Salary:**
  - Lever and Ashby: structured, when the company publishes it.
  - Greenhouse: parsed from description text, best-effort.
  - SmartRecruiters: the list API exposes no salary.
- **Seniority:** inferred from the job title (SmartRecruiters also provides experience level). It's a heuristic.
- **Posting dates:**
  - Greenhouse uses `first_published`, falling back to `updated_at`.
  - Lever uses `createdAt`.
  - Ashby uses `publishedAt`.
  - SmartRecruiters uses `releasedDate`.
- **Company names:** for Lever and Ashby, the name is the board slug in title case.
- **Growth metrics** appear from the second run onward with the same snapshot store.

### Legal

The Actor reads only job postings that companies publish through their ATS's public job-board API for syndication. It collects no personal profiles. You are responsible for how you use the data.

### Use cases

- **Sales triggers:** set `titleKeywords: ["SDR","account executive"]` to find accounts building out a sales team. Use `["data engineer"]` if you sell data tools.
- **Account scoring in Clay / HubSpot / Salesforce:** pull `open_roles`, `new_roles_30d` and `open_roles_change_pct` as columns.
- **Recruiting and staffing agencies:** get a daily list of companies opening roles in your niche, with the posting URLs.
- **Investors and market research:** track hiring velocity and department mix across a portfolio or a list of competitors.
- **Job boards and aggregators:** run with `outputMode: "jobs"` for clean, de-duplicated postings with salary where it's published.

### API & integration examples

**Python**

```python
from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("eligible_fame/hiring-signal-feed").call(run_input={
    "companies": ["greenhouse:stripe", "ashby:ramp", "lever:palantir"],
    "outputMode": "companies", "titleKeywords": ["sales", "SDR"]})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["company"], row.get("open_roles"), row.get("new_roles_30d"))
```

**Node.js**

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('eligible_fame/hiring-signal-feed').call({ companies: ['greenhouse:discord'], outputMode: 'both' });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.filter(r => r.row_type === 'company'));
```

**cURL**

```bash
curl -X POST "https://api.apify.com/v2/acts/eligible_fame~hiring-signal-feed/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"companies":["greenhouse:stripe","ashby:ramp"],"outputMode":"companies"}'
```

**No-code:** the Apify app works in **n8n, Make and Zapier** (Run Actor, then Get dataset items). For a weekly hiring-signal sheet, schedule the Actor in Apify with the same `snapshotStoreName` so each run reports growth since the last one.

### Use it from AI agents and MCP clients (Claude, Cursor, ChatGPT, n8n AI Agent)

This Actor works as a tool in **Apify's MCP server** (`https://mcp.apify.com`), so an LLM agent can ask "which of these companies are hiring SDRs right now?" and get structured rows back.

**Add only this Actor as an MCP tool** (Claude Desktop, Cursor, VS Code, Windsurf):

```json
{
  "mcpServers": {
    "hiring-signals": { "url": "https://mcp.apify.com?tools=eligible_fame/hiring-signal-feed" }
  }
}
```

On first connection the client opens an Apify sign-in (OAuth). Alternatively add the header `"Authorization": "Bearer <APIFY_TOKEN>"`. With the default server (`https://mcp.apify.com`) the agent can also find it via `search-actors` ("hiring signals Greenhouse") and run it with `call-actor`.

**Prompt examples for an agent**

- "Use hiring-signal-feed on greenhouse:stripe, ashby:ramp and lever:palantir in `companies` mode and tell me who added the most roles in the last 30 days."
- "Run hiring-signal-feed for these 50 career-page URLs with titleKeywords \['SDR','account executive'] and return only companies with new_roles_30d ≥ 3."

**Tips for agents**

- Use `outputMode: "companies"` for compact, cheap answers (one row per company, $0.01 each); switch to `jobs` only when the posting list is needed.
- Set `maxTotalChargeUsd` in the MCP `callOptions` to cap spend per call.
- Read `row_type`: `company`, `job` or `error`. Error rows carry a human-readable `message` the agent can relay, and are free.
- Pay-per-event pricing makes it usable with Apify **agentic payments** (e.g. Skyfire), so an agent without an Apify account can pay per run.

### FAQ

**Does it scrape LinkedIn or Indeed?** No. It reads only the public job-board APIs that Greenhouse, Lever, Ashby and SmartRecruiters publish for career pages. Those are the employer's own systems of record, and they're often fresher than aggregators.

**How do I find a company's board?** Paste its careers URL, or just the name or slug. The Actor tries each ATS in turn. If no board is found, you get a free `error` row.

**How is growth calculated?** Each run saves a snapshot to a named key-value store. The next run compares job IDs against it to compute roles added, roles closed and percentage change.

**How much does monitoring 500 companies cost?** In `companies` mode it's about $0.02 + 500 × $0.01 ≈ $5 per run. Set a maximum charge per run in the Apify console.

**Are salaries included?** Yes, where the employer publishes them (common in US pay-transparency states). They come from structured fields or are parsed from the posting.

**What if I pass a Workday URL, a typo or an empty list?** You get a free `error` row explaining what's wrong (`INVALID_COMPANY` / `BOARD_NOT_FOUND`). If nothing in the input is usable, the run stops with an `INVALID_INPUT` message before any result is charged. Common mistakes are fixed automatically: a single text block instead of a list, duplicates, numbers sent as strings.

**Is this legal?** It reads public, employer-published job listings through endpoints intended for public career pages. It collects no personal data.

# Actor input Schema

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

One per line. Accepts board URLs (job-boards.greenhouse.io/stripe, jobs.lever.co/spotify, jobs.ashbyhq.com/ramp, jobs.smartrecruiters.com/BoschGroup), prefixed ids (greenhouse:stripe, lever:palantir, ashby:notion, smartrecruiters:Experian), or a bare slug (auto-detected: Greenhouse → Lever → Ashby → SmartRecruiters). Unsupported or malformed entries return a free error row; they are never charged.

## `outputMode` (type: `string`):

jobs = one row per job; companies = one hiring-signal row per company; both = both.

## `titleKeywords` (type: `array`):

Keep only jobs whose title contains any of these (case-insensitive), e.g. 'sales', 'SDR'. Company signals are computed on the filtered set.

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

Add plain-text descriptions to job rows (larger output). Not available for SmartRecruiters list endpoint.

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

0 = no limit. Company signals always use all jobs.

## `snapshotStoreName` (type: `string`):

Named key-value store used to remember the previous run for growth/added/closed metrics. Use the same name across scheduled runs.

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

How many company boards to fetch in parallel (1-10).

## `requestDelaySecs` (type: `number`):

Global polite pacing across all requests (minimum 0.25).

## Actor input object example

```json
{
  "companies": [
    "greenhouse:stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp",
    "smartrecruiters:Experian"
  ],
  "outputMode": "both",
  "includeDescriptions": false,
  "maxJobsPerCompany": 0,
  "snapshotStoreName": "hiring-signal-snapshots",
  "maxConcurrency": 3,
  "requestDelaySecs": 0.5
}
```

# Actor output Schema

## `companies` (type: `string`):

One row per company: open roles by department/seniority, new roles 7d/30d, growth since last run, salary bands, ATS.

## `jobs` (type: `string`):

One normalized row per job posting.

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

All rows in the default dataset, including error rows.

# 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": [
        "greenhouse:stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ramp",
        "smartrecruiters:Experian"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("eligible_fame/hiring-signal-feed").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": [
        "greenhouse:stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/ramp",
        "smartrecruiters:Experian",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("eligible_fame/hiring-signal-feed").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": [
    "greenhouse:stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp",
    "smartrecruiters:Experian"
  ]
}' |
apify call eligible_fame/hiring-signal-feed --silent --output-dataset

```

## MCP server setup

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

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/57bnqlTfJ5gJA1xnx/builds/PFUdO5ZwngzaBgmER/openapi.json
