# Company Hiring Signals: Open Roles by Domain Across 10 ATS (`accountable_eel/company-hiring-signal`) Actor

Hiring signals for a list of companies: paste domains, we find each one's job system (Greenhouse, Lever, Ashby, Workable, Workday + 5 more) and return open roles, departments, locations, remote share and new job postings since the last run. To Google Sheets or Clay. Pay per company found.

- **URL**: https://apify.com/accountable\_eel/company-hiring-signal.md
- **Developed by:** [Adrian Voss](https://apify.com/accountable_eel) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 company signal returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Company Hiring Signals: Open Roles by Domain Across 10 ATS

**Hiring signals for a list of companies, from nothing but their domains.** Paste `stripe.com`,
`n26.com`, `notion.so`. For each company the actor finds its job system (Greenhouse, Lever,
Ashby, Workable, SmartRecruiters, Personio, Recruitee, Teamtailor, Workday or iCIMS), reads its
live job board and returns one row: open roles, roles by department, top locations, remote share,
newest and oldest posting, median posting age. Turn on monitoring and every scheduled run adds new
roles and closed roles since the last run: a new job postings alert for each company on your list.
Charged per company found. A company whose job system can't be found costs nothing.

You don't need to know which ATS a company uses, or its board slug. The actor reads the company's
own homepage and careers pages for board links, embeds and redirects, and only when the site names
none does it try each system's public job-board endpoint with the company's name. Every row says
how the job system was found and how sure that is.

### Who it's for

- **Sales and GTM teams** who prioritise accounts by hiring: a company opening 12 sales roles in
  Germany is buying tools for them. Filter on `byDepartment`, `byLocation` and
  `newRolesSinceLastRun` in Clay or Google Sheets.
- **Recruiters and staffing agencies** who watch target companies for new openings in their
  specialty, and want the new postings the day they appear.
- **Investors and analysts** tracking headcount intent across a portfolio or a market map: who is
  growing, who stopped hiring, where they hire.
- **Job-board and data builders** who need fresh company-level job data from the source ATS,
  optionally one row per posting.

### Why this one

- **Domain in, job system out.** No slugs, no guessing which of ten vendors a company uses. The
  actor finds it from the company's own site, including redirects (`getresponse.com/careers` →
  Workable) and embedded boards (n26's Greenhouse embed), and from Workday and iCIMS links that
  no slug guess could ever find.
- **Company-level signal, not a dump.** One row per company with the counts a list-builder filters
  on, computed over the whole board: departments, top 10 locations, remote share, posting age.
- **New and closed roles since the last run.** Scheduled as a watchlist, each row says what opened
  and what closed, with a sample of the new roles (title and link).
- **Honest about what it knows.** `detectionMethod` and `detectionConfidence` say how each job
  system was found. A probe that lands on a same-named board of a different company is rejected
  and not billed. Estimates are flagged (`openRolesExact: false`), and fields a platform doesn't
  publish stay empty instead of being guessed.
- **Straight from the ATS.** Every number comes from the company's own live job board, not a
  resold database, so it is as fresh as the run.

### What you get

One **company row** per input:

| Field | What it is |
|---|---|
| `domain`, `companyName` | The company, named the way its site or board names it |
| `ats`, `atsName`, `careersUrl` | Job system found, and the public job board |
| `detectionMethod` | `input` (you pasted a board URL), `redirect`, `link`, `probe`, or `none` |
| `detectionConfidence` | `high` (redirect/link/input), `medium` (probe confirmed by the board's own company name or postings), `low` (probe with nothing on the board to confirm it) |
| `detectedOn`, `otherAtsFound`, `pagesChecked` | Where the job system was found, other systems also found, pages read |
| `openRoles`, `openRolesExact`, `jobsAnalyzed` | Open roles on the whole board; false when it's an estimate; postings read |
| `byDepartment` | `{ "Engineering": 16, "Sales": 41, ... }` for the whole board |
| `byLocation` | Top 10 locations, `{ "San Francisco, California": 60, ... }` |
| `remoteShare` | Share of open roles that are remote, 0 to 1 |
| `newestPostingAt`, `oldestPostingAt`, `medianPostingAgeDays` | How fresh the board is |
| `firstRun`, `newRolesSinceLastRun`, `closedRolesSinceLastRun`, `newRoles` | Monitoring only: the change since the last run, and up to 10 new roles (title + link) |
| `note` | Anything worth knowing about this row, in plain words |

With **"Also return one row per open job"** on, each company row is accompanied by one **job
row** per open role (`rowType: "job"`): `jobTitle`, `jobDepartment`, `jobLocation`, `jobRemote`,
`jobPostedAt`, `jobUrl`, and with monitoring `isNewRole`. Every row carries the same columns, so
filter on `rowType` in Sheets or Clay.

**Per-platform notes.** Teamtailor publishes no departments, so `byDepartment` is empty there.
Workday lists only relative dates ("Posted 3 Days Ago"), so only `newestPostingAt` is given, and
whole-board counts come from Workday's own filters (which also corrects its 2,000-result cap).
Large iCIMS boards are read 5 pages deep and `openRoles` is then an estimate. SmartRecruiters'
counts cover the whole board; dates cover the first 300 postings.

### Monitoring: new job postings alert per company

Tick **Monitor: report new and closed roles since the last run**, save the input as a Task and
schedule it daily or weekly.

- **Run 1** remembers each company's board. Its rows show `firstRun: true` and no counts yet.
- **Every later run** fills `newRolesSinceLastRun` (roles never seen on this watchlist),
  `closedRolesSinceLastRun` (roles on the board last run that are gone now) and `newRoles`, a
  sample of up to 10 new roles, newest first.
- With job rows on, a monitoring run returns job rows **only for the new roles**, so a quiet day
  costs only the company rows. The first run returns every open role once, as the baseline.
- `closedRolesSinceLastRun` is only given when both runs read the whole board; for a sampled
  board (big Workday, iCIMS or SmartRecruiters boards) it stays empty rather than counting roles
  that merely fell off the pages read.
- If a company moves to another job system, that run re-baselines and says so in `note`.
- The memory lives in your Apify account (a named key-value store), per watchlist and company.
  Leave **Watchlist name** empty for one shared memory, or name it to keep separate schedules
  apart.

### Price

- **Company signal returned**: $20 per 1,000 companies
- **Job row returned**: $1 per 1,000 companies

Plus a $0.00005 start fee per run. Each event above is billed independently, only when it actually returns data — misses (`found:false`) are never charged.

- **Companies:** $20 per 1,000 companies whose job system was found (a board with 0 open roles is
  a real answer and counts). A company where nothing was found is free.
- **Job rows** (only with the checkbox on): $1 per 1,000 job rows. Cap them with
  **Max job rows per company** (default 200).
- A small start fee per run.

A weekly watchlist of 500 companies is about 2,000 company rows a month, **about $16 a month**,
plus job rows only if you ask for them.

### How to use

1. **In the Apify Console.** Open the actor page and click **Start** — the `companies` field is already pre-filled with a working example. Results land in the run's dataset as soon as each item is found.
2. **Via the API.** Call it directly with a POST request — no Console needed once you have an API token:
   ```bash
   curl "https://api.apify.com/v2/acts/accountable_eel~company-hiring-signal/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
     -X POST \
     -H "Content-Type: application/json" \
     -d '{"companies":["n26.com","getresponse.com","parcellab.com"]}'
   ```
3. **On a schedule.** Save this actor as an Apify **Task** with the input you want, then add a **Schedule** (hourly, daily, weekly) so it runs on its own — no server of your own required.

**Hiring signals to Google Sheets or Clay without code.** Save your list as a Task with monitoring
on and schedule it weekly. In n8n or Make, trigger on "Apify: run succeeded", read the run's
dataset, keep rows where `rowType` is `company`, and append `companyName`, `ats`, `openRoles`,
`newRolesSinceLastRun` and `careersUrl` to a Google Sheet. In Clay, use the HTTP API column below
with the company's domain.

### Input

```json
{
  "companies": [
    "n26.com",
    "getresponse.com",
    "parcellab.com"
  ]
}
```

One company per line: its website domain, or its careers-page URL if you have it. A job-board URL (Greenhouse, Lever, Workday...) works too. Accepted formats: n26.com, https://www.palantir.com/careers, https://jobs.lever.co/palantir, https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite.

- `includeJobs` (default off): also one row per open job, billed per job row.
- `maxJobsPerCompany` (default 200, up to 5,000): caps job rows and job charges per company.
- `deltaMode`, `deltaName`: monitoring, see above.
- **Tip:** for a Workday company, paste its careers-page URL. Workday can't be guessed from a
  domain, only found from a link on the company's site.

### Sample output

| query | found | status | rowType | domain | companyName | ats | atsName | careersUrl | detectionMethod | detectionConfidence | detectedOn | otherAtsFound | pagesChecked | openRoles | openRolesExact | jobsAnalyzed | byDepartment | byLocation | remoteShare | newestPostingAt | oldestPostingAt | medianPostingAgeDays | firstRun | newRolesSinceLastRun | closedRolesSinceLastRun | newRoles | jobId | jobTitle | jobDepartment | jobLocation | jobRemote | jobPostedAt | jobUrl | isNewRole | note | scrapedAt |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| n26.com | true | OK | \<row type (company or job)> | <company domain> | <company name> | \<job system (ats)> | <job system name> | <job board> | <how the job system was found> | <detection confidence> | <found on page> | <other job systems also found> | <company pages checked> | <open roles> | \<open roles is exact?> | <postings analyzed> | <open roles by department> | \<open roles by location (top 10)> | \<share of roles that are remote (0-1)> | <newest posting> | <oldest open posting> | \<median posting age (days)> | \<first run of this watchlist?> | <new roles since last run> | <closed roles since last run> | \<new roles (sample of 10)> | \<job id (job rows)> | \<job title (job rows)> | \<job department (job rows)> | \<job location (job rows)> | \<remote? (job rows)> | \<job posted (job rows)> | \<job link (job rows)> | \<new since last run? (job rows)> | <note> | 1970-01-01T00:00:00.000Z |

A real company row (live run, 2026-09-25, first run of a watchlist, so the since-last-run
counts are still empty; trimmed to 4 departments and locations):

```json
{
  "rowType": "company",
  "domain": "palantir.com",
  "companyName": "Palantir",
  "ats": "lever",
  "careersUrl": "https://jobs.lever.co/palantir",
  "detectionMethod": "link",
  "detectionConfidence": "high",
  "detectedOn": "https://www.palantir.com/careers",
  "openRoles": 324,
  "byDepartment": { "Dev": 80, "Delta": 68, "Echo": 36, "Information Security": 21 },
  "byLocation": { "New York, NY": 100, "London, United Kingdom": 42, "Palo Alto, CA": 29, "Seattle, WA": 13 },
  "remoteShare": 0,
  "newestPostingAt": "2026-09-25T15:29:56.130Z",
  "oldestPostingAt": "2009-12-05T00:00:00.000Z",
  "medianPostingAgeDays": 427,
  "firstRun": true,
  "newRolesSinceLastRun": null,
  "closedRolesSinceLastRun": null
}
```

And one of its job rows (with "Also return one row per open job" on):

```json
{
  "rowType": "job",
  "domain": "palantir.com",
  "ats": "lever",
  "jobTitle": "Administrative Business Partner",
  "jobDepartment": "Administrative",
  "jobLocation": "Singapore, Singapore",
  "jobRemote": false,
  "jobPostedAt": "2026-08-11T17:38:11.368Z",
  "jobUrl": "https://jobs.lever.co/palantir/6ed76ce8-4156-4b60-b120-403538bd66cd",
  "isNewRole": true
}
```

### Use it from Clay, n8n, Make, or an AI agent

This actor runs synchronously over plain HTTP — call it directly from a script, a workflow tool, or an AI agent, no Apify Console needed once you have an API token.

```bash
curl "https://api.apify.com/v2/acts/accountable_eel~company-hiring-signal/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"companies":["n26.com","getresponse.com","parcellab.com"]}'
```

**n8n.** Add an HTTP Request node: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~company-hiring-signal/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body Content Type `JSON`, JSON Body `{"companies":["n26.com","getresponse.com","parcellab.com"]}` (swap in an expression from an earlier node for a real value).

**Clay.** Add an "HTTP API" column: Method `POST`, URL `https://api.apify.com/v2/acts/accountable_eel~company-hiring-signal/run-sync-get-dataset-items?token=<YOUR_TOKEN>`, Body `{"companies":["{{company}}"]}`, mapping the row's company into the `companies` array.

**MCP.** In Claude, Cursor, or any MCP client with the Apify MCP server, ask for "Company Hiring Signals API: Open Roles by Domain" — the agent will find and run this actor.

### vs. alternatives

| | What it costs | What you get | Trade-off |
|---|---|---|---|
| **This actor** | $20 per 1,000 companies found, misses free; job rows $1 per 1,000 | Job system detected from a domain; open roles, departments, locations, remote share, posting age; new and closed roles since last run | Ten job systems; companies on a custom-built careers site aren't covered |
| Career-site job APIs on the Apify Store | From about $12 per 1,000 jobs | Every posting as a row | You pay per posting and aggregate yourself; you still need to know which companies are on which system |
| Hiring-signal data vendors | Subscriptions, usually per company per month | Pre-aggregated signals | A resold database updated on their schedule, not your list read live |
| Checking careers pages by hand | Your time | Everything | Doesn't scale past a handful of companies, and no history |

Store figures as of September 2026.

### FAQ

**Which job systems are covered?**
Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Personio, Recruitee (Tellent), Teamtailor,
Workday and iCIMS. Companies on another system, or on a careers site they built themselves, come
back with `ats` empty and are not charged.

**How is the job system found?**
The homepage (or the careers URL you gave), then `/careers`, `/jobs`, `/karriere`, `/join-us` and
careers links on the site, looking for board links, embeds and redirects. If none, one request to
each system's public job-board endpoint with the company's name taken from its domain. A board
found that way must name the company, or mention it in its postings, to be accepted at medium
confidence.

**Why was I charged for a company with 0 open roles?**
Its job system was found and read: "on Greenhouse, not hiring right now" is a real answer, and on
a watchlist it is exactly the heartbeat that shows the company is still being checked.

**Why did a company come back empty?**
Its site names no supported job board and no probe matched. The `note` says what was checked.
If the site names a board that didn't answer this time, the row says so, keeps `ats` and
`careersUrl`, has no counts, and is not charged; run it again later.
Workday boards can only be found from a link; paste the careers-page URL to help. A site that
refuses every request from cloud servers (5xx or no DNS) comes back as `REQUEST_FAILED`, free.

**What do `medium` and `low` confidence mean?**
Both mean the board was found by guessing the slug from the domain, not from the company's own
site. `medium`: the board's company name, or its postings' text, names the company. `low`: the
board publishes nothing to confirm it. Two different companies can share a name, so for either
one, glance at `careersUrl`. A probe that lands on a board naming a different company is rejected
outright and not billed.

**Does it log in or use personal data?**
No. It reads public careers pages and the public job-board endpoints each ATS vendor publishes
for embedding. No login, no candidate data, no personal data about anyone. Check that your use
fits each site's terms and your local law.

**Can an AI agent call this?**
Yes, through the Apify MCP server or the API call above.

**One company's full job list instead?**
The same parsers run as dedicated actors per job system, for example
[Greenhouse Jobs](https://apify.com/accountable_eel/greenhouse-jobs-lookup) and
[Workday Jobs](https://apify.com/accountable_eel/workday-jobs-lookup), with title, location and
date filters.

# Actor input Schema

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

One company per line: its website domain, or its careers-page URL if you have it. A job-board URL (Greenhouse, Lever, Workday...) works too. Accepted formats: n26.com, https://www.palantir.com/careers, https://jobs.lever.co/palantir, https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite. You're only charged for the ones we actually find — a miss costs nothing.

## `testRun` (type: `boolean`):

Turn this on to test your input on a small sample before running the full list. Turn it off to process everything.

## `onlyFound` (type: `boolean`):

Only keep rows where something was actually found. Misses are always free, whether or not you show them here.

## `includeKeywords` (type: `array`):

Optional. Only keep results that mention at least one of these words (e.g. a job title, a city, a product name). Leave empty to keep everything.

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

Optional. Drop any result that mentions one of these words. Leave empty to skip nothing.

## `maxResults` (type: `integer`):

Optional. Stop the run once this many results have been found — useful for a quick, cheap sample. Leave blank for no limit.

## `includeJobs` (type: `boolean`):

Adds one row per open role after each company's hiring-signal row: title, department, location, remote, posted date and link. Each job row is billed as a job (see Pricing). With monitoring on, only roles new since the last run are returned.

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

Caps the job rows (and the job charges) per company when the box above is on. 1 to 5,000. Doesn't change the company row's counts, which always cover the whole board.

## `deltaMode` (type: `boolean`):

Turns the list into a watchlist. Every company row gains new roles and closed roles since the previous run, plus a sample of the new ones — a new job postings alert for each company. The first run has nothing to compare against, so it just remembers each board. Schedule the same input daily or weekly.

## `deltaName` (type: `string`):

Leave empty to use one shared memory for this actor on your account. Name it to keep separate memories for separate schedules ("sales-targets", "competitors"). Naming a watchlist with the box above off still fills the since-last-run columns, but returns every job row.

## `columns` (type: `array`):

Choose which pieces of information to include in each result row. All are included by default.

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

Parallel requests. Keep conservative — this target has no browser fallback, so getting blocked costs more than slow-and-steady.

## `proxyConfiguration` (type: `object`):

Apify Proxy config. Residential recommended for anti-bot-sensitive targets.

## Actor input object example

```json
{
  "companies": [
    "n26.com",
    "getresponse.com",
    "parcellab.com"
  ],
  "testRun": false,
  "onlyFound": false,
  "includeKeywords": [],
  "excludeKeywords": [],
  "includeJobs": false,
  "maxJobsPerCompany": 200,
  "deltaMode": false,
  "deltaName": "",
  "columns": [
    "rowType",
    "domain",
    "companyName",
    "ats",
    "atsName",
    "careersUrl",
    "detectionMethod",
    "detectionConfidence",
    "detectedOn",
    "otherAtsFound",
    "pagesChecked",
    "openRoles",
    "openRolesExact",
    "jobsAnalyzed",
    "byDepartment",
    "byLocation",
    "remoteShare",
    "newestPostingAt",
    "oldestPostingAt",
    "medianPostingAgeDays",
    "firstRun",
    "newRolesSinceLastRun",
    "closedRolesSinceLastRun",
    "newRoles",
    "jobId",
    "jobTitle",
    "jobDepartment",
    "jobLocation",
    "jobRemote",
    "jobPostedAt",
    "jobUrl",
    "isNewRole",
    "note"
  ],
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# 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": [
        "n26.com",
        "getresponse.com",
        "parcellab.com"
    ],
    "includeKeywords": [],
    "excludeKeywords": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("accountable_eel/company-hiring-signal").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": [
        "n26.com",
        "getresponse.com",
        "parcellab.com",
    ],
    "includeKeywords": [],
    "excludeKeywords": [],
}

# Run the Actor and wait for it to finish
run = client.actor("accountable_eel/company-hiring-signal").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": [
    "n26.com",
    "getresponse.com",
    "parcellab.com"
  ],
  "includeKeywords": [],
  "excludeKeywords": []
}' |
apify call accountable_eel/company-hiring-signal --silent --output-dataset

```

## MCP server setup

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

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/VKq0xtKtmX1FJaV8f/builds/1OqHi76El9JlgFUm3/openapi.json
