# ATS Hiring Signals & Salary Feed (`privatex/ats-hiring-signals`) Actor

Monitor public job listings across Greenhouse, Lever, Ashby, Workable, and SmartRecruiters. Extract structured job data, published salary ranges, remote work details, and hiring changes. Track new, updated, and removed listings for recruitment, sales intelligence, and market research.

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

## Pricing

from $2.00 / 1,000 job results

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

## ATS Hiring Signals & Salary Feed

Turn a list of companies into a clean, normalized feed of their **open jobs, published pay ranges and hiring changes** — straight from the official public job-board APIs of **Greenhouse, Lever, Ashby, Workable and SmartRecruiters**.

No login, no cookies, no proxy, no browser. Because the data comes from the endpoints these platforms publish for career sites, runs are fast, stable and cheap.

### Who is it for

Recruiters and sourcers tracking who is hiring, sales and partnership teams using hiring as a buying signal, and analysts or job-board builders who need clean job and pay data without writing five ATS integrations.

### Quick start

1. Open the **Input** tab. **Demo mode is pre-ticked**: the first run returns FREE synthetic data about fictional companies (labelled `dataOrigin: SYNTHETIC_DEMO`) so you can inspect the output format at no cost. To get real data, untick **Demo mode** - the four example companies (Airbnb, Ramp, Palantir, Hugging Face) are pre-filled, capped at 25 jobs each, costing at most about $0.20; raise **Max jobs per company** for full boards. A run started with completely empty input (for example from the API) is also a free demo.
2. Press **Start**. No account, API key or proxy is needed: the Actor reads each company's public job-board API.
3. Open **Output**: one row per open job with title, location, remote flag, pay range (if published) and a `change` column (`first_seen`, `new`, `updated`, `unchanged`, `removed`).
4. Run it again later with the same input: the Actor remembers the previous run and marks what is `new`, `updated` or `removed`.

**Example input**

```json
{
  "companies": ["greenhouse:airbnb", "lever:palantir"],
  "titleKeywords": ["engineer"],
  "remoteOnly": false,
  "postedWithinDays": 30
}
```

**Example output row** (SAMPLE - synthetic, illustrative values; not from a real account)

```json
{
  "company": "Airbnb",
  "ats": "greenhouse",
  "jobId": "1234567",
  "change": "new",
  "title": "Senior Backend Engineer",
  "location": "Remote - US",
  "workplaceType": "remote",
  "salaryMin": 180000, "salaryMax": 220000, "salaryCurrency": "USD", "salaryPeriod": "year",
  "url": "https://boards.greenhouse.io/airbnb/jobs/1234567"
}
```

### Errors you may see

- `"companies" is missing / is empty / must be a list of strings`: fix the input; the run fails on purpose, nothing is charged.
- `No public job board was found for any entry`: none of your companies has a public Greenhouse, Lever, Ashby, Workable or SmartRecruiters board. Use a careers URL or `ats:slug`.
- Demo output is never real jobs and is never billed.

### Pricing

**$0.002 per job listing delivered** ($2 per 1,000 jobs). A job is billed only when its row is written to your dataset. `removed` markers (a job that disappeared since your last run) are **free**. Filters (`titleKeywords`, `remoteOnly`, `maxJobsPerCompany`, ...) are applied before billing, so you pay only for jobs you keep.

Pay-per-event. Platform usage (compute, storage) is included in the price - there is no separate Apify usage bill for you. A negligible Actor-start event ($0.00005 per GB of memory, minimum one) also applies. You can cap spending with Apify's "Maximum charged results / cost per run" setting; the Actor stops cleanly at your limit.

### What is verified, and what is not

- Greenhouse, Lever, Ashby, Workable and SmartRecruiters public job-board APIs are called live; behaviour was checked against real public boards.
- Change tracking (`new`/`updated`/`removed`) needs a previous run with the same input and `stateStoreName`; the first run reports `first_seen`.

### Limitations

- Only companies that publish through one of the five supported ATS systems are found. Company names and domains are matched by guessing the board slug - check the `ats` and `atsSlug` columns, or pass a careers URL / `ats:slug` for exact results.
- Pay ranges appear only where the employer published them; `salarySource` tells you whether the range is structured or parsed from text.

Built by **PRIVATEX**. Questions or a data-format that does not parse? Open an issue on the Actor page and include the (anonymised) column headers or first lines.

### What you get

- **Auto-discovery** – give a company name, a domain, a careers URL or `ats:slug`; the Actor finds the job board.
- **One schema for five ATS platforms** – title, department, team, employment type, locations, country, remote/hybrid/onsite, posted date, job and apply URLs.
- **Pay transparency** – salary min/max, currency, period and a **yearly-normalised** amount. Taken from the official structured field when the ATS exposes it (Greenhouse, Ashby, Lever), otherwise parsed from the job text and labelled `salarySource: "text"`.
- **Change tracking** – every row is labelled `first_seen`, `new`, `updated`, `unchanged` or `removed` compared with the previous run. Schedule it daily and use `changesOnly` to receive just the delta.
- **Filters that save money** – title keywords, exclusions, location, remote only, only jobs with pay, posted within N days, max jobs per company. You are charged for returned jobs only.

### Who uses it

- **Sales & RevOps teams** – hiring signals: who is hiring SDRs, data engineers or security staff right now.
- **Recruiters & talent-intelligence teams** – competitor job and pay benchmarking by role and country.
- **Job boards & aggregators** – a legal, structured source of direct-from-employer listings.
- **Compensation analysts & journalists** – pay-transparency datasets (US state laws, EU Pay Transparency Directive).
- **AI agents / automations** – a reliable "what is company X hiring for" tool.

### Input

```json
{
  "companies": ["greenhouse:airbnb", "ashby:ramp", "https://jobs.lever.co/palantir", "Hugging Face"],
  "titleKeywords": ["engineer", "data"],
  "locationKeywords": ["United States", "remote"],
  "onlyWithSalary": true,
  "maxJobsPerCompany": 50,
  "trackChanges": true,
  "changesOnly": false
}
```

| Field | Meaning |
| --- | --- |
| `companies` | Names, domains, careers URLs or `ats:slug` entries. URLs and `ats:slug` are exact; names/domains are matched by slug – verify `ats`/`atsSlug` in the output. |
| `atsPlatforms` | Which systems to probe for names/domains. |
| `titleKeywords` / `excludeTitleKeywords` | Title include / exclude filters. |
| `locationKeywords`, `remoteOnly` | Location and remote filters. |
| `onlyWithSalary` | Keep only jobs with a published pay range. |
| `postedWithinDays` | Freshness filter. |
| `maxJobsPerCompany` | Newest-first cap per company (0 = unlimited). |
| `includeDescription` | Adds plain-text description to each row. |
| `trackChanges`, `changesOnly`, `stateStoreName` | Run-to-run change detection. |

### Output example

```json
{
  "company": "Ramp",
  "ats": "ashby",
  "atsSlug": "ramp",
  "change": "new",
  "title": "Senior Backend Engineer",
  "location": "New York, NY (HQ)",
  "workplaceType": "hybrid",
  "postedAt": "2026-10-22T19:20:16.077Z",
  "url": "https://jobs.ashbyhq.com/ramp/...",
  "salaryMin": 204000,
  "salaryMax": 280000,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "salaryAnnualMin": 204000,
  "salaryAnnualMax": 280000,
  "salarySource": "ats-structured"
}
```

A `SUMMARY` record in the key-value store lists, per company, which board was found, how many jobs are open and how many were returned.

### Good to know

- Only **public** job postings that employers publish for candidates are read. No personal data is collected.
- Companies that use other systems (Workday, iCIMS, custom sites) are reported as "no public job board found".
- SmartRecruiters' public listing does not expose descriptions or pay; those fields are empty for that ATS.
- Pay parsed from free text is best-effort; structured pay is flagged `ats-structured`.
- Very large boards (thousands of jobs) take a few more seconds; SmartRecruiters scanning is capped at 5,000 postings per company.

### Scheduling a hiring-signal monitor

Create a schedule (daily), keep `trackChanges` on, set `changesOnly: true`, and connect the dataset to a webhook, Google Sheets, Slack or Zapier/Make.

# Actor input Schema

## `demoMode` (type: `boolean`):

Preview the output format with FREE synthetic data about fictional companies. No job boards are contacted and nothing is charged. Untick it and fill in Companies for real data. A run that gives no Companies (for example empty API input) behaves the same way; untick this box to require Companies.

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

Required unless Demo mode is on. One entry per line. Accepts a company name (Airbnb), a domain (ramp.com), a careers URL (https://jobs.lever.co/palantir) or an explicit board (greenhouse:airbnb, ashby:ramp, lever:palantir, workable:huggingface, smartrecruiters:BoschGroup). Careers URLs and explicit boards are exact; names and domains are matched by their slug, so check the `ats`/`atsSlug` columns.

## `atsPlatforms` (type: `array`):

Which job-board systems to probe when a company is given as a name or domain.

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

Keep only jobs whose title contains at least one of these words (case-insensitive). Leave empty for all jobs. Example: engineer, data, sales.

## `excludeTitleKeywords` (type: `array`):

Drop jobs whose title contains any of these words. Example: intern, director.

## `locationKeywords` (type: `array`):

Keep only jobs whose location, country or workplace type contains one of these words. Example: United States, London, Germany, remote.

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

Return only jobs flagged as fully remote by the company's ATS.

## `onlyWithSalary` (type: `boolean`):

Return only jobs where a salary range was published (structured ATS field or parsed from the job text). Useful for pay-transparency research.

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

0 disables the filter. Jobs without a publish date are excluded when this is set.

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

Cap on returned jobs per company board (newest first). 0 = no cap. You only pay for returned jobs.

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

Adds the plain-text job description to every row. Makes the dataset much larger.

## `trackChanges` (type: `boolean`):

Remember each company's jobs in a named key-value store so every row is labelled new, updated, unchanged or removed compared with the previous run. Schedule the Actor daily to get a hiring-signal feed.

## `changesOnly` (type: `boolean`):

Skip unchanged jobs. Requires 'Track changes'. The first run for a company still returns all its jobs (labelled first_seen).

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

Name of the key-value store that remembers previous runs. Use different names to keep separate watchlists.

## `concurrency` (type: `integer`):

How many companies are fetched at the same time (1-20).

## Actor input object example

```json
{
  "demoMode": true,
  "companies": [
    "greenhouse:airbnb",
    "ashby:ramp",
    "lever:palantir",
    "workable:huggingface"
  ],
  "atsPlatforms": [
    "greenhouse",
    "lever",
    "ashby",
    "workable",
    "smartrecruiters"
  ],
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "locationKeywords": [],
  "remoteOnly": false,
  "onlyWithSalary": false,
  "postedWithinDays": 0,
  "maxJobsPerCompany": 25,
  "includeDescription": false,
  "trackChanges": true,
  "changesOnly": false,
  "stateStoreName": "ats-hiring-signals-state",
  "concurrency": 5
}
```

# Actor output Schema

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

No description

## `summary` (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 = {
    "demoMode": true,
    "companies": [
        "greenhouse:airbnb",
        "ashby:ramp",
        "lever:palantir",
        "workable:huggingface"
    ],
    "maxJobsPerCompany": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("privatex/ats-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 = {
    "demoMode": True,
    "companies": [
        "greenhouse:airbnb",
        "ashby:ramp",
        "lever:palantir",
        "workable:huggingface",
    ],
    "maxJobsPerCompany": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("privatex/ats-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 '{
  "demoMode": true,
  "companies": [
    "greenhouse:airbnb",
    "ashby:ramp",
    "lever:palantir",
    "workable:huggingface"
  ],
  "maxJobsPerCompany": 25
}' |
apify call privatex/ats-hiring-signals --silent --output-dataset

```

## MCP server setup

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