# Career Site Jobs Monitor - Greenhouse, Lever, Ashby & more (`iwc/career-site-jobs-monitor`) Actor

Live job postings from career sites on Greenhouse, Lever, Ashby, Workable and SmartRecruiters via their public JSON APIs. No login, no proxies. Paste a careers URL and the ATS is auto-detected. Stable dedup keys, ISO dates and keyword/location/date filters for new-roles-since-last-run monitoring.

- **URL**: https://apify.com/iwc/career-site-jobs-monitor.md
- **Developed by:** [Imran](https://apify.com/iwc) (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

$1.00 / 1,000 job posting 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

## Career Site Jobs Monitor — Greenhouse, Lever, Ashby, Workable, SmartRecruiters

Pull live job postings straight from company career sites. Give it a list of companies
(an ATS board URL, an `ats:slug`, or just the company's careers page) and it returns every
open role as flat JSON — with a stable `dedupKey`, ISO timestamps and filters, so you can
run it on a schedule and get **only the roles that are new since last time**.

**No login, no API key, no proxy.** Each supported ATS publishes a public JSON endpoint that
companies use to syndicate their own postings. This actor reads those endpoints and nothing
else, which is why it's fast and doesn't break the way page scrapers do.

### Supported ATS platforms

| ATS | What you can pass | Descriptions |
|---|---|---|
| Greenhouse | `greenhouse:stripe`, `https://boards.greenhouse.io/stripe`, `job-boards.greenhouse.io/…`, EU boards | ✔ (`includeDescription`) |
| Lever | `lever:spotify`, `https://jobs.lever.co/spotify`, EU boards | ✔ |
| Ashby | `ashby:notion`, `https://jobs.ashbyhq.com/notion` | ✔ (+ published compensation) |
| Workable | `workable:acme`, `https://apply.workable.com/acme`, `acme.workable.com` | ✖ (list endpoint only) |
| SmartRecruiters | `smartrecruiters:BoschGroup`, `https://jobs.smartrecruiters.com/BoschGroup` | ✖ (list endpoint only) |

**Any careers page URL** (`https://acme.com/careers`) also works: the actor fetches the page,
finds the embedded board of any of the five ATSs, and continues from there. If a page uses an
unsupported ATS you get a warning naming the URL, never a silent empty result.

### What it does

1. Resolves each company entry to an ATS board (explicit, by URL, or by page discovery).
2. Reads the board's public JSON API, following pagination where the ATS paginates.
3. Normalises every posting into the same flat record regardless of ATS.
4. Applies your filters — title keywords, excluded keywords, locations (incl. `remote`), `postedAfter`.
5. Deduplicates within the run and pushes one record per posting, up to `maxItems`.

### Input

| Field | Type | Default | Notes |
|---|---|---|---|
| `companies` | array of strings | — | **Required.** ATS URLs, `ats:slug`, or careers page URLs |
| `keywords` | array | `[]` | Keep postings whose title contains any keyword (case-insensitive) |
| `excludeKeywords` | array | `[]` | Drop postings whose title contains any of these |
| `locations` | array | `[]` | Substring match on location; include `"remote"` to also keep remote-flagged roles |
| `postedAfter` | string | — | ISO date/date-time. Drops older postings; keeps postings with no date |
| `includeDescription` | boolean | `false` | Adds plain-text `descriptionText` (Greenhouse, Lever, Ashby) |
| `maxItems` | integer | `500` | Billing guardrail — hard cap on postings pushed per run |
| `proxyConfiguration` | object | off | Not needed for these APIs; enable only at very high volume |

Example — every engineering role at four companies, UK or remote, posted this month:

```json
{
  "companies": ["greenhouse:stripe", "https://jobs.lever.co/spotify", "https://jobs.ashbyhq.com/notion", "https://jobs.smartrecruiters.com/BoschGroup"],
  "keywords": ["engineer"],
  "locations": ["United Kingdom", "London", "remote"],
  "postedAfter": "2026-08-01",
  "maxItems": 200
}
```

### Output

One JSON object per posting in the default dataset:

| Field | Type | Description |
|---|---|---|
| `source` | string | `greenhouse` | `lever` | `ashby` | `workable` | `smartrecruiters` |
| `company` | string | Board slug on that ATS |
| `companyName` | string | null | Display name when the ATS provides it |
| `jobId` | string | Posting ID on the ATS |
| `title` | string | Job title |
| `location` | string | null | Location text |
| `isRemote` | boolean | ATS remote flag, or inferred from the location text |
| `department`, `team` | string | null | When the ATS provides them |
| `employmentType` | string | null | e.g. `Full-time` |
| `compensation` | string | null | Published pay range (Ashby) |
| `postedAt` | string | null | ISO 8601 publish date |
| `updatedAt` | string | null | ISO 8601 last update (Greenhouse) |
| `url`, `applyUrl` | string | Public posting / apply links |
| `descriptionText` | string | null | Plain text, only with `includeDescription` |
| `dedupKey` | string | **Stable key** `source:company:jobId` |
| `scrapedAt` | string | ISO 8601 |

### Monitoring: new roles since last run

`dedupKey` never changes for a given posting, so the recipe is:

1. Schedule the actor (daily works well; hourly for hot accounts).
2. Either set `postedAfter` to the previous run's time, or diff each run's dataset against the
   last one on `dedupKey` — new keys are new roles, missing keys are closed roles.
3. Send the difference wherever you like (Slack, email, a Google Sheet) with an Apify integration.

Typical uses: hiring-signal alerts for sales and recruitment teams, tracking competitor
hiring, feeding a niche job board, watching a shortlist of dream companies.

### Pricing

Pay per event: **one charge per posting pushed to the dataset**. Postings removed by your
filters are free. `maxItems` caps the spend of any single run.

### Notes & limitations

- Workable and SmartRecruiters list endpoints don't include descriptions; `includeDescription`
  is ignored for them.
- A board that returns 404 (wrong slug, or the company moved to a different ATS) is reported
  in the log and skipped — it doesn't fail the run.
- Discovery reads the careers page HTML you give it. Boards injected by client-side JavaScript
  that never appear in the HTML source can't be found that way — pass the board URL directly.
- Sources are the ATSs' own public APIs; the actor sends a descriptive User-Agent and backs
  off on 429.

### Development

```bash
npm install
npm test          # unit tests (parsers, normalisers, filters — no network)
apify run         # local run with storage/key_value_stores/default/INPUT.json
```

# Actor input Schema

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

One entry per company. Accepts an ATS board URL (https://boards.greenhouse.io/stripe, https://jobs.lever.co/spotify, https://jobs.ashbyhq.com/notion, https://apply.workable.com/acme, https://jobs.smartrecruiters.com/BoschGroup), an explicit "ats:slug" (greenhouse:stripe), or any company careers page URL — the actor fetches the page and detects the embedded ATS board automatically.

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

Keep only postings whose title contains at least one of these (case-insensitive). Leave empty for all roles.

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

Drop postings whose title contains any of these (case-insensitive), e.g. "intern", "senior".

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

Keep only postings whose location contains one of these substrings, e.g. "London", "United Kingdom". Add "remote" to also keep remote-flagged roles.

## `postedAfter` (type: `string`):

ISO date or date-time (e.g. 2026-08-01 or 2026-08-01T00:00:00Z). Postings published before this are dropped; postings with no publish date are kept. Use it on a schedule to fetch only new roles.

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

Adds a plain-text `descriptionText` field. Supported for Greenhouse, Lever and Ashby (Workable and SmartRecruiters list endpoints do not carry descriptions). Increases output size; charging is per job, not per byte.

## `maxItems` (type: `integer`):

Hard cap on the number of postings pushed (and charged) in this run. The run stops once it's reached.

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

OFF by default. The ATS endpoints used here are public syndication APIs that do not block datacenter traffic, so no proxy is needed. Enable Apify Proxy only if you run very high volume and see 429s.

## Actor input object example

```json
{
  "companies": [
    "greenhouse:stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/notion",
    "https://jobs.smartrecruiters.com/BoschGroup"
  ],
  "keywords": [],
  "excludeKeywords": [],
  "locations": [],
  "includeDescription": false,
  "maxItems": 500,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

## `jobsCsv` (type: `string`):

No description

## `run` (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": [
        "greenhouse:stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/notion",
        "https://jobs.smartrecruiters.com/BoschGroup"
    ],
    "keywords": [],
    "excludeKeywords": [],
    "locations": [],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("iwc/career-site-jobs-monitor").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": [
        "greenhouse:stripe",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/notion",
        "https://jobs.smartrecruiters.com/BoschGroup",
    ],
    "keywords": [],
    "excludeKeywords": [],
    "locations": [],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("iwc/career-site-jobs-monitor").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": [
    "greenhouse:stripe",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/notion",
    "https://jobs.smartrecruiters.com/BoschGroup"
  ],
  "keywords": [],
  "excludeKeywords": [],
  "locations": [],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call iwc/career-site-jobs-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,iwc/career-site-jobs-monitor"
        }
    }
}

```

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/SJhJr18pC4TDPNqNI/builds/hXYdOxCuUYgwFiK1w/openapi.json
