# ATS Job Scraper API — Greenhouse, Ashby & Lever (`acebuilds/ats-jobs-intel`) Actor

Pull fresh jobs straight from company ATS boards (Greenhouse, Ashby, Lever) before they reach Indeed, plus a hiring-intelligence summary per company: velocity, department investment, seniority mix and buying signals. For recruiters and sales teams.

- **URL**: https://apify.com/acebuilds/ats-jobs-intel.md
- **Developed by:** [Ace](https://apify.com/acebuilds) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.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/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

## ATS Jobs Intelligence

**Fresh jobs straight from company career boards — before they hit Indeed — plus a hiring-intelligence summary for every company.**

Most job scrapers fight Indeed's and LinkedIn's anti-bot defenses and hand you stale, deduplicated rows. This actor goes to the **source**: the public JSON APIs behind companies' own Applicant Tracking Systems (Greenhouse, Ashby, Lever). The data is fresh, structured, and un-scraped — and on top of it you get a per-company **intelligence layer** that answers the question raw rows can't: *is this company scaling, and where?*

Built for **recruiters** (fresh roles first), **sales/BD teams** (hiring is a buying signal), and **competitive intel** (watch a rival's org grow in real time).

***

### Supported ATS providers

| Provider | Board spec | Where the slug comes from |
|---|---|---|
| Greenhouse | `greenhouse:slug` | `boards.greenhouse.io/<slug>` |
| Ashby | `ashby:slug` | `jobs.ashbyhq.com/<slug>` |
| Lever | `lever:slug` | `jobs.lever.co/<slug>` |

All three are public, unauthenticated endpoints (verified live). No API keys required.

***

### Input

```json
{
  "boards": ["greenhouse:airbnb", "ashby:ramp", "lever:veeva"],
  "keywords": ["engineer", "product"],
  "department": "Engineering",
  "location": "Remote",
  "remoteOnly": false,
  "postedWithinDays": 30,
  "maxJobsPerCompany": 1000,
  "includeIntelligence": true
}
```

| Field | Type | Notes |
|---|---|---|
| `boards` (required) | string\[] | `"provider:slug"` entries |
| `keywords` | string\[] | Title must contain at least one (case-insensitive) |
| `department` | string | Department contains (case-insensitive) |
| `location` | string | Location contains (case-insensitive) |
| `remoteOnly` | boolean | Only remote-flagged roles |
| `postedWithinDays` | integer | Only roles published within N days (0 = off) |
| `maxJobsPerCompany` | integer | Cap on emitted job rows per board |
| `includeIntelligence` | boolean | Emit the per-company summary row (default true) |
| `mode` | string | `snapshot` (default) or `watchlist` — see below |
| `watchlistName` | string | Names the baseline in watchlist mode (default `default`) |

***

### Output

The dataset contains two record types, distinguished by `recordType` (with matching dataset **views**):

**`recordType: "job"`** — one per matched role:

```
provider, company, id, title, department, team, location,
remote, employmentType, publishedAt, updatedAt, url, applyUrl
```

**`recordType: "company_intelligence"`** — one per board (unless disabled):

```
totalOpenRoles, newRoles7d, newRoles30d, hiringVelocity,
pctOpenedLast30d, remotePct, topDepartments, topLocations,
fastestGrowingDepartment, seniorityMix, buyingSignals, summary
```

`hiringVelocity` is one of `Aggressive` (≥25% of roles opened in 30d), `Active` (≥10%), `Steady` (<10%), `Frozen` (0 new), or `Unknown` (no dated roles). Intelligence is computed on the company's **full** role set, so it reflects real hiring posture regardless of your keyword/location filter.

Example `summary`:

> Ramp: 125 open roles, hiring aggressive · 41 opened in the last 30d · fastest-growing team: Sales · 38% remote. Signals: Scaling Sales — 6 new roles in 30d; Building leadership — 2 new director/exec role(s) in 30d.

***

### Watchlist mode — track changes over time

Set `mode: "watchlist"` to turn the actor from a one-time snapshot into a **change feed**. Instead of returning every current job each run, it remembers the last run and reports only **what changed** since — ideal for a **scheduled** run (daily/weekly) that alerts you when target companies start (or stop) hiring.

**How it works:**

1. **First run** on a given `watchlistName` + board just saves a **baseline** (a free `baseline` row — nothing is charged).
2. **Every run after** diffs the live board against that baseline and emits `recordType: "change"` rows:

| `changeType` | Meaning | Billed? |
|---|---|---|
| `new_role` | A role that wasn't there last run | ✅ as `job-scraped` |
| `removed_role` | A role that's been filled or pulled | Free |
| `company_shift` | Posture digest: velocity change, new buying signals, new/removed counts | ✅ as `company-intelligence` |
| `no_change` | Nothing changed for that board this run | Free |
| `baseline` | First-run seed | Free |

3. The baseline advances to the current state after each run, so you only ever see genuinely new changes.

Use `watchlistName` to keep separate histories (e.g. `competitors`, `target-accounts`). Set `includeIntelligence: false` to get only role-level changes without the posture digest. See the **Changes** dataset view for a clean table.

**Who it's for:** sales/BD teams treating hiring as a buying signal, recruiters watching competitors open roles, and investors/analysts tracking a company's org growth week over week.

> Tip: schedule watchlist runs in Apify (Schedules) and wire the dataset to a webhook/Slack for automatic hiring alerts.

### Pricing

**Pay per event** — you are charged only for the results you receive:

- **`job-scraped`** — one charge per job row returned.
- **`company-intelligence`** — one charge per company hiring-intelligence summary.

Boards that error (wrong slug, company migrated off that ATS) are **never charged** — error rows are free. The intelligence summary is priced separately from job rows because it's the differentiator versus commodity single-board scrapers: set `includeIntelligence: false` to skip it and pay for job rows only.

***

### Notes & limits

- **Provider field coverage differs** (from each ATS's public API): Greenhouse omits `team`/`employmentType`; Ashby and Lever omit `updatedAt`. Missing fields are `null`, never guessed.
- Lever's `createdAt` is epoch-ms and is normalized to ISO-8601 like the others.
- A board that errors (wrong slug, company migrated off that ATS) emits a `recordType: "error"` row and does not stop the run.
- Only `isListed` Ashby roles are returned.

# Actor input Schema

## `boards` (type: `array`):

List of ATS boards to scrape, each as "provider:slug". Provider is one of greenhouse, ashby, lever. The slug is the company's board identifier in its careers URL (e.g. boards.greenhouse.io/AIRBNB → "greenhouse:airbnb", jobs.ashbyhq.com/RAMP → "ashby:ramp", jobs.lever.co/VEEVA → "lever:veeva").

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

Only return jobs whose title contains at least one of these keywords (case-insensitive). Leave empty to return all roles.

## `department` (type: `string`):

Only return jobs whose department contains this text (case-insensitive). E.g. "Engineering", "Sales".

## `location` (type: `string`):

Only return jobs whose location contains this text (case-insensitive). E.g. "New York", "London", "Remote".

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

If true, return only roles flagged remote (or whose location mentions remote).

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

Only return roles first published within this many days. 0 (default) disables the filter. Note: roles without a publish date are excluded when this is set.

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

Cap on job rows emitted per board. Lower = faster + cheaper.

## `includeIntelligence` (type: `boolean`):

If true (default), emit one company-intelligence summary row per board: hiring velocity, department investment, seniority mix, and buying signals.

## `mode` (type: `string`):

snapshot (default) = return all current jobs + intelligence every run. watchlist = remember the last run and report only what CHANGED since (new roles, removed/filled roles, hiring-posture shifts). Watchlist is built for scheduled runs — the first run just saves a baseline. New roles bill as job-scraped and posture-shift digests as company-intelligence; removed roles and the baseline are free.

## `watchlistName` (type: `string`):

Names the baseline this run diffs against, so you can keep multiple independent watchlists (e.g. "competitors", "target-accounts"). Runs sharing a name and board build on the same history. Ignored in snapshot mode.

## Actor input object example

```json
{
  "boards": [
    "greenhouse:airbnb",
    "ashby:ramp",
    "lever:veeva"
  ],
  "keywords": [],
  "department": "",
  "location": "",
  "remoteOnly": false,
  "postedWithinDays": 0,
  "maxJobsPerCompany": 1000,
  "includeIntelligence": true,
  "mode": "snapshot",
  "watchlistName": "default"
}
```

# Actor output Schema

## `results` (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 = {
    "boards": [
        "greenhouse:airbnb",
        "ashby:ramp",
        "lever:veeva"
    ],
    "keywords": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("acebuilds/ats-jobs-intel").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 = {
    "boards": [
        "greenhouse:airbnb",
        "ashby:ramp",
        "lever:veeva",
    ],
    "keywords": [],
}

# Run the Actor and wait for it to finish
run = client.actor("acebuilds/ats-jobs-intel").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 '{
  "boards": [
    "greenhouse:airbnb",
    "ashby:ramp",
    "lever:veeva"
  ],
  "keywords": []
}' |
apify call acebuilds/ats-jobs-intel --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,acebuilds/ats-jobs-intel"
        }
    }
}

```

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/xm6HGt5E53kdtjWKE/builds/n0Dz0vQab05dHT5zf/openapi.json
