# ATS Job Scraper - Greenhouse, Lever & Ashby (`vdb997/ats-job-watch`) Actor

Extract live jobs from Greenhouse, Lever and Ashby. Track new, changed and removed postings across runs, with source URLs, salary data where published, and failure-safe snapshots. No cookies or API keys. Built for recruitment workflows and AI agents.

- **URL**: https://apify.com/vdb997/ats-job-watch.md
- **Developed by:** [Victor Diaz](https://apify.com/vdb997) (community)
- **Categories:** Jobs, Automation, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $25.00 / 1,000 company board checkeds

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 Job Scraper - Greenhouse, Lever & Ashby

Get live company job postings and track **new, changed and removed jobs** across repeated runs. Paste job-board URLs, optionally give the watch a name, and export consistent JSON, CSV or Excel through Apify.

Use it for a recruiting watchlist, a job-board feed, company hiring research, or an AI agent that needs current openings with verifiable source links. It reads public ATS feeds without cookies, proxy configuration, third-party API keys or LLM charges.

### Who this is for

**Recruiting and hiring research.** Use the ATS job scraper to keep a recruiting watchlist or job feed current across Greenhouse, Lever and Ashby. Start with one company board, export its current jobs, then reuse a Watch ID to receive newly observed, edited and removed postings.

### Quick start

```json
{
  "boards": ["greenhouse:stripe", "lever:palantir", "ashby:Ashby"],
  "includeDescriptions": true
}
```

Supported inputs include `https://job-boards.greenhouse.io/stripe`, `https://jobs.lever.co/palantir`, `https://jobs.ashbyhq.com/Ashby`, and `lever-eu:company` for European Lever boards. Copy the company identifier exactly; Ashby names are case-sensitive. Company homepages and other ATS providers are not supported.

### Track changes automatically

```json
{
  "boards": ["greenhouse:stripe", "ashby:Ashby"],
  "watchId": "my-engineering-watch",
  "outputMode": "changes",
  "titleKeywords": ["engineer", "developer"],
  "removalConfirmations": 2,
  "includeDescriptions": false
}
```

Save this input as an Apify Task and schedule it daily. Use the same Watch ID for the same watch. The first run creates a baseline; subsequent runs return changes. In n8n or Make, keep dataset rows with `recordType = job` for your job workflow, and route `board_error` rows to your error handling.

The Actor keeps snapshots in a named key-value store in **your Apify account**. Snapshots contain normalized job metadata and a description hash, not full historical descriptions. A new Watch ID starts a fresh baseline. Leave Watch ID blank for extraction without persistent tracking. Do not overlap runs sharing a Watch ID; overlapping execution is unsupported. Named snapshot storage persists until you delete it and is subject to your account's storage retention and pricing.

#### What each change means

| `changeType` | Meaning |
| --- | --- |
| `baseline` | First successful observation; returned only in All jobs mode. |
| `new` | A source job ID appeared after the baseline. This does not prove the role was created today. |
| `changed` | Title, location, department, employment type, workplace type, published compensation, description hash, or job/apply URL changed. `changes` includes before/after values; descriptions use hashes. |
| `unchanged` | No change to those fields. Returned only in All jobs mode. |
| `removed` | The source ID was absent for the required number of successful checks. This means removed from the public feed, not proof of a filled position. Emitted once. |

The default is **two successful checks** to confirm removal. A failed request, malformed response, oversized board or incomplete Greenhouse response never advances the disappearance counter or replaces the previous snapshot. A temporary disappearance followed by a return keeps the original `firstSeenAt`. If a posting returns after a confirmed removal, it is treated as new because the old record has left the active snapshot.

Changing title filters does not create false removals: the complete board is compared before the output filter is applied. Keywords are a case-insensitive substring OR match, not semantic search. Boards omitted from a run are left untouched.

### Output

The default dataset has three record types:

- **`job`**: one current or changed job, with `jobKey`, `nativeId`, `boardId`, `provider`, `company`, `title`, `locations`, `departments`, `employmentType`, `workplaceType`, `salaryRanges`, `salaryText`, `publishedAt`, `updatedAt`, `jobUrl`, `applyUrl`, `description`, `descriptionHash`, `sourceUrl`, `firstSeenAt`, `lastSeenAt`, `observedAt`, `changeType`, `changedFields` and `changes`.
- **`board_summary`**: one successfully checked board, with `jobsOpen`, `emittedJobs`, counts for every change type, `pendingMissing` and `trackingEnabled`.
- **`board_error`**: a failed board with a readable error and `previousSnapshotPreserved: true`.

A `SUMMARY` JSON record in the run's key-value store reports successes, failures, rows written and spending-limit stops. Use the Output tab to open results or the summary. A changes-only run can correctly return zero job rows while still returning successful board summaries.

`company` uses the source-provided company name when available and otherwise the board identifier. Missing source fields remain null or empty. Greenhouse's list feed does not provide normalized structured compensation: it is not guessed from prose. Lever and Ashby compensation is returned only when published by the source, retaining original currency and interval. `firstSeenAt` is this watch's observation time. Ashby's `publishedAt` can be its last republish time; `updatedAt` is never relabeled as a publication date. Remote status is not inferred from free-text descriptions.

### Example result

A real result from the launch verification (timestamps show when this watch observed the posting):

```json
{
  "recordType": "job",
  "changeType": "baseline",
  "provider": "greenhouse",
  "boardId": "greenhouse:stripe",
  "title": "Abuse Research Engineer",
  "locations": [
    "Remote from the US"
  ],
  "salaryRanges": [],
  "jobUrl": "https://stripe.com/jobs/search?gh_jid=8172503",
  "firstSeenAt": "2026-10-06T23:54:17.619Z",
  "observedAt": "2026-10-06T23:54:17.619Z"
}
```

### Pricing

The price is **$0.025 per successfully checked company board**, regardless of the number of jobs or changes it returns, plus Apify's standard **$0.00005 start event** at the supported 512 MB memory allocation. Platform usage is included. Check the Actor's Pricing tab for the active price.

- 3 successful boards: $0.07505 per run.
- 10 boards checked daily for 30 days: $7.50150 in Actor event charges.
- A valid empty board and an unchanged board count as successful checks.
- Failed board checks, individual jobs, summaries and errors have no separate event fee.
- No proxy, LLM or external API subscription is required. Your Apify account's storage and retention rules still apply.

The Actor checks the platform spending limit before each board and stops when it cannot charge another board check. Up to 20 unique boards per run, 5,000 jobs per board, 25 MB uncompressed source response, and a 30-second timeout per request. A board exceeding a limit is reported as an error; partial data is not treated as a complete snapshot.

### API and AI-agent workflows

Run this Actor with the Apify API or Apify MCP server. It uses limited permissions and a small input schema. Agents should inspect board summaries and errors before interpreting absent job rows. This Actor reads source content as data and does not execute instructions found in job descriptions.

Suggested agent request: “Check these company job boards with Watch ID `research-watch`. Return changed engineering jobs and tell me which sources failed.”

### Source coverage and limitations

Sources are the documented [Greenhouse Job Board API](https://docs.greenhouse.io/job-board.html), [Lever Postings API](https://github.com/lever/postings-api), and [Ashby Job Postings API](https://developers.ashbyhq.com/docs/public-job-posting-api). Only publicly listed postings are included; Ashby records marked unlisted are excluded.

This is an independent tool, not affiliated with those providers or employers. It does not apply for jobs, collect applicant records, rank candidates, authenticate to employer systems, or bypass blocks. No claim is made that an opening is genuine, funded, recently created or still recruiting beyond what the public source reports. Respect source terms and your intended use of the data.

If a board fails, open an issue on this Actor with the public board URL and run ID. Do not include API keys or applicant data.

### Try one source before scheduling

1. Open the Input tab and use one supported source with `outputMode: "all"` for an immediate export.
2. Check the returned records and the check summary. Confirm the source, coverage and price suit your workflow.
3. To monitor, set a stable Watch ID and `outputMode: "changes"`. The first successful run establishes a silent baseline.
4. Reuse that Watch ID on later runs. A summary without change records means the check completed without matching changes.
5. Add an Apify schedule after the manual checks work. Avoid overlapping runs with the same Watch ID. Use Maximum cost per run to cap event charges.

Public-data monitors by the same developer: [browse the catalog](https://apify.com/vdb997).

# Actor input Schema

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

1–20 Greenhouse, Lever or Ashby job-board URLs, or provider:slug (for example greenhouse:stripe, lever:palantir, ashby:Ashby). Company homepages are not supported. Ashby names are case-sensitive.

## `watchId` (type: `string`):

Reuse the same ID on future runs to keep per-board snapshots in your account. Leave blank for a one-off extraction. Use a new ID to reset. Do not run the same watch concurrently.

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

All returns current jobs and confirmed removals. Changes returns only new, changed and removed jobs; its first run establishes a baseline without job alerts. Every board also returns a summary or error row.

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

Case-insensitive OR filter, for example engineer or account executive. Filtering affects output only, so changing keywords does not create false removals. Leave empty for all titles.

## `removalConfirmations` (type: `integer`):

Default 2: a posting must be missing from two successful board checks before it is reported removed. Failed requests never count. This means removed from the public feed, not proof the role was filled.

## `includeDescriptions` (type: `boolean`):

Return plain-text descriptions. Changes are still detected if this is off. Removed rows have no description, because historical snapshots store its hash rather than the full text.

## Actor input object example

```json
{
  "boards": [
    "greenhouse:stripe",
    "lever:palantir",
    "ashby:Ashby"
  ],
  "outputMode": "all",
  "titleKeywords": [],
  "removalConfirmations": 2,
  "includeDescriptions": true
}
```

# Actor output Schema

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

Dataset of recordType job, board_summary and board_error. Filter recordType=job for job data; use changeType for alerts.

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

Checked boards, failures, record counts and whether the spending limit stopped work.

# 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:stripe",
        "lever:palantir",
        "ashby:Ashby"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("vdb997/ats-job-watch").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:stripe",
        "lever:palantir",
        "ashby:Ashby",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("vdb997/ats-job-watch").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:stripe",
    "lever:palantir",
    "ashby:Ashby"
  ]
}' |
apify call vdb997/ats-job-watch --silent --output-dataset

```

## MCP server setup

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

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/joPFMjyeILHJmM9oz/builds/oxRwUgjRcbZACRdkE/openapi.json
