# Job Search API: 12,000+ Career Sites incl. Workday (`appley_rhodolite/ats-jobs-api`) Actor

Pull open jobs from any company's public Greenhouse, Lever, Ashby or SmartRecruiters job board in one unified JSON schema. Filter by keyword, location, remote and date. $2 per 1,000 jobs.

- **URL**: https://apify.com/appley_rhodolite/ats-jobs-api.md
- **Developed by:** [TW Data Tools](https://apify.com/appley_rhodolite) (community)
- **Categories:** Jobs, Lead generation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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

## Job Search API: keywords across 12,000+ company career sites (Workday, Greenhouse, Lever, Ashby, SmartRecruiters)

**Search open jobs by keyword across 12,166 company career sites at once.** Type a title keyword, a location or "remote", and get matching jobs from 12,166 employers on **Workday, Greenhouse, Lever, Ashby and SmartRecruiters** as one clean, identical JSON schema: title, company, department, location, remote flag, posting date, apply link and (when the employer publishes it) salary.

No API keys, no proxies, no browser, no login. The search runs on a job index that is rebuilt from the employers' own public job-board APIs, (Greenhouse, Lever, Ashby and SmartRecruiters **every night**, Workday refreshed **twice a month** (1st and 15th)), so a search finishes in seconds and you only pay for the jobs you get back.

There is also a classic **company-list mode**: give it companies, get their live job boards (the original behaviour, unchanged).

### Two modes

| Mode | Use it when | Source |
|---|---|---|
| `search` | You want jobs by keyword / location / date **across all companies** | Index of 12,166 boards, 1,333,553 open jobs (Workday refreshed twice a month, the other ATS nightly) |
| `companies` (default) | You know which companies you want, and want their live listings right now | The employers' public job-board APIs, fetched live |

#### Boards in the search index

| ATS | Company boards | Open jobs in index | Refresh |
|---|---|---|---|
| Workday | 3,568 | 896,898 | twice a month |
| Greenhouse | 4,377 | 177,118 | nightly |
| Lever | 230 | 26,439 | nightly |
| Ashby | 3,012 | 59,111 | nightly |
| SmartRecruiters | 979 | 173,987 | nightly |
| **Total** | **12,166** | **1,333,553** | |

Numbers are measured from the live index (Greenhouse, Lever, Ashby and SmartRecruiters built 2026-11-08, Workday built 2026-11-09); every board in it had at least one open job when it was checked. The index timestamp is on every result (`indexUpdatedAt`).

### Search mode

```json
{
  "mode": "search",
  "keywords": ["python", "data engineer"],
  "excludeKeywords": ["intern", "manager"],
  "locations": ["remote", "london"],
  "postedWithinDays": 14,
  "maxItems": 200
}
```

| Field | Description |
|---|---|
| `keywords` | Words to find in the job **title** (case-insensitive). Empty = every job. |
| `keywordMatch` | `any` (default): at least one keyword. `all`: every keyword must appear. |
| `excludeKeywords` | Drop jobs whose title contains any of these. |
| `locations` | Substring match on the job location (`"london"`, `"germany"`, `"remote"`). Multi-location jobs match on any of their locations. |
| `remoteOnly` | Only jobs the employer flags as remote. |
| `atsFilter` | Limit to `greenhouse`, `lever`, `ashby`, `smartrecruiters`. Reading fewer ATS also makes the run faster. |
| `companyFilter` | Only these companies (exact slug or company name). |
| `postedWithinDays` | Only jobs first published in the last N days. |
| `newSinceHours` | **New jobs**: only jobs our index first saw in the last N hours. |
| `includeDescription` | Adds `descriptionText` (plain text). Fetched live, only for matched jobs, newest first, up to `maxDescriptionItems` (default 200). |
| `maxItems` | Cap on returned jobs (you pay per job). Newest posted jobs come first. |

#### New jobs in the last 24 hours

```json
{ "mode": "search", "newSinceHours": 24, "keywords": ["software engineer"], "remoteOnly": true, "maxItems": 500 }
```

`firstSeenAt` is the time our index first saw the job. Schedule this input daily and you get each day's new openings from Greenhouse, Lever, Ashby and SmartRecruiters. Workday is refreshed twice a month, so use `atsFilter` without `workday` for this 24-hour view; Workday jobs appear in batches after each twice-monthly refresh.

#### Jobs at specific companies, with descriptions

```json
{ "mode": "search", "companyFilter": ["stripe", "airbnb"], "keywords": ["engineer"], "includeDescription": true, "maxItems": 50 }
```

#### Output (search mode)

```json
{
  "jobId": "6757302",
  "ats": "greenhouse",
  "company": "Airbnb",
  "companySlug": "airbnb",
  "title": "Senior Staff Machine Learning Engineer, Data & Eval",
  "department": null,
  "location": "United States",
  "locations": ["United States"],
  "isRemote": null,
  "employmentType": null,
  "postedAt": "2026-10-19T06:00:40Z",
  "updatedAt": "2026-11-05T18:22:10Z",
  "applyUrl": "https://job-boards.greenhouse.io/airbnb/jobs/6757302",
  "salary": null,
  "sourceUrl": "https://job-boards.greenhouse.io/airbnb/jobs/6757302",
  "firstSeenAt": "2026-10-19T06:00:40Z",
  "indexUpdatedAt": "2026-11-08T03:00:12Z",
  "indexStale": false,
  "fetchedAt": "2026-11-08T10:00:00Z"
}
```

`indexUpdatedAt` is when the index was last rebuilt. If it is more than 48 hours old, every item has `indexStale: true` and the run log shows a warning (results are still returned).

### Company-list mode (default)

The original mode: pass career-page URLs, `ats:slug` pairs or bare slugs and get those companies' live postings.

```json
{
  "companies": ["greenhouse:airbnb", "https://jobs.lever.co/spotify", "notion"],
  "keywords": ["engineer", "data"],
  "locations": ["remote", "london"],
  "postedWithinDays": 30,
  "maxItems": 200
}
```

| Field | Description |
|---|---|
| `companies` | Career-page URLs, `greenhouse:airbnb` style pairs, or bare slugs. A bare slug is probed on all four ATSs and the board with the most postings is used (check the log, and use `ats:slug` if a slug exists on several). Workday is available in `search` mode only; for one Workday company live, use the separate workday-jobs-live Actor. |
| `keywords`, `locations`, `remoteOnly`, `postedWithinDays`, `includeDescription`, `maxItems` | As in search mode (`keywords` match any word). |
| `redactContacts` | Default `true`. Replaces emails and phone numbers in descriptions. |
| `maxItemsPerCompany` | Cap per company (0 = no limit). |

| ATS | Example career page | Slug |
|---|---|---|
| Greenhouse | `boards.greenhouse.io/airbnb`, `job-boards.greenhouse.io/airbnb` | `airbnb` |
| Lever | `jobs.lever.co/spotify` | `spotify` |
| Ashby | `jobs.ashbyhq.com/notion` | `notion` |
| SmartRecruiters | `careers.smartrecruiters.com/BoschGroup` | `BoschGroup` |

Company-list output adds `team`, `workplaceType` and `fetchedAt`; salary and plain-text descriptions as available.

`salary` is `{min, max, currency, interval, summary}` when the ATS publishes pay (Ashby does for many postings; Lever and Greenhouse only when the employer fills it in the public feed).

### Use cases

- Job boards and newsletters that need fresh listings across many employers.
- Recruiters and sourcers: "who is hiring a data engineer in Berlin this week?"
- Job alerts: schedule a daily run with `newSinceHours: 24`.
- Sales and market intelligence: which companies are hiring for which roles.
- Labor-market research and salary-transparency studies.

### Pricing

**$2 per 1,000 jobs** (pay per result, $0.002 per job) plus a $0.005 start fee per run. That is a fraction of the $12 per 1,000 that the largest career-site job APIs charge. Use `maxItems` and Apify's max-cost-per-run setting to cap spend; the Actor stops when your limit is reached.

### Data source and disclaimer

This Actor is an independent tool and is **not affiliated with, endorsed by, or sponsored by** Greenhouse, Lever, Ashby, SmartRecruiters, Workday or any listed employer. Data comes from the public job-board APIs that those vendors offer for publishing postings; the postings belong to the employers. The company directory was compiled from publicly visible career-page URLs. You are responsible for using the data lawfully and in line with each employer's and vendor's terms. Personal data of recruiters or hiring managers is not collected.

### Limits

- Search mode covers the boards in the index (see the table above), not every employer on the internet. Companies on other ATS (Teamtailor, iCIMS and others) are not included. For Workday, the index holds the career sites we found on myworkdayjobs.com; very large Workday sites are capped (the list API returns at most 2,000 jobs per query), and the `department`, `employmentType` and salary fields are empty because Workday's list API does not return them.
- Greenhouse, Lever, Ashby and SmartRecruiters are rebuilt nightly: very new postings can take up to a day to appear, closed postings up to a day to disappear. Workday is rebuilt twice a month (1st and 15th): allow up to 16 days. Use company-list mode for live data.
- Search matches the job **title** (not the description). `department` is empty for Greenhouse in the index.
- Greenhouse's public feed has no team field and rarely carries salary; `isRemote` for Greenhouse is inferred from location names containing "remote".
- Lever and SmartRecruiters publish no "updated" timestamp (`updatedAt` is `null`).
- Descriptions in search mode are fetched live and capped by `maxDescriptionItems`.

### FAQ

**How is search different from the company-list mode?** Search needs no company names: it scans a prebuilt index of all 12,166 boards and returns matching jobs. Company-list mode fetches only the companies you list, live.

**How fresh is the data?** The index is rebuilt every night. Each result carries `indexUpdatedAt`; if the index is ever older than 48 hours, results are flagged `indexStale: true`.

**How do I get only new jobs?** Use `newSinceHours: 24` (jobs first seen by the index in the last 24 hours) or `postedWithinDays: 1` (by the employer's own posting date).

**How do I find a company's slug?** For `companyFilter` or company-list mode, open its careers page; the last part of the URL is the slug (`jobs.lever.co/spotify` -> `spotify`). Company-list mode also accepts the full URL.

**Why is a company missing from search?** It may use another ATS or have no public postings, or it is not in the directory yet. Use company-list mode with its career-page URL.

**Can I get descriptions?** Yes: `includeDescription: true`. They are fetched live for matched jobs (up to `maxDescriptionItems`); emails and phone numbers are removed unless you set `redactContacts` to `false`.

**What will a search cost?** $0.005 to start plus $0.002 per returned job, so 500 jobs cost about $1.005. Nothing is charged for jobs you do not receive.

# Actor input Schema

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

'search' = keyword search across thousands of companies' career sites using a prebuilt index (Greenhouse, Lever, Ashby and SmartRecruiters refreshed nightly, Workday twice a month). 'companies' = fetch the listed companies' boards live (default).

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

Mode 'companies' only. One entry per company. Accepts a career-page URL (boards.greenhouse.io/airbnb, jobs.lever.co/spotify, jobs.ashbyhq.com/notion, careers.smartrecruiters.com/Bosch), an 'ats:slug' pair such as greenhouse:airbnb, or a bare slug (auto-detects the ATS by probing all four; use 'ats:slug' if a slug exists on several).

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

Keep only jobs whose title contains these words (case-insensitive). In search mode see 'Keyword match'. Empty = all jobs.

## `keywordMatch` (type: `string`):

Search mode: 'any' keeps titles containing at least one keyword, 'all' requires every keyword.

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

Search mode: drop jobs whose title contains any of these words.

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

Keep only jobs whose location contains at least one of these texts (substring match, case-insensitive), e.g. 'London' or 'Germany'.

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

Keep only jobs flagged as remote by the employer's ATS.

## `atsFilter` (type: `array`):

Search mode: limit to these ATS: greenhouse, lever, ashby, smartrecruiters, workday. Empty = all five. Workday is available in search mode only (for one Workday company live, use the workday-jobs-live Actor).

## `companyFilter` (type: `array`):

Search mode: only these companies (exact company slug or company name, case-insensitive), e.g. 'stripe'.

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

Keep only jobs first published in the last N days. Leave empty or 0 for no limit.

## `newSinceHours` (type: `integer`):

Search mode: only jobs our index first saw in the last N hours (24 = new jobs in the last day). The index is rebuilt nightly, so values under 24 are not more precise than the last refresh. Workday is refreshed twice a month: for Workday jobs 'new' means first seen by the latest refresh, so values under 384 (16 days) are not meaningful.

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

Adds descriptionText (HTML converted to plain text). Makes runs slower, and for SmartRecruiters needs one extra request per job. In search mode descriptions are fetched live, only for matched jobs, up to 'Max descriptions'.

## `maxDescriptionItems` (type: `integer`):

Search mode with includeDescription: fetch descriptions live for at most this many matched jobs (newest first).

## `redactContacts` (type: `boolean`):

Replaces email addresses and phone numbers found in descriptions with a placeholder. Only affects descriptionText.

## `maxItemsPerCompany` (type: `integer`):

Mode 'companies' only. Cap on jobs returned per company, so one big employer cannot use up the whole budget. 0 = no limit.

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

Maximum number of jobs to return in total (you pay per job). 0 = no limit (search mode is capped at 100,000).

## `indexStoreId` (type: `string`):

Search mode: override the public key-value store holding the index. Leave empty.

## Actor input object example

```json
{
  "mode": "companies",
  "companies": [
    "greenhouse:airbnb",
    "lever:spotify",
    "ashby:notion"
  ],
  "keywordMatch": "any",
  "remoteOnly": false,
  "includeDescription": false,
  "maxDescriptionItems": 200,
  "redactContacts": true,
  "maxItemsPerCompany": 20,
  "maxItems": 50
}
```

# Actor output Schema

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

All job postings in the default dataset.

## `overview` (type: `string`):

Overview view of the default dataset.

# 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",
        "lever:spotify",
        "ashby:notion"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("appley_rhodolite/ats-jobs-api").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",
        "lever:spotify",
        "ashby:notion",
    ] }

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

```

## MCP server setup

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

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/xo52bsMuhtnG7b6Dx/builds/4NZS3QJ4VhUtvAONH/openapi.json
