# Greenhouse, Lever & Ashby Jobs API with New-Job Alerts (`neorhapsody/ats-jobs-feed`) Actor

Job postings API for company career sites on Greenhouse, Lever, Ashby, Workable and SmartRecruiters. Give it a company name or domain. You get normalised location, country and salary, a country filter, and alerts for new and removed jobs between runs.

- **URL**: https://apify.com/neorhapsody/ats-jobs-feed.md
- **Developed by:** [fikri marhan](https://apify.com/neorhapsody) (community)
- **Categories:** Jobs, Lead generation, Automation
- **Stats:** 2 total users, 1 monthly users, 0.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

## Greenhouse, Lever & Ashby Jobs API with New-Job Alerts

**Get every open job straight from company career sites, plus alerts when jobs are added or removed.** Give it company names, domains or job-board links. It finds the company's applicant tracking system (ATS) and returns clean, normalised job data from the platform's **official public job-board API**. No login, cookies or proxies are needed.

Supported platforms: **Greenhouse, Lever, Ashby, Workable, SmartRecruiters.**

### Use cases

- **Hiring signals and lead generation**: see which companies are hiring, for which roles and in which countries.
- **Job boards and aggregators**: a fresh, structured feed from hundreds of company career pages.
- **Recruiting and talent intelligence**: track competitors' openings, departments and pay ranges.
- **Job alerts**: schedule it daily in *"Only new jobs"* mode and send the new postings to Slack, email or Google Sheets.
- **Job market research by country**: for example, all tech roles in Malaysia, Singapore or Indonesia from top employers.

### One job postings API for five job board platforms

Greenhouse, Lever, Ashby, Workable and SmartRecruiters each have their own public job board API with different fields, pagination and URL schemes. This Actor calls each **official public job board API** (for example the Greenhouse Job Board API and the Lever Postings API) and returns one consistent schema. You don't need an API key, and it doesn't scrape HTML.

### Features

- **Auto-detection**: give it `notion.so` and it checks the company's careers page for its ATS. Or give it a slug like `stripe` and it probes each platform in turn.
- **Normalised locations**: city, ISO country code, country name, all countries for multi-location jobs, regions (EMEA, APAC, LATAM…) and a remote flag.
- **Normalised salary**: min, max, currency and interval (year/month/hour). It uses the structured pay data from Ashby and Lever when present, and otherwise conservatively parses pay ranges from the description.
- **Country filter**: `MY`, `SG`, `United States`… with optional "remote, no country".
- **Change monitoring**: each job is marked `new`, `existing` or `removed` since your last run (state is kept in a named key-value store). Modes: all jobs, only new jobs, or changes (new + removed).
- **Privacy-friendly**: no recruiter or hiring-manager names are collected, and emails and phone numbers are removed from descriptions.
- **Polite**: requests to each host are spaced out, with retries and backoff that respect `Retry-After`. When detecting from a domain it fetches at most three pages and respects `robots.txt`.

### Input examples

Track all Singapore and Malaysia jobs at a few companies:

```json
{ "companies": ["stripe", "openai", "smartrecruiters:BoschGroup"], "countries": ["SG", "MY"] }
```

Daily alert of new engineering jobs only:

```json
{ "companies": ["notion.so", "lever:spotify", "huggingface"], "titleKeywords": ["engineer"], "outputMode": "newOnly", "baselineFirstRun": true, "monitorId": "eng-watch" }
```

### Output example

```json
{
  "platform": "ashby", "companySlug": "openai", "companyName": "Openai", "jobId": "b71906c2-...",
  "title": "Quality Engineer, Rack Infrastructure & Site Operations", "department": "Scaling",
  "workplaceType": "hybrid", "isRemote": false, "location": "Singapore", "city": "Singapore",
  "countryCode": "SG", "countryName": "Singapore", "countryCodes": ["SG"],
  "salaryMin": 171000, "salaryMax": 345000, "salaryCurrency": "SGD", "salaryInterval": "YEAR", "salarySource": "structured",
  "postedAt": "2026-09-09T18:22:22.536Z", "url": "https://jobs.ashbyhq.com/openai/...", "changeType": "new", "firstSeenAt": "2026-10-22T14:00:00.000Z"
}
```

You can download results as JSON, CSV or Excel, or read them through the API. The `RUN_SUMMARY` record in the run's key-value store shows per-company counts, error counts by code, warnings and the events charged. Inputs that couldn't be read appear as error records in the dataset (see Troubleshooting).

### Need it as a REST API instead?

The same data is available as the **ATS Jobs API** on RapidAPI: https://rapidapi.com/fikrimarhan/api/ats-jobs-api4 (Greenhouse, Lever, Ashby, SmartRecruiters). Use it when you want live job lists from your own code with one HTTP request, instead of scheduled runs and datasets.

### Pricing

Pay per event, with no extra platform-usage charges:

- **$1.50 per 1,000 jobs** (`job`, $0.0015 per job written to the dataset)
- **$0.005 per company looked up** (`company-lookup`, charged once per company whose job board was actually read; inputs where no board is found are free)

Jobs removed by your filters, or by "Only new jobs" mode, are free. A daily alert on 50 companies that yields 20 new jobs costs about $0.28 per run. If you set a **maximum cost per run**, the actor stops cleanly at the limit and doesn't advance the monitor state for a board it couldn't deliver in full, so nothing is lost. Error records are never charged.

### How do I find a company's Greenhouse, Lever or Ashby slug?

Open the company's careers page and click a job. The slug is in the job board URL:

- `boards.greenhouse.io/`**`airbnb`** or `job-boards.greenhouse.io/`**`airbnb`** → `greenhouse:airbnb`
- `jobs.lever.co/`**`spotify`** → `lever:spotify`
- `jobs.ashbyhq.com/`**`ramp`** → `ashby:ramp`
- `apply.workable.com/`**`huggingface`** → `workable:huggingface`
- `jobs.smartrecruiters.com/`**`BoschGroup`** → `smartrecruiters:BoschGroup`

You can also paste the full board URL or just the company's domain, and the Actor will find the board itself.

### Tips

- Explicit `platform:slug` inputs are the fastest and most reliable. Domain detection works when the careers page links to the ATS.
- Use one **Monitor ID** per schedule, so each watch list remembers its own jobs.
- SmartRecruiters boards can be huge. Use *Max jobs per company* to cap them.

### Is this legal?

The actor reads the same public, unauthenticated job-board APIs that companies' own careers pages use to show their openings. It collects job postings, not personal data. You're responsible for how you use and republish the data; please link back to the original posting.

### Troubleshooting

One bad company never stops the run. Each problem becomes an **error record** in the dataset with `isError: true`, an `errorCode`, a plain-English `errorMessage` and the offending `input`. The *Errors* view shows these columns. Error records are free. Counts per code are in `RUN_SUMMARY`.

| Code | Meaning | What to do |
|---|---|---|
| `INVALID_INPUT` | The input isn't a slug, `platform:slug`, board URL or domain, or another input field is invalid. | Fix the value named in the message. |
| `BOARD_NOT_FOUND` | No public Greenhouse/Lever/Ashby/Workable/SmartRecruiters board was found (or only empty boards with that name). | Use the exact board URL or `platform:slug`. The company may use another ATS (e.g. Workday, which isn't supported). |
| `BOARD_NOT_PUBLIC` | The board API refused access (HTTP 401/403). | The board is private or disabled. |
| `RATE_LIMITED` | The platform kept returning HTTP 429 after retries. | Try later, or raise *Delay between requests*. |
| `UPSTREAM_ERROR` | The platform returned a server error after retries. | Usually temporary. Re-run later. |
| `TIMEOUT` / `NETWORK_ERROR` | The platform didn't respond. | Re-run later. |
| `UPSTREAM_SCHEMA_CHANGED` | The platform's API no longer returns the expected fields. Also logged as a warning when only some jobs are affected; those jobs are skipped. | Please report it, so the parser can be updated. |
| `BOARD_EMPTY` (warning) | The board exists but has no open jobs. | None. |
| `MAX_CHARGE_REACHED` (warning) | Your maximum cost per run was reached; the remaining companies were skipped. | Raise the limit. |
| `NO_BOARDS_READ` | None of the inputs could be read, so the run is marked failed. | See the error records for each input. |
| `INTERNAL_ERROR` / `FETCH_FAILED` | An unexpected bug. | Please report it (see below). |

**Found a bug or a wrong result?** Open an **Issue** on this Actor's page in Apify Store (the *Issues* tab) and describe what you expected. Please **paste the run URL into the issue**: Apify then shares that run (log, input, dataset and RUN_SUMMARY) with the developer automatically, and only that run. Alternatively, enable **Settings → Login & Privacy → Share run data with developers** for this Actor. The Actor sends no telemetry anywhere; everything stays in your own run storage.

# Actor input Schema

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

One per line. Accepts a company slug ("stripe"), a company domain ("notion.so") for auto-detection, a job board URL ("https://jobs.lever.co/spotify"), or an explicit "platform:slug" ("greenhouse:stripe", "smartrecruiters:BoschGroup").

## `platforms` (type: `array`):

Which ATS platforms to probe when a slug or domain is given.

## `countries` (type: `array`):

Only jobs located in these countries. ISO codes or names, e.g. MY, SG, United States. Leave empty for all.

## `includeRemoteWithoutCountry` (type: `boolean`):

When filtering by country, also keep remote jobs whose location does not name any country (e.g. "Remote", "Anywhere").

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

Keep only jobs marked as remote.

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

Keep jobs whose title contains any of these words (case-insensitive), e.g. engineer, data, sales.

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

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

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

All jobs (each marked new or existing), only jobs that are new since the last run, or changes (new + removed).

## `monitorId` (type: `string`):

Name for this watch list. Runs with the same Monitor ID share memory of previously seen jobs; use different IDs for separate schedules.

## `baselineFirstRun` (type: `boolean`):

In 'new only' / 'changes' mode, the first run for a board just records the current jobs and outputs nothing, so you only get alerts for genuinely new postings afterwards.

## `stateStoreName` (type: `string`):

Named key-value store used to remember jobs between runs.

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

Add the full plain-text description. Emails and phone numbers of individuals are removed. Makes results larger (and SmartRecruiters slower: one extra request per job, max 200 per company).

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

Limit output per company (0 = no limit). For SmartRecruiters this also limits how many postings are fetched (default cap 2000).

## `maxConcurrency` (type: `integer`):

How many companies to process at the same time (1-5). Requests to the same platform are always spaced out politely.

## `requestDelayMs` (type: `integer`):

Polite rate limit per host. Minimum 200 ms.

## Actor input object example

```json
{
  "companies": [
    "stripe",
    "lever:spotify"
  ],
  "platforms": [
    "greenhouse",
    "lever",
    "ashby",
    "workable",
    "smartrecruiters"
  ],
  "includeRemoteWithoutCountry": false,
  "remoteOnly": false,
  "outputMode": "all",
  "monitorId": "default",
  "baselineFirstRun": false,
  "stateStoreName": "ats-jobs-feed-state",
  "includeDescription": false,
  "maxJobsPerCompany": 20,
  "maxConcurrency": 3,
  "requestDelayMs": 500
}
```

# Actor output Schema

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

All job postings (one row per job), plus error records for companies that could not be read.

## `salaries` (type: `string`):

Salary-focused view of the job postings.

## `errors` (type: `string`):

Error columns: code, message and input.

## `runSummary` (type: `string`):

RUN_SUMMARY JSON: input/output counts, errors and warnings by code, charged events.

# 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": [
        "stripe",
        "lever:spotify"
    ],
    "maxJobsPerCompany": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("neorhapsody/ats-jobs-feed").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": [
        "stripe",
        "lever:spotify",
    ],
    "maxJobsPerCompany": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("neorhapsody/ats-jobs-feed").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": [
    "stripe",
    "lever:spotify"
  ],
  "maxJobsPerCompany": 20
}' |
apify call neorhapsody/ats-jobs-feed --silent --output-dataset

```

## MCP server setup

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

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/Jzd87ZCAlT4YvnnGc/builds/myGUh7jQBagIz5dN0/openapi.json
