# Hiring Signals + ATS Jobs: Greenhouse Lever Ashby Workable (`technicaldost/ats-jobs-hiring-signals`) Actor

Find companies hiring now. Open jobs from public Greenhouse, Lever, Ashby and Workable boards plus a company hiring-signal row: new roles, hiring surges, first sales/security hires, growing teams and new locations. Paste domains or slugs; daily delta mode. No API key.

- **URL**: https://apify.com/technicaldost/ats-jobs-hiring-signals.md
- **Developed by:** [Technical Dost Solutions](https://apify.com/technicaldost) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.80 / 1,000 job postings

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 & ATS Jobs Scraper — Greenhouse, Lever, Ashby, Workable

**Find companies that are hiring right now, and see what they are hiring for.** Paste company domains, names or careers URLs, and this Actor returns every open job from their public **Greenhouse, Lever, Ashby or Workable** job board, plus one **company hiring-signal row** per company: new roles in the last 7 and 30 days, hiring surges, growing departments, first sales or security hires, new office locations and salary bands. Built for **B2B sales and RevOps teams, recruiters and staffing agencies, and VC or market analysts** who want hiring intent as a lead trigger, not a raw job dump.

Schedule it with **Only new since last run** on and each run returns only roles you have not seen yet, and a signal row only when a company opened or closed roles. No API key, no login, no proxy.

### Why this Actor

- **Hiring intent, not just rows.** A company that just posted 5 account-executive roles is buying sales tools. A company posting its first security engineer is buying security software. The `companySignal` row tells you that in one line (`signalSummary`) with a 0–100 `signalScore`.
- **Domains in, boards out.** You do not need to know which ATS a company uses. Enter `notion.so` or `datadoghq.com` and the Actor checks Greenhouse, Lever, Ashby and Workable and keeps the board with the most open roles.
- **Monitoring mode.** A named key-value store remembers each stream, so daily or weekly runs only return new jobs and changed companies. You never pay twice for the same job.
- **Fair pricing.** Job rows are priced at market rate. Signal rows are charged **only when a company has at least one signal**. Companies with no board, or no signal, are free.

### What you get

#### Company hiring-signal row (`recordType = "companySignal"`)

| Field | Example |
|---|---|
| `company`, `companyDomain`, `ats`, `careersUrl` | `Figma`, `figma.com`, `greenhouse`, `https://job-boards.greenhouse.io/figma` |
| `signalScore` | `68` (0–100 hiring-intent score) |
| `signalSummary` | `Hiring surge: 47 new roles in 30 days; Growing: Sales (+8); 48 open sales roles; Hiring RevOps / sales ops` |
| `signalTypes` | `hiring_surge`, `new_roles_7d`, `first_hire`, `department_growth`, `new_location`, `sales_team_build`, `revops_hiring`, `new_since_last_run`, `roles_closed` |
| `openRoles`, `matchingRoles` | `144`, `49` (roles that match your filters) |
| `newRoles7d`, `newRoles30d` | `12`, `47` |
| `newSinceLastRun`, `closedSinceLastRun` | `3`, `1` (monitoring mode) |
| `salesRoles`, `engineeringRoles` | `48`, `27` |
| `firstHireFunctions` | `["security"]` |
| `growingDepartments`, `topDepartments`, `topFunctions` | `["Sales (+8)"]`, `["Sales (44)", "Engineering (27)"]`, `["sales (48)"]` |
| `newLocations` | `["Tokyo"]` |
| `salaryBandMedian`, `salaryCurrency`, `rolesWithSalary` | `217225`, `USD`, `163` |
| `remoteShare` | `0.58` |
| `sampleNewRoles` | Up to 5 newest role titles |
| `signalId`, `companyId`, `scrapedAt` | Stable IDs and ISO 8601 timestamp |

#### Job row (`recordType = "job"`)

| Field | Example |
|---|---|
| `jobKey` (stable ID), `jobId` | `ashby:ramp:f9d015d7-…` |
| `company`, `companyDomain`, `ats`, `boardSlug` | `Ramp`, `null`, `ashby`, `ramp` |
| `title`, `jobFunction` | `Manager, Account Executive, Strategic`, `sales` |
| `department`, `team` | `Sales`, `Account Executive` |
| `location`, `country`, `isRemote`, `workplaceType` | `Remote (US)`, `US`, `true`, `remote` |
| `employmentType` | `Full-Time` |
| `salaryMin`, `salaryMax`, `salaryCurrency`, `salaryInterval` | `520000`, `620000`, `USD`, `year` |
| `firstPublishedAt`, `updatedAt`, `daysOpen` | ISO 8601 dates, `2` |
| `jobUrl`, `applyUrl`, `careersUrl` | Public posting links |
| `isNewSinceLastRun` | `true` (monitoring mode; `null` on the first run) |
| `companyOpenRoles`, `companyNewRoles30d` | Company context on every job |
| `description` | Plain text, only when *Include job description* is on |

Two table views: **Hiring signals — sales prospecting** (score, company, why now, sales roles and first hires first) and **Open jobs**. Export to CSV, Excel, JSON, Google Sheets or your CRM.

`jobFunction` is classified from the title and department into: `sales`, `revops`, `marketing`, `customer_success`, `engineering`, `data_ai`, `security`, `product`, `design`, `finance`, `people`, `legal`, `operations`, `other`.

### How to use

1. Add your **Companies**, one per line. Any of these work:
   - a domain: `notion.so`, `datadoghq.com`
   - a name or board slug: `stripe`, `ramp`
   - a board URL: `https://jobs.ashbyhq.com/ramp`, `https://jobs.lever.co/palantir`, `https://boards.greenhouse.io/figma`, `https://apply.workable.com/huggingface`
   - an explicit board: `greenhouse:anthropic`, `lever:spotify`, `ashby:linear`, `workable:huggingface`
2. Pick an **Output**: jobs and signals, jobs only, or signals only.
3. Optional: filter by **job function**, title keywords, department, location, remote, or *posted within N days*.
4. For monitoring, turn on **Only new since last run** and give each list its own **Stream name**. Then add a **Schedule** (Console → Schedules → daily or weekly).

#### Example input: SDR team, companies building a sales team

```json
{
  "companies": ["figma.com", "notion.so", "linear.app", "vercel.com", "ramp", "datadoghq.com"],
  "outputMode": "signals",
  "jobFunctions": ["sales", "revops"],
  "onlyNew": true,
  "stateKey": "sales-tools-weekly"
}
```

#### Example input: staffing agency, new remote engineering roles every day

```json
{
  "companies": ["greenhouse:anthropic", "ashby:openai", "lever:spotify", "stripe"],
  "outputMode": "jobs",
  "jobFunctions": ["engineering", "data_ai"],
  "remoteOnly": true,
  "excludeTitleKeywords": ["intern"],
  "onlyNew": true,
  "stateKey": "agency-eng-daily"
}
```

#### Example input: VC analyst, 30-day hiring velocity across a portfolio

```json
{
  "companies": ["ramp", "plaid.com", "duolingo", "figma.com", "openai.com"],
  "outputMode": "signals"
}
```

### Use cases

- **Sales prospecting:** a sales-tool SDR team runs it weekly on 2,000 target domains and works only the companies that opened sales, RevOps or first-in-function roles.
- **Recruiting and staffing:** an agency monitors 300 scale-ups and gets new engineering roles daily in its CRM.
- **Investment research:** a VC analyst tracks 30-day hiring velocity across a portfolio and its competitors.

### Pricing (pay per event — no subscription, no start fee)

| Event | Price | When it is charged |
|---|---|---|
| Job row (`job`) | **$0.0018** per job ($1.80 per 1,000) | Each job row delivered. In monitoring mode only new jobs are delivered. |
| Company hiring signal (`company-signal`) | **$0.025** per company | Only when the company has at least one hiring signal. |

Companies with no public board, empty boards and companies with no signal are **free**. Use **Max rows per run** or a maximum charge per run to cap spend. Signals-only runs are the cheapest way to watch thousands of companies.

### Integrations

#### n8n

- **Import the ready-made workflow:** download [`integrations/n8n-workflow.json`](https://apify.com/technicaldost/ats-jobs-hiring-signals) and use *Workflows → Import from file*. Every Monday it runs the Actor through the Apify API, keeps companies with `signalScore ≥ 50` and appends them to Google Sheets. Create an *HTTP Header Auth* credential with `Authorization: Bearer <your Apify API token>`.
- **Or use n8n's Apify node:** *Run an Actor* → `technicaldost/ats-jobs-hiring-signals` → *Get dataset items*.

#### Make (Integromat)

1. Add **Apify → Run an Actor**, Actor `technicaldost/ats-jobs-hiring-signals`, *Run synchronously* = Yes, and paste one of the JSON inputs above.
2. Add **Apify → Get Dataset Items** using the run's `defaultDatasetId`.
3. Add a **Filter** (for example `recordType = companySignal` and `signalScore ≥ 50`), then **Google Sheets → Add a Row**, **HubSpot → Create/Update a Company** or **Slack → Create a Message**.
4. Schedule the scenario daily or weekly.

No Apify module? Use **HTTP → Make a request**: `POST https://api.apify.com/v2/acts/technicaldost~ats-jobs-hiring-signals/run-sync-get-dataset-items` with the header `Authorization: Bearer <token>` and your JSON input as the body. The response is the array of rows.

#### Zapier, API, Python, MCP

- **Zapier:** use the Apify app (*Run Actor* → *Find dataset items*).
- **REST API:** call the `run-sync-get-dataset-items` endpoint above, or start a run and read its dataset.
- **Python / JavaScript:** use the `apify-client` package.
- **AI agents:** call this Actor through the **Apify MCP server**, for example "which of these 50 companies started hiring sales reps this month?".

### FAQ

**Where does the data come from?** The public job-board APIs that Greenhouse, Lever, Ashby and Workable provide for embedding careers pages. The same data is shown on each company's public careers page. No login is used.

**Which companies are covered?** Any company that hosts its careers page on Greenhouse, Lever, Ashby or Workable. That is a large share of venture-backed tech companies. Companies on Workday, iCIMS, SmartRecruiters, Taleo or a custom careers site are **not** covered yet and are reported as "not found" (free).

**How does auto-detection work, and can it pick the wrong company?** For a domain or name, the Actor tries a few slug variants (e.g. `datadoghq.com` → `datadoghq`, `datadog`) on each ATS and keeps the board with the most open roles. Two different companies can share a slug on different ATS platforms (for example a small agency called "Notion" on Workable). When precision matters, enter the board URL or `ats:slug`. The chosen board is always in `ats`, `boardSlug` and `careersUrl`.

**How fresh is the data?** Live. Each run reads the boards at that moment.

**What counts as a "first hire"?** In monitoring mode: a job function that was not on the board at the previous run. On a first run: a function where the company (with 40 or fewer open roles) has only one or two roles, all posted in the last 30 days. Treat it as a strong hint, not a fact.

**Why is `companyDomain` empty for some companies?** It comes from your input or from the job URLs. Lever, Ashby and Workable URLs live on the ATS domain, so enter a domain if you need it.

**Why are salaries missing for some jobs?** Salaries are returned only when the company publishes a pay range on its board (common in US states with pay-transparency laws).

### Compliance

Not affiliated with Greenhouse, Lever, Ashby or Workable. This Actor reads only public job postings through public, unauthenticated job-board endpoints, at a polite request rate. It collects no personal data about candidates or employees. Job descriptions are the copyright of the hiring company: use them for research and prospecting, and do not republish them as your own job board without permission. You are responsible for complying with the terms of the sites you use, and with GDPR, CAN-SPAM and other outreach laws when you contact companies.

### Support

Found a bug or need another ATS (SmartRecruiters, Recruitee, Personio, Teamtailor)? Open an issue on the **Issues** tab.

# Actor input Schema

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

One company per line. Accepts a company domain (notion.so), a name or board slug (stripe), a careers/board URL (https://jobs.ashbyhq.com/ramp, https://jobs.lever.co/palantir, https://apply.workable.com/huggingface) or an explicit ats:slug (greenhouse:anthropic). Explicit URLs and ats:slug are the most precise.

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

Which applicant tracking systems to search when auto-detecting a company's job board. Board URLs and ats:slug entries are always fetched from their own ATS.

## `autoDetectAts` (type: `boolean`):

Try a few slug variants (e.g. 'acme', 'acmehq', 'acmeio' from acme.io) on every selected ATS and keep the board with the most open roles. Turn off to try only the exact slug you entered.

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

jobs+signals = one company hiring-signal row per company plus its job rows. jobs = job rows only. signals = only company hiring-signal rows (cheapest way to monitor many companies).

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

Remembers what this stream already delivered. Job rows are returned only for roles you have not received yet, and a company signal row only when the company opened or closed roles since the last run. Ideal for daily or weekly schedules: you never pay twice for the same job.

## `stateKey` (type: `string`):

Separate 'only new' memory per list, client or pipeline (e.g. 'saas-targets-weekly'). Each stream de-duplicates independently. Stored in the named key-value store 'ats-hiring-signals-state'.

## `jobFunctions` (type: `array`):

Keep only jobs in these functions (classified from title and department). Leave empty for all. Company signals still describe the whole company.

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

Keep jobs whose title contains a word starting with any of these terms (case-insensitive), e.g. 'account executive', 'SDR', 'engineer' (also matches 'engineering'). Leave empty for all.

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

Drop jobs whose title contains a word starting with any of these terms, e.g. 'intern' (also drops 'internship' and 'internal'), 'contract'.

## `departments` (type: `array`):

Keep jobs whose department or team contains a word starting with any of these terms, e.g. 'Sales', 'Engineering'. Leave empty for all.

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

Keep jobs whose location or country contains any of these whole words, e.g. 'London', 'Remote', 'US', 'DE', 'Germany' ('US' does not match 'Australia'). Leave empty for all.

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

Keep only roles marked remote by the ATS or with 'Remote' in the location.

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

Keep only jobs first published in the last N days. 0 = any age.

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

Add the plain-text job description (up to 20,000 characters) to each job row. Makes runs slower and the dataset larger.

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

Cap on job rows returned per company (newest first). Signals are always computed from all open roles.

## `maxItems` (type: `integer`):

Hard cap on rows returned (and charged) per run, counting job rows and company signal rows together.

## Actor input object example

```json
{
  "companies": [
    "stripe",
    "ramp",
    "notion.so",
    "https://jobs.lever.co/palantir"
  ],
  "atsPlatforms": [
    "greenhouse",
    "lever",
    "ashby",
    "workable"
  ],
  "autoDetectAts": true,
  "outputMode": "jobs+signals",
  "onlyNew": false,
  "stateKey": "default",
  "remoteOnly": false,
  "postedWithinDays": 0,
  "includeDescription": false,
  "maxJobsPerCompany": 500,
  "maxItems": 1000
}
```

# Actor output Schema

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

All rows produced by Hiring Signals + ATS Jobs: Greenhouse Lever Ashby Workable (default dataset items, JSON).

# 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",
        "ramp",
        "notion.so",
        "https://jobs.lever.co/palantir"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("technicaldost/ats-jobs-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 = { "companies": [
        "stripe",
        "ramp",
        "notion.so",
        "https://jobs.lever.co/palantir",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("technicaldost/ats-jobs-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 '{
  "companies": [
    "stripe",
    "ramp",
    "notion.so",
    "https://jobs.lever.co/palantir"
  ]
}' |
apify call technicaldost/ats-jobs-hiring-signals --silent --output-dataset

```

## MCP server setup

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