# Hiring Signal Tracker: Greenhouse, Lever & Ashby Job Changes (`upward_enterprises/hiring-signals-watchlist`) Actor

Watch any list of company job boards on Greenhouse, Lever and Ashby and get one row per change: job opened, closed, reposted, or retitled, with title, department, location, seniority, days open, and link, plus a free per-board hiring-velocity summary. Official APIs, no keys. From $2.50 per 1,000.

- **URL**: https://apify.com/upward_enterprises/hiring-signals-watchlist.md
- **Developed by:** [Upward Enterprises](https://apify.com/upward_enterprises) (community)
- **Categories:** Lead generation, Jobs, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.25 / 1,000 hiring events

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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 Signal Tracker: Greenhouse, Lever & Ashby Job Changes

**$4 per 1,000 hiring events ($2.50 on the Gold tier) plus $0.50 per 1,000 board checks, no API key, no start fee. Every event is delivered once.**

Give it the job boards of the companies you care about, hosted on Greenhouse, Lever, or Ashby, and run it on a schedule. Each run compares every board with what it saw last time and delivers one row per change: a job **opened**, a job **closed** (filled or pulled), a job **reposted** with a new date, or a job whose title, department, or location **changed**. Each board also gets a summary row with its open count, this run's changes, jobs opened and closed in the last 7 and 30 days, the median days open, and how many postings have been open more than 90 days.

Built on the three ATS vendors' official public job-board APIs, the same endpoints the companies' own career pages read. No login, no scraping of HTML, no keys.

### What you get

**Event rows** (`kind: "event"`):

```json
{
  "kind": "event",
  "eventType": "opened",
  "board": "greenhouse:stripe",
  "ats": "greenhouse",
  "boardToken": "stripe",
  "company": "Stripe",
  "boardUrl": "https://job-boards.greenhouse.io/stripe",
  "jobId": "8172508",
  "title": "Abuse Investigator",
  "department": "Risk",
  "team": null,
  "location": "Dublin",
  "locations": ["Dublin"],
  "workplaceType": null,
  "employmentType": null,
  "seniority": "mid",
  "publishedAt": "2026-09-03T17:32:53.000Z",
  "firstSeen": "2026-09-10T12:00:00.000Z",
  "daysOpen": 0,
  "previous": null,
  "baseline": false,
  "url": "https://stripe.com/jobs/search?gh_jid=8172508",
  "observedAt": "2026-09-10T12:00:00.000Z",
  "billedAs": "job-event"
}
```

`eventType` is `opened`, `closed`, `reposted`, or `changed`. For `changed`, `previous` holds the old title, department, or location; for `reposted`, the old posting date; for `closed`, when the job was last seen. `daysOpen` counts from when the tracker first saw the job (on a board's first check, from the posting date). `seniority` is a rule-based read of the title: intern, junior, mid, senior, staff, principal, manager, director, vp, executive.

**Board rows** (`kind: "board"`), one per board per run:

```json
{
  "kind": "board",
  "board": "lever:palantir",
  "company": "palantir",
  "status": "ok",
  "openCount": 310,
  "opened": 4,
  "closed": 2,
  "reposted": 0,
  "changed": 1,
  "eventsDelivered": 7,
  "eventsFiltered": 0,
  "opened7d": 11,
  "opened30d": 46,
  "closed7d": 9,
  "closed30d": 38,
  "ghostJobs": 27,
  "medianDaysOpen": 41.5,
  "checkedAt": "2026-09-10T12:00:00.000Z",
  "previousCheckAt": "2026-09-09T12:00:00.000Z",
  "billedAs": "board-check"
}
```

`status` is `ok`, `baseline` (first check, state recorded, no events yet), `not-found` (wrong token), `error`, or `invalid` (unrecognised board string). Only `ok` and `baseline` checks are billed. **One summary row** (`kind: "summary"`, free) totals everything and says whether the run hit your spending cap.

### Input

| Field | What it does |
|---|---|
| Job boards to watch | One per line: `greenhouse:stripe`, `lever:palantir`, `ashby:notion`, or the board URL (`https://job-boards.greenhouse.io/stripe`, `https://jobs.lever.co/palantir`, `https://jobs.ashbyhq.com/notion`). The token is the last part of the company's public job-board URL. |
| Events to deliver | Any of opened, closed, reposted, changed. Empty means all. |
| Title keywords, Departments, Locations | Only jobs matching any of the words or phrases, case-insensitive. Filters apply to events; the board summary always covers the whole board. |
| Deliver the current jobs on first check | Off by default: the first check of a board records its jobs and delivers a free board row; events begin with the second check. On: current jobs are delivered as `opened` events marked `baseline`. |
| Snapshot name | Which memory this watchlist uses, one record per board in a key-value store named `hiring-signals-watchlist-<name>`. Give each schedule its own name. |
| Max events per run | Default 2,000. Boards whose events did not fit are re-detected next run. |
| Proxy | Normally off. |

Examples:

```json
{ "boards": ["greenhouse:stripe", "lever:palantir", "ashby:notion"], "eventTypes": ["opened"], "titleKeywords": ["sales", "account executive", "revenue"] }
```

Tell me when any of these companies opens a sales role.

```json
{ "boards": ["https://job-boards.greenhouse.io/acme", "https://jobs.ashbyhq.com/globex"], "eventTypes": ["opened", "closed"], "departments": ["engineering", "data"] }
```

Engineering hiring velocity at two competitors, opened and closed, with the board rows giving the 7- and 30-day counts.

### Pricing

| Event | Free | Bronze | Silver | Gold and above |
|---|---|---|---|---|
| Hiring event | $0.004 | $0.0036 | $0.003 | $0.0025 |
| Board check | $0.0005 | $0.00045 | $0.0004 | $0.0003 |

A daily watchlist of 200 boards costs about $0.10 a day in checks. A typical mid-size board changes two to five jobs a day, so 200 boards produce around 600 events a day at most, $2.40 on the free tier, and far fewer with a keyword or department filter. The first check of each board is a single board-check fee; nothing else is billed until something changes. The summary row is free, and so are boards that could not be read. Set a maximum total charge on the run to cap spend; the Actor stops cleanly when it is reached, never delivers rows it could not bill, and does not advance a board's memory until its events have been delivered, so nothing is lost.

### Use cases

- **Sales triggers**: a company opening its first sales, RevOps, or security role is buying tools for it; pipe `opened` events with title keywords into Clay, HubSpot, or Slack.
- **Recruiting and staffing**: which target accounts opened roles in your specialty this week, and which roles closed (a filled req means a new hire to congratulate, or a pulled req means a freeze).
- **Competitive intelligence**: hiring velocity by department across competitors, from the board rows, without reading a single posting.
- **Job seekers and career coaches**: a personal watchlist of dream companies, filtered to the roles that matter, as they appear.
- **AI agents**: ask "which companies on my list opened engineering manager roles this week" through the Apify MCP server.

### Data source and limits

- Greenhouse (`boards-api.greenhouse.io`), Lever (`api.lever.co/v0/postings`), and Ashby (`api.ashbyhq.com/posting-api`) publish every company's public job board as JSON for embedding in career sites; the Actor reads those endpoints with a declared User-Agent and spaces requests per vendor (Lever's robots policy asks for one request a second). Job descriptions are not stored.
- Only public postings are visible: internal or unlisted jobs never appear. Companies on other ATS vendors (Workday, iCIMS, SmartRecruiters, Workable) are not supported yet; the board row says `invalid` for anything else.
- A `closed` event means the posting left the public board; the Actor cannot tell a filled role from a withdrawn one.
- Boards are compared between runs, so the cadence you schedule is the resolution you get. Daily is typical; hourly works.
- Rows are about companies and their open roles. No personal data is collected.
- At the end of each run the Actor reports run counts (rows delivered, events charged, duration, status) to its maintainer's dashboard. It never sends your input, your results, or anything about your account.

### About

Built and maintained by **Upward Enterprises · AI & API Services** (Iowa, USA). Public-data tools for people and AI agents, with pay-per-record pricing and no keys to manage.

### Changelog

- **0.1** (2026-09-10): first release.

# Actor input Schema

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

One board per line as ats:token or the board's URL. Greenhouse: greenhouse:stripe or https://job-boards.greenhouse.io/stripe. Lever: lever:palantir or https://jobs.lever.co/palantir. Ashby: ashby:notion or https://jobs.ashbyhq.com/notion. The token is the last part of the company's public job-board URL.

## `eventTypes` (type: `array`):

Which changes produce a billed event row. Empty means all four.

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

Only jobs whose title contains any of these words or phrases, case-insensitive, e.g. "engineer", "sales", "VP". Empty means every job.

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

Only jobs whose department or team contains any of these, case-insensitive. Empty means every department.

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

Only jobs whose location contains any of these, case-insensitive, e.g. "Remote", "New York", "Iowa". Empty means everywhere.

## `includeBaselineJobs` (type: `boolean`):

Off (default): the first check of a board only records its open jobs and delivers a free board summary; events start with the second check. On: the first check also delivers every currently open job as an "opened" event marked baseline, billed as events.

## `snapshotName` (type: `string`):

Which memory this watchlist uses. Kept on your account as a key-value store named hiring-signals-watchlist-<name>, one record per board. Give each schedule its own name.

## `maxEvents` (type: `integer`):

Upper limit on delivered event rows. Boards are still checked and their state updated; the summary row says whether the limit cut the results.

## `proxyConfiguration` (type: `object`):

Normally not needed: these are official public APIs. Turn a proxy on only if the summary row reports rate limiting.

## Actor input object example

```json
{
  "boards": [
    "greenhouse:stripe",
    "lever:palantir",
    "ashby:notion"
  ],
  "eventTypes": [],
  "titleKeywords": [],
  "departments": [],
  "locations": [],
  "includeBaselineJobs": false,
  "snapshotName": "default",
  "maxEvents": 2000,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `all` (type: `string`):

No description

## `eventColumns` (type: `string`):

No description

## `boardColumns` (type: `string`):

No description

## `csv` (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:stripe",
        "lever:palantir",
        "ashby:notion"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("upward_enterprises/hiring-signals-watchlist").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:notion",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("upward_enterprises/hiring-signals-watchlist").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:notion"
  ]
}' |
apify call upward_enterprises/hiring-signals-watchlist --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,upward_enterprises/hiring-signals-watchlist"
        }
    }
}
```

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/T927qRZfOCBajncmt/builds/csAckZJxl6l5Hna7p/openapi.json
