# Hiring Signals Job Feed (multi-ATS) (`signalroute/hiring-signals-job-feed`) Actor

Paste company domains, get every open job from Greenhouse, Lever, Ashby, Workable and SmartRecruiters in one normalized feed, with new-since-last-run flags. Public feeds only: no login, no proxies.

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

## Pricing

from $1.50 / 1,000 job saveds

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 Signals Job Feed: Greenhouse, Lever, Ashby, Workable and SmartRecruiters

*By **SignalRoute**: data feeds that route buying signals to your team.*

**Turn a list of company domains into one clean, normalized job feed, and see which roles are new since your last run.**

Paste `stripe.com`, `linear.app` or `huggingface.co`. The Actor finds which applicant tracking system (ATS) each company uses and pulls every open role from that ATS's **public JSON job feed**. Each job comes back in one consistent schema. Use it for sales triggers ("they just opened 5 RevOps roles"), investor or competitor monitoring, recruiting research, or job alerts.

- ✅ **Auto-detects the ATS:** Greenhouse, Lever, Ashby, Workable and SmartRecruiters
- ✅ **Public feeds only:** no login, no cookies, no residential proxies, and a low memory footprint (256–512 MB)
- ✅ **New-since-last-run:** jobs are flagged `is_new`, and you can output only new jobs on a schedule
- ✅ **Fails soft:** a company that can't be resolved becomes an error row with a reason code, and the rest of the run continues
- ✅ **Run summary:** success rate, per-platform counts and error counts are saved to `RUN_SUMMARY`

### ⚡ Start in one click: ready-made example tasks

| Example task | What it does |
|---|---|
| [**AI startups hiring: daily new roles**](https://apify.com/signalroute/hiring-signals-job-feed/examples/ai-startups-hiring-daily) | OpenAI, Anthropic, Hugging Face, Linear, Ramp, Mistral, Perplexity and Cursor. Set to `onlyNew`, so after the first run you see only roles that were just opened. |
| [**Fintech sales & GTM hiring**](https://apify.com/signalroute/hiring-signals-job-feed/examples/fintech-sales-gtm-hiring) | Stripe, Ramp, Brex, Plaid, Affirm, Chime and SoFi. Use it to spot new sales, RevOps and GTM roles as buying signals. |
| [**Competitor hiring tracker (5 domains)**](https://apify.com/signalroute/hiring-signals-job-feed/examples/competitor-hiring-tracker) | A 5-company template. Swap in your competitors, add a daily schedule, and get a feed of their new roles. |

Open a task, press **Try for free**, and change the domains to your own list.

### Who uses it, and for what

- **Sales and RevOps teams:** "Acme just opened 4 SDR roles and a Head of RevOps" is a strong outbound trigger. Push new jobs to a CRM, Slack or Clay with an Apify integration or webhook.
- **Investors and analysts:** track headcount growth by department across a portfolio or a watchlist of startups, every day.
- **Competitive intelligence:** see which teams your competitors are building (AI, enterprise sales, new regions) before they announce anything.
- **Recruiters and agencies:** get one clean feed of new roles at your target accounts, instead of checking dozens of career pages by hand.
- **Job seekers and job boards:** build alerts or a niche job board from companies' own ATS feeds.

### 💰 Pricing: pay only for results

This Actor uses **pay per event**. You pay for companies found and jobs delivered, not for compute time.

| Event | Price | When it's charged |
|---|---|---|
| Actor start | $0.001 | Once per run (per GB of memory; the default 512 MB counts as one) |
| Company resolved | $0.01 | Once per company whose job board was found and fetched |
| Job saved | $0.0015 | Per job row in the dataset (that's $1.50 per 1,000 jobs) |

- **Error rows are free.** If a company can't be resolved, you get an error row with a reason code and you pay nothing for it.
- **Examples:** 10 companies × 30 jobs ≈ **$0.55**. 50 companies × 40 jobs ≈ **$3.50** for a full run. A daily `onlyNew` watch of 20 companies costs about **$0.20 a day** plus $0.0015 per new job.
- **Set a spending cap:** use *Max cost per run* in the run options. The Actor stops cleanly when the cap is reached and marks skipped companies `BUDGET_REACHED`.
- Apify's free plan credit covers your first test runs.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `domains` | array of strings | (required) | Company domains or URLs. Optional per-line override: `domain\|platform:boardToken` |
| `platforms` | array | all | Limit detection to `greenhouse`, `lever`, `ashby`, `workable` or `smartrecruiters` |
| `maxJobsPerCompany` | integer | 200 | Cap per company. Newest first for Greenhouse, Ashby and Workable. 0 means no cap |
| `onlyNew` | boolean | false | Output only jobs not seen in earlier runs |
| `stateStoreName` | string | `hiring-signals-state` | Named key-value store that remembers seen jobs. Use one name per watchlist |
| `includeDescription` | boolean | true | Add a plain-text `description_snippet` |
| `snippetLength` | integer | 300 | Snippet length in characters |
| `scanCareersPage` | boolean | true | Read the homepage and `/careers` to find the exact board link. When off, the Actor only guesses from the domain |
| `maxConcurrency` | integer | 5 | Companies processed in parallel |
| `requestTimeoutSecs` | integer | 20 | Timeout for each HTTP request |

```json
{
  "domains": ["stripe.com", "linear.app", "huggingface.co", "ubisoft.com|smartrecruiters:Ubisoft2"],
  "maxJobsPerCompany": 100,
  "onlyNew": false
}
```

**Overrides.** When a company's board token differs from its domain, or it uses a career page the Actor can't detect, pin it like this: `doordash.com|greenhouse:doordashusa` or `bosch.com|smartrecruiters:BoschGroup`.

### Output (dataset)

One row per job (illustrative values):

```json
{
  "company": "Stripe",
  "domain": "stripe.com",
  "platform": "greenhouse",
  "job_id": "7212345",
  "title": "Account Executive, Enterprise",
  "location": "San Francisco, CA",
  "department": "Sales",
  "url": "https://stripe.com/jobs/search?gh_jid=7212345",
  "posted_at": "2026-10-28T17:02:11Z",
  "description_snippet": "Stripe is a financial infrastructure platform...",
  "is_new": true
}
```

When a company fails, it produces one **error row** with `domain`, `error_code`, `error` and `scraped_at`. Error rows are never charged.

| error_code | Meaning |
|---|---|
| `INVALID_INPUT` | The line couldn't be parsed as a domain or override |
| `ATS_NOT_FOUND` | No supported ATS board was found. The company may self-host or use Workday or iCIMS |
| `BOARD_NOT_FOUND` | The board token from an override or a careers link returned 404 |
| `FETCH_FAILED` | Network errors or timeouts while fetching. Retry the run |
| `BUDGET_REACHED` | The run's maximum charge was reached, so this company was skipped |
| `INTERNAL_ERROR` | An unexpected parsing error. Please report it |

The **`RUN_SUMMARY`** record in the default key-value store holds the input count, resolved count, success rate, jobs found, saved and new, per-platform and per-error counts, and a status for each company.

### Scheduled monitoring (new jobs only)

1. Set `onlyNew: true` and choose a `stateStoreName`, for example `my-watchlist`.
2. Create a daily Apify Schedule.
3. Each run outputs only roles that weren't there last time. Jobs that disappear are dropped from the state, so a role that gets reposted shows up as new again.

The state is saved only for companies that were fully fetched. If a company was cut short by `maxJobsPerCompany` or the budget, its earlier state is merged in rather than replaced.

### How detection works

1. If you give an override, the Actor uses it.
2. Otherwise it scans the homepage, `/careers` and `/jobs` for embedded board links (`boards.greenhouse.io/x`, `jobs.lever.co/x`, `jobs.ashbyhq.com/x`, `apply.workable.com/x`, `jobs.smartrecruiters.com/x`).
3. If no link is found, it guesses the slug from the domain (`stripe`, then `stripeai`, `stripehq` and so on) on each ATS's public API. Guessed boards with 0 open jobs are ignored, because placeholder accounts are common.

### Memory and speed

| Workload | Peak memory (measured) | Time |
|---|---|---|
| Small watchlist (baseline: runtime + SDK) | ~130 MB | under 1 min on a normal network |
| 16 companies, up to 1,000 jobs each, concurrency 10 (5,034 jobs) | **198 MB** | ~2 min |

The **512 MB default** leaves plenty of headroom. 256 MB works for small watchlists with `maxConcurrency` ≤ 5. The Actor uses plain HTTP only, with no browser, so it costs very little to run.

### Limits and roadmap

- **Supported:** Greenhouse, Lever, Ashby, Workable and SmartRecruiters.
- **Not yet supported:** Workday, iCIMS, Taleo, Teamtailor and self-hosted careers pages. These companies return `ATS_NOT_FOUND`. *Workday support is next on the roadmap. Many large enterprises use it, and its public `/wday/cxs/` job-search endpoint needs per-tenant discovery.*
- **SmartRecruiters descriptions** come from one extra public request per job, which makes these runs slower. Turn off `includeDescription` for the fastest runs.
- **Caps:** Lever and SmartRecruiters caps follow the API's order instead of strict newest-first.
- **Accuracy:** auto-detection resolved 83% of a 30-company benchmark with no overrides. Add `domain|platform:token` overrides to fix the rest.

### Support

Found a company that should resolve but doesn't? Open an issue on the Actor's **Issues** tab and include the domain. SignalRoute usually adds the fix or an override hint within a few days.

### Legal

This Actor reads only public, unauthenticated job-board APIs that the ATS vendors publish for embedding job listings. It does not collect personal data.

# Actor input Schema

## `domains` (type: `array`):

Company website domains or URLs, one per line, e.g. `stripe.com` or `https://linear.app`. Optional override when auto-detection can't find the board: `domain|platform:boardToken`, e.g. `ubisoft.com|smartrecruiters:Ubisoft2`.

## `platforms` (type: `array`):

Only detect / fetch these ATS platforms. Leave empty for all.

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

Cap on jobs saved per company (0 = no cap). Keeps costs predictable for very large employers.

## `onlyNew` (type: `boolean`):

Output only jobs not seen in previous runs with the same state store. Ideal for a daily Schedule - you only pay for new jobs (company events still apply).

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

Named key-value store that remembers which jobs you've already seen. Use a different name per watchlist to track separately.

## `includeDescription` (type: `boolean`):

Add a plain-text description snippet to each job.

## `snippetLength` (type: `integer`):

Maximum length of the plain-text description snippet. 0 = no snippet.

## `scanCareersPage` (type: `boolean`):

Fetch the homepage and /careers, /jobs pages to find the exact board link (more accurate). If off, only domain-name guessing is used.

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

How many companies are processed in parallel. Higher is faster but uses a bit more memory.

## `requestTimeoutSecs` (type: `integer`):

Timeout for each HTTP request to a careers page or ATS feed.

## Actor input object example

```json
{
  "domains": [
    "stripe.com",
    "linear.app",
    "palantir.com",
    "huggingface.co",
    "equinox.com"
  ],
  "platforms": [],
  "maxJobsPerCompany": 200,
  "onlyNew": false,
  "stateStoreName": "hiring-signals-state",
  "includeDescription": true,
  "snippetLength": 300,
  "scanCareersPage": true,
  "maxConcurrency": 5,
  "requestTimeoutSecs": 20
}
```

# Actor output Schema

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

All job and error rows from the default dataset.

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

JSON summary of companies resolved, jobs saved, and errors.

# 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 = {
    "domains": [
        "stripe.com",
        "linear.app",
        "palantir.com",
        "huggingface.co",
        "equinox.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("signalroute/hiring-signals-job-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 = { "domains": [
        "stripe.com",
        "linear.app",
        "palantir.com",
        "huggingface.co",
        "equinox.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("signalroute/hiring-signals-job-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 '{
  "domains": [
    "stripe.com",
    "linear.app",
    "palantir.com",
    "huggingface.co",
    "equinox.com"
  ]
}' |
apify call signalroute/hiring-signals-job-feed --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,signalroute/hiring-signals-job-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/IXLvzpBIVnJvdnhZP/builds/7jgXgvFerkUawW9cB/openapi.json
