# Careers Jobs Scraper: Greenhouse, Lever, Ashby, Workday (`scraperworks/jobs-watch`) Actor

Every open job from a list of companies' careers sites in one clean format, or only the jobs that are new or closed since your last run. Paste careers URLs, domains or board names.

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

## Pricing

from $1.50 / 1,000 jobs

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

## Company Careers Jobs Scraper: Greenhouse, Lever, Ashby, Workday

Get every open job from a list of companies' careers sites, in one clean format. Or switch on **watch mode** and get only what changed since your last run: new jobs, and jobs that closed.

Paste careers links, company websites or board names. jobs-watch works out which hiring system each company uses (Greenhouse, Lever, Ashby, Workday, Recruitee or Personio) and reads its public job feed directly.

### Who it's for

- **Recruiters and agencies** watching target companies for new openings.
- **Job boards and newsletters** that list jobs from a set of companies.
- **Sales teams** using hiring as a buying signal ("they're hiring five data engineers").
- **Investors and analysts** tracking which teams are growing.
- **AI agents** that need "open roles at company X" in the middle of a task.

### What makes it different

- **Six systems in one Actor.** Greenhouse, Lever, Ashby and Workday, plus Recruitee and Personio, with identical fields whatever the source.
- **Paste anything.** `https://boards.greenhouse.io/airbnb`, `jobs.lever.co/palantir`, `bunq.recruitee.com`, `ramp.com` or just `discord` all work.
- **Watch mode built in.** Schedule it daily and get only the new and closed jobs, not the same list again.
- **Salary when the company lists it** (Ashby, Lever, Recruitee and Personio), never guessed.
- **Big Workday boards in full.** Workday's own pages stop at 2,000 jobs; jobs-watch fetches past that.
- **No personal data.** Email addresses and phone numbers are removed from descriptions.

### Input

| Field | What it does |
|---|---|
| **Companies** | One per line: a careers or job-board link, a company website, or a board name. Up to 1,000 per run |
| **What to return** | *All open jobs*, or *Only changes since my last run* (watch mode) |
| **Job title contains** | Keep titles containing any of these words (not case-sensitive) |
| **Job title must not contain** | Drop titles containing any of these, e.g. `intern` |
| **Locations** | Keep jobs in any of these places, e.g. `london`, `germany`, `remote` |
| **Remote jobs only** | Keep only remote jobs |
| **Include job descriptions** | Full description as text and HTML (on by default) |
| **Max jobs per company** | 0 means no limit |

Example:

```json
{
  "companies": ["https://boards.greenhouse.io/airbnb", "jobs.lever.co/palantir", "ramp.com",
                "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite"],
  "mode": "watch",
  "titleKeywords": ["engineer"],
  "excludeTitleKeywords": ["intern"],
  "locations": ["remote", "new york"]
}
```

### Output

One row per job. A real example (description shortened):

```json
{
  "company": "ramp",
  "ats": "ashby",
  "jobId": "34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "title": "Security Engineer, Cloud",
  "department": "Engineering",
  "team": "Backend",
  "locations": ["New York, NY (HQ)", "Remote (Canada)", "Remote (US)", "Miami, FL"],
  "country": null,
  "workplaceType": "hybrid",
  "employmentType": "full-time",
  "postedAt": "2026-04-07T17:12:35Z",
  "updatedAt": null,
  "salaryMin": 211400,
  "salaryMax": 290600,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "url": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245",
  "applyUrl": "https://jobs.ashbyhq.com/ramp/34413f8d-26bf-4bbc-8ade-eb309a0e2245/application",
  "descriptionText": "About Ramp\nRamp is building the smart infrastructure for finance teams…",
  "descriptionHtml": "<h1><strong>About Ramp</strong></h1>…",
  "status": "new",
  "firstSeenAt": "2026-10-17T09:00:00Z",
  "scrapedAt": "2026-10-17T09:00:00Z"
}
```

- **status** is `open` in "All open jobs" mode. In watch mode it's `new` (not there last run) or `closed` (there last run, gone now). Closed rows carry the title, link and locations from when the job was open.
- **country** is filled when the job's locations point to one country, and left empty when they span several.
- Fields a source doesn't provide are `null`, never guessed.
- A company that can't be found or fails gets one **error row** (`"status": "error"`) saying why. The rest of the run carries on, and that company's jobs are not marked closed.

### Watch mode

jobs-watch remembers, in a storage in **your own** Apify account, which jobs it has shown you for each company list. The next run with the same list returns only the differences. Changing filters doesn't make jobs look closed. Use a different company list and it starts fresh. The first watch run returns everything as new.

Tip: create an Apify **schedule** that runs it daily or weekly, and connect a webhook, Slack or email integration to get the new jobs delivered.

### Pricing

Pay per result:

| Event | Price |
|---|---|
| Job returned | $1.50 per 1,000 jobs |
| Closed job (watch mode) | $0.50 per 1,000 |

Error rows are free. Set a maximum cost per run in Apify and jobs-watch stops cleanly when it's reached; jobs it didn't get to are still reported as new next time.

### FAQ

**Is this allowed?** jobs-watch reads only public job postings, the same feeds the companies' own careers pages use, with no login. Greenhouse and Lever document these feeds as public. It sends requests at a gentle pace and follows each site's robots.txt.

**A company returns an error row "couldn't find a job board".** Its careers page may build the job list in the browser, or it may use a system jobs-watch doesn't read yet. Paste a direct job-board link instead (open one job from their careers page and copy the address).

**Does it support Recruitee and Personio?** Yes: paste the company's `name.recruitee.com` or `name.jobs.personio.de` link. Many Personio sites ask automated tools not to read them (their robots.txt); jobs-watch respects that and returns an error row saying so, free.

**Does it support SmartRecruiters?** No. SmartRecruiters' API asks automated tools other than LinkedIn's not to read it, so those links get an error row.

**Why is a Workday run slower?** Workday is paged 20 jobs at a time, and descriptions need one extra request per job. Turn off descriptions for a much faster run.

**How fresh is the data?** Live: every run reads the companies' feeds at that moment.

# Actor input Schema

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

One company per line. Paste a careers-page or job-board link (boards.greenhouse.io/airbnb, jobs.lever.co/palantir, jobs.ashbyhq.com/ramp, nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite, bunq.recruitee.com, tado.jobs.personio.de), a company website (figma.com), or a board name (discord). jobs-watch works out which system the company uses. Up to 1,000 per run.

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

"All open jobs" returns every open job each run. "Only changes" (watch mode) returns jobs that are new since your last run with the same company list, plus jobs that have closed. The first watch run returns everything as new.

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

Keep jobs whose title contains any of these words. Not case-sensitive: "engineer" also matches "Engineering Manager". Leave empty for all titles.

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

Drop jobs whose title contains any of these words, e.g. "intern".

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

Keep jobs in any of these places, matched against every location the job lists, e.g. "london", "germany", "remote".

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

Keep only jobs marked remote, or listing a remote location.

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

Add the full description as plain text and HTML. Turn off for smaller, faster results.

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

Stop after this many jobs from one company. 0 means no limit.

## Actor input object example

```json
{
  "companies": [
    "https://boards.greenhouse.io/discord",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/linear"
  ],
  "mode": "all",
  "remoteOnly": false,
  "includeDescription": true,
  "maxJobsPerCompany": 0
}
```

# Actor output Schema

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

One row per job (open, new or closed), plus an error row per company that returned nothing.

# 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": [
        "https://boards.greenhouse.io/discord",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/linear"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("scraperworks/jobs-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 = { "companies": [
        "https://boards.greenhouse.io/discord",
        "https://jobs.lever.co/spotify",
        "https://jobs.ashbyhq.com/linear",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("scraperworks/jobs-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 '{
  "companies": [
    "https://boards.greenhouse.io/discord",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/linear"
  ]
}' |
apify call scraperworks/jobs-watch --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scraperworks/jobs-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/9UhmN5cdvct3430Th/builds/GtdYMmcj3CANFgWKM/openapi.json
