# ATS Job Scraper: Only New Jobs (Greenhouse, Lever, Ashby) (`batchpane/ats-jobs-only-new`) Actor

Only new or changed jobs since your last run, at $1.50 per 1,000 jobs plus $0.001 per board. Track the public job boards of up to 1,000 companies on Greenhouse, Lever, Ashby, Workable, Recruitee and Breezy HR. Filters for title, location, remote and posting date run before you pay.

- **URL**: https://apify.com/batchpane/ats-jobs-only-new.md
- **Developed by:** [Batch Pane](https://apify.com/batchpane) (community)
- **Categories:** Jobs, Lead generation
- **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

## ATS Job Scraper: Only New Jobs (Greenhouse, Lever, Ashby, Workable, Recruitee, Breezy)

**Track the job boards of hundreds of companies and get only the jobs you have not seen yet.**

Give it a list of companies (job board links or short names). Every run reads their public job boards and outputs the
jobs that are **new or changed since your last run**. Schedule it daily and you have a clean feed of fresh openings.

- **Only new since the last run, built in.** The actor remembers which jobs you already received (in your own Apify
  account), so a daily run gives you just the new ones. In our test, 10 company boards with 2,452 open jobs: the first
  run delivered all 2,452 in 46 seconds; the next run checked the same 10 boards in 4 seconds and delivered 0 jobs,
  because nothing had changed.
- **Six ATS in one actor:** Greenhouse, Lever (US and EU), Ashby, Workable, Recruitee and Breezy HR. Mix them in one
  list. Paste the company's own careers page and the actor finds the job board link on it.
- **Filters that run before you pay:** title contains / does not contain, location, remote only, and **posted within
  the last N days**. Jobs that do not match are not output and not charged.
- **Changed jobs and closed jobs:** see when a job's title, location, team, salary or type changes, and (optional)
  get a row for every job that disappeared since the last run.
- **Clean, flat rows:** title, department, team, locations, remote, workplace type, employment type, salary (when the
  ATS publishes it), publish date, job link and apply link. Optional plain-text description.
- **No personal data:** no recruiter names, no e-mail addresses, no phone numbers. E-mail addresses and phone numbers
  are removed from descriptions.

*Measured on the Apify platform on 20 October 2026 (UTC), 512 MB memory: Airbnb, Stripe, Figma, Discord (Greenhouse),
Palantir, Spotify (Lever), Notion, OpenAI (Ashby), Hugging Face (Workable), bunq (Recruitee).*

### How "only new" works

1. The first run reads all boards and outputs every open job as `"changeType": "new"` (or, with
   `firstRunOutputsAll: false`, outputs nothing and just remembers today's jobs).
2. The actor stores the job ids it has seen (plus a short fingerprint of title, locations, team, salary and type) in a
   key-value store named `batchpane-ats-jobs-state` in **your** Apify account. No job content is stored there.
3. Every next run with the same company list outputs only jobs that are new (and, in the default mode, changed).
4. Want separate feeds? Give each task its own **Memory name**. Want to start over? Tick **Forget previous runs**.

Modes: `newAndChanged` (default), `new`, or `all` (every open job, every run, no memory).

### Use with AI agents (MCP)

Works as a tool through the Apify MCP server: `https://mcp.apify.com?tools=batchpane/ats-jobs-only-new`

**Input the agent should send:**
`{"companies": ["greenhouse:airbnb", "https://jobs.lever.co/palantir"], "outputMode": "new", "titleIncludes": ["engineer"], "postedWithinDays": 7}`

**Results:** rows with `"type": "job"` are jobs (charged); one row per company with `"type": "board"` tells whether
the board was read and how many jobs were new (free).

**Example prompt:**

> Use batchpane/ats-jobs-only-new to check greenhouse:airbnb, jobs.lever.co/palantir and jobs.ashbyhq.com/notion for
> remote engineering jobs posted in the last 7 days. Give me a table with company, title, location and link.

### Input

| Field | Default | Notes |
|---|---|---|
| Companies | – | Job board URLs (`https://job-boards.greenhouse.io/airbnb`, `https://jobs.lever.co/palantir`, `https://jobs.ashbyhq.com/notion`, `https://apply.workable.com/huggingface`, `https://bunq.recruitee.com`, `https://acme.breezy.hr`), short forms (`greenhouse:airbnb`, `lever:palantir`, `ashby:notion`, `workable:huggingface`, `recruitee:bunq`, `breezy:acme`), or a careers page. Up to 1,000 per run. |
| Which jobs to output | new and changed | `newAndChanged`, `new` or `all` |
| First run outputs all jobs | on | off = first run only remembers |
| Title contains / does not contain | – | case-insensitive words |
| Location contains | – | e.g. `Amsterdam`, `Germany`, `Remote` |
| Remote jobs only | off | |
| Posted within the last N days | – | 1-365 |
| Include job description | off | plain text, e-mails and phone numbers removed |
| Also list removed jobs | off | free rows with `changeType: "removed"` |
| Memory name | from the company list | share or separate memories between tasks |

Example input (tested on the platform):

```json
{
  "companies": ["greenhouse:airbnb", "https://jobs.lever.co/palantir", "https://jobs.ashbyhq.com/notion"],
  "outputMode": "newAndChanged",
  "titleExcludes": ["intern"]
}
```

### Output

Job row:

```json
{
  "type": "job",
  "changeType": "new",
  "ats": "ashby",
  "board": "notion",
  "boardUrl": "https://jobs.ashbyhq.com/notion",
  "companyName": null,
  "jobId": "1fc309c8-da20-4ff2-84c7-8b863ece2b0a",
  "title": "Software Engineer, AI Connections (Developer Platform)",
  "department": "Engineering",
  "team": "Engineering",
  "locations": ["San Francisco, California", "New York, New York"],
  "location": "San Francisco, California",
  "remote": false,
  "workplaceType": "Hybrid",
  "employmentType": "FullTime",
  "salary": null,
  "publishedAt": "2026-08-24T14:44:49.699+00:00",
  "updatedAt": null,
  "url": "https://jobs.ashbyhq.com/notion/1fc309c8-da20-4ff2-84c7-8b863ece2b0a",
  "applyUrl": "https://jobs.ashbyhq.com/notion/1fc309c8-da20-4ff2-84c7-8b863ece2b0a/application",
  "scrapedAt": "2026-10-20T10:54:02.811Z"
}
```

Board row (one per company, free): `type: "board"`, `status` (`ok`, `failed`, `blocked`, `skipped`), `jobsFound`,
`jobsMatchingFilters`, `newJobs`, `changedJobs`, `removedJobs`, `jobsOutput`, and an `error` code when it failed.

### Pricing

- **$1.50 per 1,000 jobs delivered** ($0.0015 per job row).
- **$0.001 per company board checked**, also when it has no new jobs. That is what a daily "anything new?" check costs:
  100 companies = $0.10 per day.
- Plus Apify's standard start fee per run ($0.00005 per GB of memory; $0.000025 at the default 512 MB).

| Example | Cost |
|---|---|
| First run, 100 companies with 3,000 open jobs in total | 3,000 x $0.0015 + 100 x $0.001 = $4.60 |
| Daily run after that, 100 companies, 40 new jobs | 40 x $0.0015 + 100 x $0.001 = $0.16 |
| 20 companies, only "engineer" jobs, 5 new | 5 x $0.0015 + 20 x $0.001 = $0.0275 |

Boards that fail, are blocked or skipped are not charged; removed-job rows and board rows are never charged. You can set
a maximum cost per run in Apify; the actor stops before it would go over it.

### Good to know

- Supported: Greenhouse, Lever, Ashby, Workable, Recruitee, Breezy HR. Not (yet): Workday, SmartRecruiters, Personio,
  Teamtailor, iCIMS, Taleo. SmartRecruiters' and Personio's job feeds ask automated clients not to fetch them in their
  robots.txt, and this actor respects that.
- Salary appears only where the company publishes it in the ATS (common on Ashby and Lever, rare on Greenhouse).
- Greenhouse EU boards (`job-boards.eu.greenhouse.io`) are not supported yet.
- "Changed" looks at title, locations, department/team, workplace type, employment type and salary, not at the
  description text. The same locations in another order, or a change in only upper/lower case or spacing, does not
  count as a change (so you are not charged twice for the same job).
- A careers page is loaded once to find the job board link; pages that load their job widget only with JavaScript may
  not show the link. Use the job board URL or the short form in that case.
- Requests are polite: at most one request per second per ATS host, robots.txt respected.
- Safety stop: the run stops after 20 boards in a row fail ("Stop after failures in a row"; 0 switches
  only this rule off). A second rule cannot be switched off: after 50 boards, the run stops if fewer than 25% succeeded.

### Responsible use

This actor reads job boards that companies publish through their ATS so that their jobs are found. It is not affiliated
with Greenhouse, Lever, Ashby, Workable, Recruitee or Breezy HR. Use the data in line with the job boards' terms and the
law that applies to you; do not use it to contact individual employees. Job descriptions are the employers' content;
if you turn on descriptions, they may mention people by name. Do not republish them wholesale or use them to profile
individuals.

### Support

Found a board that should work but does not? Open an issue in the **Issues** tab with the board URL and the run ID. We
aim to answer within 7 days. Answers may be drafted with help of an AI assistant and are checked by a person.

### Changelog

- 0.1: first version.

# Actor input Schema

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

One company per line. Use a job board URL (https://jobs.lever.co/palantir, https://job-boards.greenhouse.io/airbnb, https://jobs.ashbyhq.com/notion, https://apply.workable.com/huggingface, https://bunq.recruitee.com, https://acme.breezy.hr), a short form like greenhouse:airbnb or lever:palantir, or the company's own careers page (we look for a link to one of these job boards on it). Up to 1,000 per run.

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

"newAndChanged" (default): only jobs that are new or changed (title, location, team, salary, type) since the last run with the same company list. "new": only new jobs. "all": every open job, every run (no memory). The first run outputs all current jobs, unless you switch off "First run outputs all jobs".

## `firstRunOutputsAll` (type: `boolean`):

true (default): the first run outputs every current job (as "new"). false: the first run only remembers the current jobs and outputs none, so later runs give you just what was posted after today.

## `titleIncludes` (type: `array`):

Only jobs whose title contains at least one of these words (case-insensitive), e.g. engineer, designer. Empty = all titles.

## `titleExcludes` (type: `array`):

Skip jobs whose title contains any of these words, e.g. intern, senior.

## `locationIncludes` (type: `array`):

Only jobs with a location that contains one of these texts, e.g. Amsterdam, Germany, Remote. Empty = all locations.

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

Only jobs marked remote by the ATS (or with "Remote" in the location).

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

Only jobs with a publish date in the last N days (1-365). Jobs without a publish date are then left out. Empty = no date filter.

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

Adds the job description as plain text. E-mail addresses and phone numbers in it are removed. Makes the run slower for Greenhouse boards. Default: false.

## `includeRemoved` (type: `boolean`):

Adds a row (changeType "removed", not charged) for each job that was open in the last run and is gone now.

## `stateKey` (type: `string`):

Name under which "already seen" jobs are remembered, in the key-value store "batchpane-ats-jobs-state" of your account. Empty = derived from the company list. Use the same name in different tasks to share the memory.

## `resetState` (type: `boolean`):

true = start over: this run behaves like a first run for this memory name.

## `stopAfterFailures` (type: `integer`):

Safety stop: the run stops when this many boards in a row fail. Default 20; 0 switches only this in-a-row rule off. A second rule always stays on: after 50 boards, the run stops if fewer than 25% succeeded.

## Actor input object example

```json
{
  "companies": [
    "greenhouse:airbnb",
    "https://jobs.lever.co/palantir",
    "https://jobs.ashbyhq.com/notion"
  ],
  "outputMode": "newAndChanged",
  "firstRunOutputsAll": true,
  "remoteOnly": false,
  "includeDescription": false,
  "includeRemoved": false,
  "resetState": false,
  "stopAfterFailures": 20
}
```

# Actor output Schema

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

Job rows (type "job") and one row per company board (type "board").

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

Counts for this run.

# 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:airbnb",
        "https://jobs.lever.co/palantir",
        "https://jobs.ashbyhq.com/notion"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("batchpane/ats-jobs-only-new").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:airbnb",
        "https://jobs.lever.co/palantir",
        "https://jobs.ashbyhq.com/notion",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("batchpane/ats-jobs-only-new").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:airbnb",
    "https://jobs.lever.co/palantir",
    "https://jobs.ashbyhq.com/notion"
  ]
}' |
apify call batchpane/ats-jobs-only-new --silent --output-dataset

```

## MCP server setup

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

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/yvm8XsCp5Vrm2LEmH/builds/H0GIWjbNaGLTk9abL/openapi.json
