# Multi-ATS Tech Hiring Signals (Greenhouse, Lever, Ashby) (`vesper_studio/multi-ats-tech-hiring-signals`) Actor

Turn Greenhouse, Lever and Ashby job boards into B2B buying signals and salary benchmarks. Detect tech-stack keywords (Snowflake, Salesforce, Rust, AI…), extract pay bands, normalize locations, delta mode for daily monitoring. 100% HTTP, no proxy.

- **URL**: https://apify.com/vesper_studio/multi-ats-tech-hiring-signals.md
- **Developed by:** [Xavier Richard](https://apify.com/vesper_studio) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.50 / 1,000 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

## Multi-ATS Tech Hiring Signals & Compensation Intel (Greenhouse, Lever, Ashby)

Turn public job boards from **Greenhouse**, **Lever (US & EU)**, and **Ashby** into actionable **B2B buying signals**, **120+ tech-stack intelligence**, **seniority/department classification**, and **salary benchmarks**.

Built for **B2B Sales / RevOps / GTM Engineers (Clay, HubSpot, Apollo)**, **technical recruiters**, and **VC / market analysts**, this Actor queries the official public posting APIs directly over **100% pure HTTP** — no headless browser, zero proxy required, and a minimal **128–256 MB RAM** footprint.

***

### Key Capabilities & GTM Intelligence (V2)

- **Automatic 120+ Tech Stack Detection (`fullTechStack` & `techCategories`)**: Out of the box — even when `techKeywords` is left empty — every posting is scanned against a curated dictionary of **120+ B2B technologies across 7 categories**:
  - `ai_ml` (`OpenAI`, `Anthropic`, `LangChain`, `LlamaIndex`, `RAG`, `PyTorch`, `vLLM`, `Hugging Face`, `Pinecone`, `Qdrant`...)
  - `data_analytics` (`Snowflake`, `Databricks`, `BigQuery`, `Redshift`, `dbt`, `Airflow`, `Dagster`, `Fivetran`, `Kafka`, `Looker`, `Tableau`...)
  - `cloud_devops` (`AWS`, `GCP`, `Azure`, `Kubernetes`, `Docker`, `Terraform`, `Datadog`, `Grafana`, `OpenTelemetry`, `GitHub Actions`, `ArgoCD`...)
  - `databases_storage` (`PostgreSQL`, `MySQL`, `MongoDB`, `Redis`, `Elasticsearch`, `ClickHouse`, `DynamoDB`, `Supabase`...)
  - `languages_frameworks` (`TypeScript`, `Python`, `Rust`, `Go`, `Java`, `C++`, `C#`, `.NET`, `React`, `Next.js`, `Node.js`, `FastAPI`, `GraphQL`...)
  - `crm_gtm_revops` (`Salesforce`, `HubSpot`, `Clay`, `Gong`, `Outreach`, `Salesloft`, `Apollo`, `ZoomInfo`, `6sense`, `Segment`, `Braze`, `Stripe`, `NetSuite`, `Workday`...)
  - `security_compliance` (`Okta`, `Auth0`, `CrowdStrike`, `Snyk`, `Wiz`, `Vanta`, `Drata`, `SOC 2`, `ISO 27001`, `HIPAA`, `GDPR`...)
- **Synonym & Context-Aware Keyword Matching (`detectedTechKeywords`)**:
  - Automatically expands common aliases (`Kubernetes` ↔ `K8s`, `PostgreSQL` ↔ `Postgres`, `Salesforce` ↔ `SFDC`, `AWS` ↔ `Amazon Web Services`, `GCP` ↔ `Google Cloud`, `SOC 2` ↔ `SOC2`).
  - Context-sensitive disambiguation for ambiguous names (`Go` matches `Golang` or `Go` alongside technical terms, never English `"go to"`; `Clay`, `Gong`, `Outreach`, `Segment`, `Linear`, `Spark` require proper capitalization or GTM/data context).
- **High-Intent B2B Buying Signals (`buyingSignals`)**: Automatically tags postings with actionable GTM triggers:
  - `leadership_hire` (new CTO, VP, Head of, Director)
  - `founding_or_first_hire` ("founding engineer", "first data hire", "0 to 1", "greenfield")
  - `migration_or_modernization` ("migrate from/to", "legacy system", "cloud migration", "re-architecting")
  - `ai_transformation` (LLM, RAG, agentic workflows, fine-tuning)
  - `revops_gtm_scaling` (RevOps / GTM Systems expansion)
  - `security_compliance_push` (SOC 2, ISO 27001, HIPAA, Vanta/Drata rollouts)
  - `team_expansion` ("rapidly growing team", "scaling our team")
- **Normalized Seniority (`seniority`) & Department (`departmentCategory`)**:
  - Classifies every job title into 12 seniority levels (`c-level`, `vp`, `director`, `head`, `principal`, `staff`, `lead`, `manager`, `senior`, `mid`, `junior`, `intern`).
  - Maps raw ATS department strings into 12 canonical B2B categories (`Engineering`, `Data & AI`, `Sales & GTM`, `Revenue Operations`, `Security & IT`, `Product & Design`, `Customer Success`, `Marketing`, `People & Talent`, `Finance & Legal`, `Operations`, `Other`).
- **Company Domain Extraction (`companyDomain`)**: Extracts clean company root domains (`stripe.com`, `dust.tt`) from custom career URLs and Ashby's organization metadata for immediate table joins in **Clay**, **HubSpot**, **Apollo**, or **Salesforce**.
- **Compensation Benchmarking (`compensation`)**:
  - Extracts structured pay bands from Greenhouse (`pay_input_ranges` & `.pay-range`), Lever (`salaryRange`), and Ashby (`compensation`), plus a conservative free-text salary parser.
  - Preserves multi-zone/multi-tier salary bands in `compensation.allRanges`, computes `annualizedMin` & `annualizedMax`, and flags `offersEquity` (`true`/`false`) and `offersBonus` (`true`/`false`).
- **Smart Delta Mode & Job Lifecycle Tracking (`onlyNew`)**:
  - Persists job IDs and content fingerprints (`title + location + salary`) in the named Key-Value Store `multi-ats-hiring-signals-state`.
  - Re-emits jobs whose salary band or title changed (`changeStatus: "updated"`).
  - Diffs each board against its previous snapshot to count **closed/filled positions since the last run** (`closedJobsSinceLastRun` in `SUMMARY`).

***

### Supported ATS & Public Endpoints

| ATS | Public Endpoints Integrated | Special Capabilities |
| :--- | :--- | :--- |
| **Greenhouse** | `GET https://boards-api.greenhouse.io/v1/boards/{board_token}/jobs?content=true&pay_transparency=true`<br>`GET https://boards-api.greenhouse.io/v1/boards/{board_token}/jobs/{job_id}?questions=true` | Extracts hidden locations/workplace types from `metadata`, parses multi-zone `pay_input_ranges` (cents) and `.pay-range` HTML blocks, and fetches application questions on demand. |
| **Lever (US & EU)** | `GET https://api.lever.co/v0/postings/{site}?mode=json`<br>`GET https://api.eu.lever.co/v0/postings/{site}?mode=json` | Automatic US → EU instance fallback, full pagination (`limit=100` & `skip`) for large boards (>20 jobs), `salaryRange` + `salaryDescriptionPlain` extraction, and HTML apply-form card parsing. |
| **Ashby** | `GET https://api.ashbyhq.com/posting-api/job-board/{job_board_name}?includeCompensation=true` | Structured `compensation` parsing (`Salary` + `Equity` + `Bonus` components & tiers), unlisted job filtering, plus company display name, `publicWebsite` (`companyDomain`), and application form resolution via Ashby's public GraphQL endpoint. |

***

### Flexible Target Formats & Parallel Auto-Detection

Pass any mix of the following in `targets`:

1. **Raw company slug / board token** (ATS is automatically detected by probing Greenhouse, Ashby, Lever US, and Lever EU **in parallel**):
   - `"stripe"`, `"openai"`, `"dust"`, `"palantir"`
2. **Prefixed slug** (skips probing when you already know the ATS):
   - `"greenhouse:stripe"`, `"gh:airbnb"`, `"lever:palantir"`, `"lever-eu:acme"`, `"ashby:dust"`
3. **Full ATS career board URL** (ATS, region, and token are parsed automatically):
   - `https://boards.greenhouse.io/stripe`
   - `https://job-boards.eu.greenhouse.io/acme`
   - `https://jobs.lever.co/palantir`
   - `https://jobs.eu.lever.co/acme`
   - `https://jobs.ashbyhq.com/dust`
4. **Company careers page URL** (fetched once and inspected for an embedded Greenhouse, Lever, or Ashby board):
   - `https://www.company.com/careers`
5. **Structured target object** (via API):
   - `{ "url": "https://boards.greenhouse.io/stripe", "company": "Stripe" }`
   - `{ "ats": "lever", "token": "palantir", "region": "us", "company": "Palantir Technologies" }`

***

### Unified Output Schema (Dataset Item)

```json
{
  "jobId": "gh-1234567",
  "ats": "greenhouse",
  "company": "Stripe",
  "companyToken": "stripe",
  "companyDomain": "stripe.com",
  "title": "Senior Solutions Architect - Data",
  "seniority": "senior",
  "department": "Engineering",
  "departmentCategory": "Engineering",
  "location": "Paris, France (Hybrid)",
  "isRemote": false,
  "publishedAt": "2026-10-15T10:30:00.000Z",
  "url": "https://boards.greenhouse.io/stripe/jobs/1234567",
  "compensation": {
    "min": 85000,
    "max": 110000,
    "currency": "EUR",
    "period": "yearly",
    "annualizedMin": 85000,
    "annualizedMax": 110000,
    "offersEquity": true,
    "offersBonus": true,
    "summary": "Zone A - Paris",
    "source": "ats_structured"
  },
  "detectedTechKeywords": ["Snowflake", "Python"],
  "fullTechStack": ["Snowflake", "dbt", "Python", "AWS", "Terraform"],
  "techCategories": {
    "data_analytics": ["Snowflake", "dbt"],
    "cloud_devops": ["AWS", "Terraform"],
    "languages_frameworks": ["Python"]
  },
  "buyingSignals": ["migration_or_modernization"],
  "questions": [],
  "atsJobId": "1234567",
  "workplaceType": "hybrid",
  "city": "Paris",
  "region": null,
  "country": "France",
  "countryCode": "FR",
  "allLocations": ["Paris, France"],
  "employmentType": "Full-time",
  "updatedAt": "2026-10-16T08:15:00.000Z",
  "applyUrl": "https://boards.greenhouse.io/stripe/jobs/1234567",
  "requisitionId": "JR-9941",
  "changeStatus": "new",
  "scrapedAt": "2026-10-19T20:00:00.000Z"
}
```

***

### Local Development & Testing

```bash
## Install dependencies
npm install

## Validate all Apify schemas (.actor/*.json)
apify validate-schema

## Run linter and unit/integration test suite
npm run lint
npm test

## Run the Actor locally
apify run
```

# Actor input Schema

## `targets` (type: `array`):

One company per line. Accepted: a raw board slug (<code>stripe</code>, <code>openai</code> - the ATS is auto-detected in parallel), a prefixed slug (<code>greenhouse:stripe</code>, <code>lever:palantir</code>, <code>lever-eu:acme</code>, <code>ashby:dust</code>), an ATS board URL (<code>https://boards.greenhouse.io/stripe</code>, <code>https://jobs.lever.co/palantir</code>, <code>https://jobs.eu.lever.co/acme</code>, <code>https://jobs.ashbyhq.com/dust</code>) or any careers page URL that embeds one of these boards. Through the API you can also pass objects such as <code>{"url": "https://jobs.lever.co/acme", "company": "Acme Inc."}</code>.

## `techKeywords` (type: `array`):

Keep only jobs whose title or description mentions at least one of these technologies / tools (whole-word, synonym-aware: e.g. <code>Kubernetes</code> matches <code>K8s</code>, <code>PostgreSQL</code> matches <code>Postgres</code>, <code>Salesforce</code> matches <code>SFDC</code>; short all-caps acronyms such as AI or ML and common-word brands such as Go, Clay or Gong use context/case disambiguation). Leave empty to keep all jobs (and rely on automatic 120+ tech stack detection).

## `keywordMatchScope` (type: `string`):

Where custom <code>techKeywords</code> are searched. <b>Title, department & description</b> catches stack mentions in the job body (best for buying signals); <b>Title only</b> is stricter.

## `autoDetectTechStack` (type: `boolean`):

Automatically scan every matching job against a built-in dictionary of 120+ technologies across 7 categories (AI & LLM, Data & Analytics, Cloud & DevOps, Databases, Languages & Frameworks, CRM/GTM/RevOps, Security & Compliance), populating <code>fullTechStack</code> and <code>techCategories</code> (plus <code>detectedTechKeywords</code> when no custom filter is set).

## `publishedAfter` (type: `string`):

Only return jobs published after this date. Use an absolute date <code>YYYY-MM-DD</code> (e.g. <code>2026-10-01</code>) or a relative duration: <code>24h</code>, <code>7d</code>, <code>2w</code>, <code>3m</code> (months), <code>1y</code>. Leave empty for no date filter.

## `onlyWithCompensation` (type: `boolean`):

Keep only jobs that publish a pay range (structured ATS field, or parsed from the job description if enabled).

## `includeQuestions` (type: `boolean`):

Fetch the application form questions of every delivered job (label, required, type, options) - useful for auto-apply tools. Adds one extra HTTP request per job on Greenhouse and Ashby.

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

Remember the IDs and fingerprints of jobs already delivered (in the named key-value store <code>multi-ats-hiring-signals-state</code>), track closed/filled roles since the last run, and only output new (or salary-updated) postings. Ideal for daily scheduled runs.

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

Optional pre-filter on job titles (case-insensitive substring match, e.g. <code>Engineer</code>, <code>Architect</code>, <code>Account Executive</code>, <code>RevOps</code>). Leave empty for all titles.

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

Optional exclusion filter on job titles (case-insensitive substring match, e.g. <code>Intern</code>, <code>Recruiter</code>, <code>Executive Assistant</code>).

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

Optional filter on normalized seniority level. Allowed values: <code>c-level</code>, <code>vp</code>, <code>director</code>, <code>head</code>, <code>principal</code>, <code>staff</code>, <code>lead</code>, <code>manager</code>, <code>senior</code>, <code>mid</code>, <code>junior</code>, <code>intern</code>. Leave empty for all levels.

## `departmentCategories` (type: `array`):

Optional filter on normalized department category. Allowed values: <code>Engineering</code>, <code>Data & AI</code>, <code>Sales & GTM</code>, <code>Revenue Operations</code>, <code>Security & IT</code>, <code>Product & Design</code>, <code>Customer Success</code>, <code>Marketing</code>, <code>People & Talent</code>, <code>Finance & Legal</code>, <code>Operations</code>, <code>Other</code>. Leave empty for all departments.

## `workplaceTypes` (type: `array`):

Optional filter on workplace policy: <code>remote</code>, <code>hybrid</code>, <code>onsite</code>. Leave empty for all workplace types.

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

Name of the memory used by delta mode. Use a different key per task/schedule so that independent monitors do not share their history.

## `emitUpdatedInDelta` (type: `boolean`):

When delta mode (<code>onlyNew</code>) is enabled, also output previously seen jobs if their title, location or salary band changed since the last run (marked with <code>changeStatus = "updated"</code>).

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

Add the plain-text job description to every item (<code>description</code> field). Makes the dataset larger.

## `parseCompensationFromDescription` (type: `boolean`):

When the ATS has no structured salary field, try to extract a pay range written in the job description (e.g. "The base salary range is $150,000 - $180,000"). Such ranges are flagged with <code>compensation.source = "job_description"</code>.

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

Maximum number of jobs delivered per company. 0 = unlimited.

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

Maximum number of jobs delivered in total. 0 = unlimited.

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

Upper bound of parallel requests to the ATS APIs. The default is gentle and fast enough for hundreds of companies.

## Actor input object example

```json
{
  "targets": [
    "stripe",
    "https://jobs.lever.co/palantir",
    "https://jobs.ashbyhq.com/dust"
  ],
  "techKeywords": [
    "Snowflake",
    "Salesforce",
    "Rust",
    "HubSpot",
    "AI"
  ],
  "keywordMatchScope": "titleAndDescription",
  "autoDetectTechStack": true,
  "publishedAfter": "30d",
  "onlyWithCompensation": false,
  "includeQuestions": false,
  "onlyNew": false,
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "seniorityLevels": [],
  "departmentCategories": [],
  "workplaceTypes": [],
  "stateKey": "default",
  "emitUpdatedInDelta": true,
  "includeDescription": false,
  "parseCompensationFromDescription": true,
  "maxJobsPerCompany": 0,
  "maxResults": 0,
  "maxConcurrency": 10
}
```

# Actor output Schema

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

Normalized job postings with detected tech keywords, compensation and location.

## `summary` (type: `string`):

Per-company hiring signals: buying signal score, keyword hits, top departments, salary benchmark, failed targets.

# 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 = {
    "targets": [
        "stripe",
        "https://jobs.lever.co/palantir",
        "https://jobs.ashbyhq.com/dust"
    ],
    "techKeywords": [
        "Snowflake",
        "Salesforce",
        "Rust",
        "HubSpot",
        "AI"
    ],
    "publishedAfter": "30d"
};

// Run the Actor and wait for it to finish
const run = await client.actor("vesper_studio/multi-ats-tech-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 = {
    "targets": [
        "stripe",
        "https://jobs.lever.co/palantir",
        "https://jobs.ashbyhq.com/dust",
    ],
    "techKeywords": [
        "Snowflake",
        "Salesforce",
        "Rust",
        "HubSpot",
        "AI",
    ],
    "publishedAfter": "30d",
}

# Run the Actor and wait for it to finish
run = client.actor("vesper_studio/multi-ats-tech-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 '{
  "targets": [
    "stripe",
    "https://jobs.lever.co/palantir",
    "https://jobs.ashbyhq.com/dust"
  ],
  "techKeywords": [
    "Snowflake",
    "Salesforce",
    "Rust",
    "HubSpot",
    "AI"
  ],
  "publishedAfter": "30d"
}' |
apify call vesper_studio/multi-ats-tech-hiring-signals --silent --output-dataset

```

## MCP server setup

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