# Company Hiring Signals Scraper | Open Roles, ATS & Growth (`tactful_anvil/company-hiring-signals-scraper`) Actor

Company domains or career/ATS URLs in → who is hiring, for what, where and how fast. Reads Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee & Workday boards: open roles by function/location, remote share, hiring velocity, new roles since last run + every job. No API key. $1.60 per 1,000 jobs.

- **URL**: https://apify.com/tactful\_anvil/company-hiring-signals-scraper.md
- **Developed by:** [Mr Zack](https://apify.com/tactful_anvil) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 1,000 jobs

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?

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 Scraper | Open Roles, ATS & New Jobs by Domain

Give it a list of **company domains** (or careers / ATS URLs) and get back **who is hiring, for what, where and how fast** — straight from each company's applicant tracking system (Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee, Workday). One **company-signal** row per company (open roles, roles by function, remote share, hiring velocity, new roles since your last run) plus one **job** row per open role. HTTP-only, pay per result (**$1.60 / 1,000 jobs · $4 / 1,000 company signals · $0.001 start**), MCP-ready. No API key, no login, no proxies.

### Who uses it and why

- **Sales / SDR teams & agencies** — a company that just opened 5 SDR roles is buying sales tooling; 12 new data-engineering roles = infra budget. Feed your account list, get the hiring signal, prioritize outreach. Pair with [Contact Details Scraper](https://apify.com/tactful_anvil/contact-details-scraper) for emails.
- **Recruiting agencies** — find which target accounts are actively hiring for your specialty (`keywords: ["account executive"]`, `postedWithinDays: 14`) and pitch them first.
- **Job boards & aggregators** — clean, normalized postings (title, department, location, remote, type, posted date, salary when disclosed, apply URL) from career sites, without scraping HTML.
- **Investors / analysts / competitive intel** — headcount intent by function and location for a watchlist of companies, refreshed daily with `onlyNew`.
- **AI agents** — "Which of these 50 companies is hiring salespeople in London right now?" is a single Actor call via MCP.

### How it finds the jobs

1. **Direct ATS URL** — `boards.greenhouse.io/x`, `jobs.lever.co/x`, `jobs.ashbyhq.com/x`, `jobs.smartrecruiters.com/X`, `x.recruitee.com`, `x.wd5.myworkdayjobs.com/Site` are used as-is.
2. **Domain / website URL** — fetches the homepage, follows the careers link (up to 2 hops), and looks for ATS references (embeds, links, API calls).
3. **Slug guess** — if the site hides its ATS, tries the domain label on Greenhouse, Lever, Ashby, Recruitee and SmartRecruiters and keeps it only when a real board with jobs comes back (flagged `discovery: "slug-guess"`).
4. Jobs are read from the ATS's **public JSON API** (Workday via its `cxs` endpoint, paginated) — the same data the career site renders, so fields are exact.

Companies with no detectable / supported ATS (custom career sites, iCIMS, Jobvite, SuccessFactors, Workable without a public list) produce a **free** `company-not-found` row with the careers URL that was found, so you know what to check by hand.

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `companies` | list | 4 demo companies | Domains, website / careers URLs or ATS board URLs, one per line. Duplicates are merged. |
| `keywords` | list | – | Keep roles whose title / department / team contains any keyword (case-insensitive). |
| `excludeKeywords` | list | – | Drop roles matching any of these. |
| `locations` | list | – | Substring match on location / country. `Remote` matches remote roles. |
| `remoteOnly` | boolean | false | Only remote roles. |
| `postedWithinDays` | integer | 0 | 0 = no age filter. Workday dates are approximate ("Posted 3 Days Ago"). |
| `maxJobsPerCompany` | integer | 200 | Cap before filters (max 5,000). Big employers list 1,000+ roles. |
| `includeJobs` | boolean | true | Push one row per role (charged as `job`). Off = signals only. |
| `includeCompanySignal` | boolean | true | Push one row per company with a found board (charged as `company-signal`). |
| `includeDescription` | boolean | false | Full plain-text description (≤ 5,000 chars) instead of a 500-char snippet. Not available from Workday / SmartRecruiters list APIs. |
| `onlyNew` | boolean | false | Monitor mode — remembers job ids per board in a named key-value store; later runs push only new roles and fill `newRoles`. |

### Output

#### `company-signal` row (one per company with a board)

| Field | Description |
|---|---|
| `company`, `domain`, `ats`, `atsBoardUrl`, `careersUrl`, `discovery` | Identity, ATS vendor, board URL, how it was found (`input-url` / `homepage` / `careers-page` / `slug-guess`). |
| `openRoles` | Total roles on the board (before your filters / cap). |
| `matchedRoles`, `newRoles` | Roles after filters; roles not seen in the previous run (monitor mode). |
| `remoteRoles`, `remoteShare`, `rolesWithSalary` | Remote count / % and how many roles disclose pay. |
| `postedLast7d`, `postedLast30d`, `hiringVelocityPerWeek`, `newestPostedAt`, `oldestPostedAt` | Freshness of the pipeline (when the ATS exposes posting dates). |
| `byFunction`, `engineeringRoles`, `salesRoles`, `salesToEngineeringRatio` | Roles bucketed into engineering / sales / marketing / product / customer / operations / finance / people / legal / other. |
| `topDepartments`, `topLocations`, `employmentTypes`, `topTitles` | Top 8 departments and locations with counts, employment-type mix, first 10 titles. |
| `isHiring`, `isNew`, `scrapedAt` | `isNew` = company seen for the first time in monitor mode. |

#### `job` row (one per open role)

| Field | Description |
|---|---|
| `company`, `domain`, `ats`, `jobId` | Which board the role belongs to. `jobId` is stable per ATS (used for monitor mode). |
| `title`, `department`, `team`, `employmentType` | As published. |
| `location`, `locations`, `isRemote` | Primary location, all locations, remote flag (from the ATS or the location / title text). |
| `postedAt`, `updatedAt` | ISO timestamps where the ATS provides them. |
| `url`, `applyUrl` | Job page and application page. |
| `salary` | `{min, max, currency, text}` when disclosed in structured fields or the description. |
| `description` | 500-char snippet, or full text with `includeDescription`. |
| `extra` | Vendor specifics: requisition id, offices, workplace type, experience level, industry, tags, country. |
| `isNew`, `scrapedAt` | Monitor flag and run time. |

Free `company-not-found` rows: `company`, `domain`, `careersUrl`, `reason`, `tried`.

The run's `SUMMARY` key-value record has counts: companies, atsFound, byVendor, jobsFetched, jobsPushed, signals, notFound, slugGuesses, requests.

### Example output (one real row, 8 Sep 2026)

```json
{
  "type": "company-signal", "company": "Stripe", "domain": "stripe.com", "ats": "greenhouse",
  "atsBoardUrl": "https://boards.greenhouse.io/stripe", "careersUrl": "https://stripe.com/careers", "discovery": "slug-guess",
  "openRoles": 619, "matchedRoles": 619, "newRoles": null, "remoteRoles": 100, "remoteShare": 16, "rolesWithSalary": 13,
  "postedLast7d": 80, "postedLast30d": 218, "hiringVelocityPerWeek": 50.9, "newestPostedAt": "2026-09-07T18:53:00.000Z", "oldestPostedAt": "2023-03-21T16:01:12.000Z",
  "byFunction": { "engineering": 220, "sales": 97, "operations": 80, "customer": 62, "other": 42, "marketing": 41, "product": 36, "finance": 18, "people": 14, "legal": 9 },
  "engineeringRoles": 220, "salesRoles": 97, "salesToEngineeringRatio": 0.44,
  "topDepartments": [ { "name": "Account Executives (EMEA)", "count": 18 }, { "name": "Global Operations", "count": 17 } ],
  "topLocations": [ { "name": "Singapore", "count": 46 }, { "name": "Dublin", "count": 30 }, { "name": "Bengaluru", "count": 29 } ],
  "isHiring": true, "isNew": null, "scrapedAt": "2026-09-08T03:30:28.493Z"
}
```

```json
{
  "type": "job", "company": "Stripe", "domain": "stripe.com", "ats": "greenhouse", "jobId": "8172487",
  "title": "Abuse Investigator", "department": "8611 Security Analytics", "team": null, "location": "Dublin", "locations": ["Dublin"],
  "isRemote": false, "employmentType": null, "postedAt": "2026-09-03T17:30:34.000Z", "updatedAt": "2026-09-04T18:12:20.000Z",
  "url": "https://stripe.com/jobs/search?gh_jid=8172487", "applyUrl": "https://stripe.com/jobs/search?gh_jid=8172487",
  "salary": null, "description": "Who we are. About Stripe. Stripe is a financial infrastructure platform for businesses…", "extra": { "requisitionId": "See Opening ID", "offices": ["Ireland Locations"] }
}
```

Cost of that run (4 demo companies, 200-role cap): 4 signals × $0.004 + ~420 jobs × $0.0016 + $0.001 start ≈ **$0.69**. Signals-only run (`includeJobs: false`) for 100 companies ≈ **$0.40**.

### Schedule it (hiring monitor)

Create a **Schedule** in Apify Console (daily is the sweet spot — ATS boards change a few times a day) with your account list and `onlyNew: true`. Each run then returns only roles that appeared since the last run plus the updated `company-signal` rows (`newRoles` > 0 = the account just opened positions). Push the dataset to Google Sheets, Slack or your CRM with an integration, or read it via API:

```
https://api.apify.com/v2/acts/tactful_anvil~company-hiring-signals-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN
```

Example input for an SDR team selling to sales orgs:

```json
{ "companies": ["stripe.com", "linear.app", "gopuff.com", "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"],
  "keywords": ["account executive", "sdr", "bdr", "sales development", "revenue"], "postedWithinDays": 14, "onlyNew": true }
```

### Use with AI agents (MCP)

Add the Actor to your Apify MCP server (`https://mcp.apify.com` with `tools=tactful_anvil/company-hiring-signals-scraper`) and ask: *"Which of these companies are hiring engineers in Europe this month?"* — the agent passes the domains, sets `keywords` / `locations` / `postedWithinDays`, and reads the `company-signal` rows first (cheap), then jobs for the interesting ones. Deterministic JSON, stable field names, one row per fact — no HTML for the agent to interpret.

### Limits & honesty

- Supported ATS with public list APIs: **Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee, Workday**. Workable is *detected* (vendor + board URL in the free row) but its list API is not public. iCIMS, Jobvite, SuccessFactors, Taleo, BambooHR, Personio and fully custom career sites are reported as `company-not-found` — free.
- Slug guessing can pick a same-named company on a shared board host; rows are flagged `discovery: "slug-guess"` so you can review them.
- Workday list pages give relative dates ("Posted 3 Days Ago") and no department; descriptions require the job page (not fetched).
- `postedAt` reflects what the ATS exposes (Greenhouse `first_published`, Lever `createdAt`, Ashby `publishedAt`, SmartRecruiters `releasedDate`, Recruitee `published_at`).
- Rate: ~4 companies in parallel, one request per board page. 100 companies ≈ 1–3 minutes.

### Related Actors

- [LinkedIn Jobs Scraper — Full Description, Salary & Applicants](https://apify.com/tactful_anvil/linkedin-jobs-full-details-scraper) — search jobs across all companies by keyword / location.
- [Contact Details Scraper](https://apify.com/tactful_anvil/contact-details-scraper) — emails, phones and socials for the companies that are hiring.
- [Ad Budget Signals](https://apify.com/tactful_anvil/ad-budget-signals) — are these companies also running ads? Combine both signals for account scoring.
- [Bulk Email Verifier](https://apify.com/tactful_anvil/bulk-email-verifier) — verify the addresses before outreach.

### Changelog

- **0.1.0 (8 Sep 2026)** — first release: 6 ATS list APIs, homepage → careers → slug discovery, keyword / location / remote / age filters, company-signal aggregation (functions, velocity, remote share), monitor mode, free not-found rows.

### Found this useful? Bookmark & review

If this Actor saves you a scraping pipeline, a ⭐ review and a bookmark help other sales and recruiting teams find it. Missing an ATS you need? Open an issue with a careers URL and I'll look at adding it.

# Actor input Schema

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

One per line: a domain (stripe.com), a website / careers URL, or a direct ATS board URL (boards.greenhouse.io/x, jobs.lever.co/x, jobs.ashbyhq.com/x, jobs.smartrecruiters.com/X, x.recruitee.com, x.wd5.myworkdayjobs.com/Site). The Actor finds the ATS from the homepage / careers page, then falls back to slug guesses.

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

Keep only roles whose title / department / team contains any of these, e.g. 'account executive', 'sdr', 'data engineer'. Empty = all roles.

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

Drop roles whose title / department contains any of these (e.g. intern, senior).

## `locations` (type: `array`):

Substring match on location / country, e.g. 'United States', 'London', 'Remote'.

## `remoteOnly` (type: `boolean`):

Keep only roles flagged remote by the ATS or whose location / title says remote.

## `postedWithinDays` (type: `integer`):

0 = no age filter. Only roles with a posted date can be filtered (Workday gives approximate dates).

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

Cap on roles fetched per company (before filters). Big employers list 1,000+ roles.

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

Push one row per open role (charged per job). Turn off to get only the free-of-jobs company-signal rows.

## `includeCompanySignal` (type: `boolean`):

One row per company with ATS vendor, open roles, roles by function / department / location, remote share, hiring velocity, new roles since last run.

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

Include the full plain-text description (up to 5,000 chars) instead of a 500-char snippet. Same price. Not available for Workday / SmartRecruiters list APIs.

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

Remembers job ids per company board in a named key-value store; later runs push (and charge) only new roles and report newRoles in the signal row. Ideal for daily scheduled runs.

## Actor input object example

```json
{
  "companies": [
    "stripe.com",
    "linear.app",
    "gopuff.com",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
  ],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "maxJobsPerCompany": 200,
  "includeJobs": true,
  "includeCompanySignal": true,
  "includeDescription": false,
  "onlyNew": false
}
```

# Actor output Schema

## `rows` (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": [
        "stripe.com",
        "linear.app",
        "gopuff.com",
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tactful_anvil/company-hiring-signals-scraper").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": [
        "stripe.com",
        "linear.app",
        "gopuff.com",
        "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("tactful_anvil/company-hiring-signals-scraper").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": [
    "stripe.com",
    "linear.app",
    "gopuff.com",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"
  ]
}' |
apify call tactful_anvil/company-hiring-signals-scraper --silent --output-dataset

```

## MCP server setup

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

```

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/5TqGoAJHplPUVtFX7/builds/UlXkb7GQBcxMQPTU0/openapi.json
