# ATS Jobs Scraper & Hiring Signals - Greenhouse, Lever, Ashby (`amitmudaliyar/company-jobs-hiring-signals`) Actor

Turn company domains into live hiring data. Auto-detects Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee and Breezy, then returns every open role plus a scored hiring-signal report per company: open roles, new postings, momentum and team breakdowns.

- **URL**: https://apify.com/amitmudaliyar/company-jobs-hiring-signals.md
- **Developed by:** [Amit Mudaliyar](https://apify.com/amitmudaliyar) (community)
- **Categories:** Jobs, Lead generation, Open source
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 job scrapeds

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/platform/actors/running/actors-in-store#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

## ATS Jobs Scraper & Hiring Signals — Greenhouse, Lever, Ashby, Workday & more

**Give it a list of company domains. Get back every open job they're advertising — plus a scored hiring-signal report for each company.**

No board tokens to look up. No LinkedIn scraping. No proxies. You paste `stripe.com`, this Actor works out that Stripe hires on Greenhouse, pulls all 569 open roles from the official public board API, and hands you clean, normalized, enriched data.

**Nine ATS platforms supported**, including **Workday** — so the large enterprises that Greenhouse and Lever don't cover are in scope too.

```
Input:   ["stripe.com", "openai.com", "figma.com"]
Output:  1,468 open jobs + 3 hiring-signal reports
```

***

### Why this beats a job-board scraper

Most job scrapers fight LinkedIn or Indeed for the same public postings, get rate-limited, and hand you whatever survived. This Actor goes to the **source**: the official, public, unauthenticated job-board APIs that Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee, Breezy, Workday and Personio publish so companies can embed job lists on their own careers pages.

That means:

- **It doesn't break.** No headless browsers, no anti-bot arms race, no proxy bills.
- **It's complete.** You get the company's real, current req list — not a stale aggregator index.
- **It's fast.** Hundreds of jobs per company in well under a second.
- **It's first-party data.** Straight from the system the recruiter actually posts into.

### What you can do with it

**Sales & GTM — hiring is the strongest public buying signal there is.**
A company that just opened 12 sales roles is building a sales org, and needs sales tooling *now*. Feed your target account list in, sort by `signalScore`, and work the top of the list.

**Recruiting & talent intelligence.**
Track competitor headcount plans, spot teams that are scaling, find live reqs the moment they're posted.

**Investors & market research.**
Hiring velocity is a leading indicator. `newJobsLast30Days` and `momentum` tell you who's accelerating before it shows up anywhere else.

**Job boards & aggregators.**
Build a niche board on clean, first-party listings with working apply links.

**AI agents & RAG.**
Turn on `includeDescription` for full job text, ready to embed.

> **Pairs with [Tech Stack Lookup](https://apify.com/amitmudaliyar/company-tech-stack-vendor-intelligence)** — this Actor tells you *who a company is hiring*; that one tells you *what they buy*, detecting their AI tools, CRM, security and analytics vendors from public DNS records. Same domain list in, two halves of the same account picture out.

***

### Input

The only required field is `companies`. Everything else is an optional filter.

```json
{
  "companies": ["stripe.com", "openai.com", "anthropic.com"],
  "jobFunctions": ["engineering", "ai-data-science"],
  "workplaceType": "remote",
  "postedWithinDays": 14,
  "includeHiringSignals": true
}
```

#### Ways to name a company

| Format | Example | Notes |
| --- | --- | --- |
| Domain | `stripe.com` | **Recommended.** ATS auto-detected. |
| Company name | `Stripe` | Works when the name matches the board token. |
| Explicit board | `greenhouse:stripe` | Skips detection — fastest and exact. |

Use the explicit form (`ats:slug`) when a company's board token differs from its domain, or when you want to guarantee zero detection overhead.

#### Key options

| Option | Default | What it does |
| --- | --- | --- |
| `companies` | *required* | Domains, names, or `ats:slug` pairs. |
| `keywords` | `[]` | Keep jobs whose title contains any of these. |
| `excludeKeywords` | `[]` | Drop jobs whose title contains any of these. |
| `locations` | `[]` | Substring match on location, e.g. `"London"`. |
| `jobFunctions` | `[]` | `engineering`, `ai-data-science`, `sales`, `design`, … |
| `seniorityLevels` | `[]` | `executive`, `vp`, `director`, `principal`, `manager`, `senior`, `mid`, `junior`, `intern`. |
| `workplaceType` | `any` | `remote`, `hybrid`, or `onsite`. |
| `postedWithinDays` | `0` | Only jobs posted in the last N days. |
| `includeDescription` | `false` | Adds full job text + HTML. |
| `includeHiringSignals` | `true` | Adds one analytics report per company. |
| `outputJobs` | `true` | Turn **off** to score a large account list cheaply. |

> **Tip:** To score 500 accounts for buying intent as cheaply as possible, set `outputJobs: false` and `includeHiringSignals: true`. You get one compact scored row per company instead of thousands of job rows.

***

### Output

Two record types land in the same dataset, tagged by a `type` field. The dataset has prebuilt **Open jobs** and **Hiring signals** views so you can flip between them in the UI.

#### `type: "job"`

```json
{
  "type": "job",
  "companyName": "Figma",
  "companyDomain": "figma.com",
  "ats": "greenhouse",
  "boardUrl": "https://job-boards.greenhouse.io/figma",
  "jobId": "5364702004",
  "title": "Account Executive, Emerging Enterprise (Berlin, Germany)",
  "jobFunction": "sales",
  "seniority": "mid",
  "department": "Sales",
  "location": "Berlin, Germany",
  "country": "DE",
  "workplaceType": "onsite",
  "isRemote": false,
  "employmentType": null,
  "salaryRaw": null,
  "postedAt": "2024-11-01T10:05:10.000Z",
  "daysSincePosted": 657,
  "applyUrl": "https://boards.greenhouse.io/figma/jobs/5364702004",
  "scrapedAt": "2026-08-20T23:21:48.626Z"
}
```

`jobFunction`, `seniority`, `workplaceType` and `country` are **derived by this Actor** — ATS platforms don't provide them consistently, so they're inferred from the title and location. That's what makes the data filterable and comparable across companies.

#### `type: "hiring-signal"`

One per company — the part you can't get from a plain scraper:

```json
{
  "type": "hiring-signal",
  "companyName": "Notion",
  "companyDomain": "notion.so",
  "ats": "ashby",
  "totalOpenJobs": 128,
  "newJobsLast7Days": 11,
  "newJobsLast30Days": 44,
  "medianPostingAgeDays": 38,
  "signalScore": 81,
  "momentum": "steady",
  "remoteJobs": 12,
  "remoteShare": 0.09,
  "executiveOpenings": 2,
  "engineeringJobs": 51,
  "salesJobs": 23,
  "aiDataScienceJobs": 9,
  "jobsByFunction": { "engineering": 51, "sales": 23, "…": 0 },
  "jobsBySeniority": { "mid": 70, "senior": 24, "…": 0 },
  "jobsByCountry": { "US": 96, "UK": 12, "…": 0 },
  "jobsByDepartment": { "Engineering": 44, "Go To Market": 31 },
  "topLocations": { "San Francisco": 61, "New York City": 22 }
}
```

**`signalScore` (0–100)** blends hiring volume with recency. A company with 40 open roles where half were posted this month scores far higher than one sitting on 40 stale reqs. **`momentum`** is a plain-language read: `accelerating`, `steady`, `stale`, or `not-hiring`.

***

### Supported ATS platforms

| ATS | Detection | Coverage |
| --- | --- | --- |
| Greenhouse | Auto from domain | Full board + departments |
| Ashby | Auto from domain | Full board + native remote/hybrid flags |
| Lever | Auto from domain | Full board |
| SmartRecruiters | Auto from domain | Full board, paginated to 2,000 roles |
| Workable | Auto from domain | Full board |
| Recruitee | Auto from domain | Full board |
| Breezy HR | Auto from domain | Full board |
| **Workday** | **Board URL required** | Up to 1,000 roles per board |
| Personio | Auto from domain | Best effort — see below |

#### Workday — pass the board URL

Workday powers most large enterprises, and it's supported — but it can't be auto-detected from a domain. Workday boards are addressed by three parts (tenant, data centre, site), and big careers pages are JavaScript-rendered, so the board reference isn't in the HTML. Detecting it would require a headless browser, which is exactly what keeps this Actor fast and reliable.

So give it the board URL, which you can copy from the company's careers page:

```json
{
  "companies": [
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "workday:salesforce/wd12/External_Career_Site"
  ]
}
```

Both forms work. Two Workday caveats worth knowing:

- **Posting dates are approximate.** Workday reports age as prose ("Posted 30+ Days Ago") rather than a timestamp, so dates are reconstructed. Those rows carry `postedAtIsApproximate: true`; every other ATS reports exact dates.
- **Locations** are sometimes given as an aggregate ("5 Locations"). That label is kept in `locationSummary`, and one real location is recovered from the job URL.

#### Personio — best effort

Personio serves a clean XML feed, but many tenants sit behind a WAF that blocks non-browser traffic. When that happens the company is simply reported as unresolved — and **you aren't charged**. Treat Personio coverage as a bonus, not a guarantee.

***

Companies that don't resolve are listed in the run summary under `unresolvedCompanies`, each with a specific reason — and you are **never charged** for them.

#### How detection works

1. **Slug guess.** `stripe.com` → try `stripe` against each ATS. One request, and a hit is self-verifying because the API returns the jobs directly. This resolves the large majority of companies.
2. **Careers-page discovery.** If that misses, the Actor fetches the company's `/careers` page and looks for an embedded board link (`boards.greenhouse.io/…`, `jobs.lever.co/…`, and friends).

***

### Pricing

Pay per event — you're billed for delivered results, not for compute time:

| Event | What triggers it |
| --- | --- |
| `company-resolved` | A company was successfully matched to a live ATS board. |
| `job-scraped` | One job row delivered to your dataset. |
| `hiring-signal` | One company analytics report delivered. |

Companies that can't be resolved cost you nothing. Set **Max total charge** on the run to cap spend on any single run.

***

### Run it from code

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_TOKEN' });

const run = await client.actor('amitmudaliyar/company-jobs-hiring-signals').call({
    companies: ['stripe.com', 'openai.com', 'figma.com'],
    jobFunctions: ['engineering'],
    postedWithinDays: 30,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();

const hot = items
    .filter((i) => i.type === 'hiring-signal')
    .sort((a, b) => b.signalScore - a.signalScore);

console.table(hot.map(({ companyName, totalOpenJobs, newJobsLast7Days, signalScore }) =>
    ({ companyName, totalOpenJobs, newJobsLast7Days, signalScore })));
```

***

### Notes on data

- Only **public** job postings are collected — the same listings any visitor sees on the company's careers page. No logins, no personal data, no candidate information.
- `postedAt` is the ATS's own first-published date. Long-lived evergreen reqs legitimately show large `daysSincePosted` values; use `postedWithinDays` to filter for fresh activity.
- Hiring-signal reports always describe the company's **whole board**, so scores stay comparable even when you apply job filters.

### Support

Found a company that should resolve but doesn't, or want another ATS supported? Open an issue on the Actor's **Issues** tab with the company domain — detection coverage improves with every report.

***

### More from this creator

Three Actors, one account-intelligence picture — all built on official public APIs, no proxies:

| Actor | Answers |
| --- | --- |
| **ATS Jobs Scraper & Hiring Signals** (this one) | Who is a company **hiring**? Open roles from nine ATS platforms plus a scored hiring-momentum report. |
| [Tech Stack Lookup](https://apify.com/amitmudaliyar/company-tech-stack-vendor-intelligence) | What does a company **buy**? AI tools, CRM, security and analytics vendors, detected from public DNS records. |
| [App Store Intelligence & ASO](https://apify.com/amitmudaliyar/app-store-intelligence-aso) | How does an app **perform**? Ratings, rankings and ASO across every country storefront. |

# Actor input Schema

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

Company domains (stripe.com), company names (Stripe), explicit boards (greenhouse:stripe), or a full Workday board URL. The Actor auto-detects which ATS each company uses.

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

Keep only jobs whose title contains at least one of these. Leave empty for all jobs.

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

Drop jobs whose title contains any of these words.

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

Keep jobs whose location contains any of these strings, e.g. 'London', 'New York', 'Germany'.

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

Filter by the auto-classified function of each role.

## `seniorityLevels` (type: `array`):

Filter by the auto-classified seniority of each role.

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

Match against the department name reported by the ATS.

## `workplaceType` (type: `string`):

Restrict to remote, hybrid or onsite roles.

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

Keep only jobs posted in the last N days. 0 = no limit. Great for tracking fresh hiring activity.

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

Adds descriptionText and descriptionHtml to every job. Useful for AI/RAG pipelines; makes the dataset much larger.

## `includeHiringSignals` (type: `boolean`):

Adds one analytics record per company: open roles, new roles in the last 7/30 days, momentum, signal score, and breakdowns by function, seniority and country.

## `outputJobs` (type: `boolean`):

Turn off to get only the hiring-signal reports - a fast, cheap way to score a large account list.

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

Cap the job rows returned per company. 0 = unlimited.

## `maxTotalJobs` (type: `integer`):

Hard cap on job rows for the whole run. 0 = unlimited.

## `forceAts` (type: `string`):

Skip auto-detection and only probe this ATS. Leave empty for automatic detection.

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

How many companies to resolve in parallel.

## Actor input object example

```json
{
  "companies": [
    "stripe.com",
    "openai.com",
    "anthropic.com",
    "ramp.com",
    "figma.com"
  ],
  "keywords": [],
  "excludeKeywords": [],
  "locations": [],
  "jobFunctions": [],
  "seniorityLevels": [],
  "departments": [],
  "workplaceType": "any",
  "postedWithinDays": 0,
  "includeDescription": false,
  "includeHiringSignals": true,
  "outputJobs": true,
  "maxJobsPerCompany": 0,
  "maxTotalJobs": 0,
  "forceAts": "",
  "concurrency": 5
}
```

# Actor output Schema

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

Every open role found on the resolved ATS boards, normalized across all supported platforms and enriched with job function, seniority, workplace type and country.

## `hiringSignals` (type: `string`):

One scored report per company: total open roles, new postings in the last 7 and 30 days, momentum, signal score, and breakdowns by function, seniority, department and country.

## `allRecords` (type: `string`):

The complete dataset: job rows and hiring-signal reports together, distinguished by the 'type' field.

## `downloadCsv` (type: `string`):

The full dataset as a CSV file, ready for a spreadsheet or CRM import.

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

Counts of companies requested, resolved and unresolved, jobs found and pushed, a breakdown by ATS, and the reason each unresolved company failed.

## `totalRecords` (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",
        "openai.com",
        "anthropic.com",
        "ramp.com",
        "figma.com"
    ],
    "keywords": [],
    "excludeKeywords": [],
    "locations": [],
    "jobFunctions": [],
    "seniorityLevels": [],
    "departments": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("amitmudaliyar/company-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.com",
        "openai.com",
        "anthropic.com",
        "ramp.com",
        "figma.com",
    ],
    "keywords": [],
    "excludeKeywords": [],
    "locations": [],
    "jobFunctions": [],
    "seniorityLevels": [],
    "departments": [],
}

# Run the Actor and wait for it to finish
run = client.actor("amitmudaliyar/company-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.com",
    "openai.com",
    "anthropic.com",
    "ramp.com",
    "figma.com"
  ],
  "keywords": [],
  "excludeKeywords": [],
  "locations": [],
  "jobFunctions": [],
  "seniorityLevels": [],
  "departments": []
}' |
apify call amitmudaliyar/company-jobs-hiring-signals --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,amitmudaliyar/company-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/1MiUSKaWwExDWsYih/builds/L5o9IWfyCYOdA6o0v/openapi.json
