# ATS Jobs API — $2/1K | Greenhouse, Lever, Ashby, Workday (`runix/career-site-jobs-api`) Actor

Search live jobs from Greenhouse, Lever, Ashby and Workday job-board APIs—no browser. Keyword, location, remote and date filters, company lists, salary when posted, and only-new monitoring. Official ATS endpoints, one flat schema. $2 per 1,000 jobs.

- **URL**: https://apify.com/runix/career-site-jobs-api.md
- **Developed by:** [Runix Services](https://apify.com/runix) (community)
- **Categories:** Jobs, Lead generation, Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 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 Jobs API: Greenhouse, Lever, Ashby & Workday Jobs from 1,250+ Companies

**Search 50,000+ live jobs across 1,250+ companies at once, or pull every open role from the companies you name.** Type "data engineer", pick remote, and get the newest matching jobs from every company in the directory, with salary ranges, apply links and full descriptions, in one clean schema. Nothing is scraped or cached: each run reads the companies' **official public job-board APIs** right now.

- **Search mode** (leave Companies empty): keyword, location, remote, department and posted-date filters across 1,250+ hiring companies on Greenhouse, Lever and Ashby, from seed-stage startups to public companies. Newest jobs first.
- **Company mode**: give board URLs, careers pages or just names. Ten systems: Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee, Workday, Personio, Teamtailor, BambooHR.
- **Only new jobs**: schedule it daily and get (and pay for) only postings you haven't had before.
- **$2 per 1,000 jobs.** Jobs your filters drop are free.

### Search every company (the form's prefill)

The 100 newest data engineer jobs across all 1,250+ companies:

```json
{
  "keywords": ["data engineer"],
  "maxJobs": 100
}
```

A daily feed of new remote ML roles in the US, for a job board, newsletter or recruiting team:

```json
{
  "keywords": ["machine learning", "ML engineer", "AI engineer"],
  "remote": "remote",
  "locations": ["US"],
  "postedSince": "7 days",
  "onlyNewJobs": true,
  "monitorId": "remote-ml-us"
}
```

The run's `SUMMARY` record says how many companies were searched, how many jobs were open, how many matched and how many were returned.

### Where the jobs come from

| System | Public endpoint used |
|---|---|
| Greenhouse | `boards-api.greenhouse.io/v1/boards/{board}/jobs` (+ per-job pay transparency) |
| Lever | `api.lever.co/v0/postings/{company}` (and `api.eu.lever.co`) |
| Ashby | `api.ashbyhq.com/posting-api/job-board/{board}` |
| Workable | `apply.workable.com/api/v1/widget/accounts/{account}` |
| SmartRecruiters | `api.smartrecruiters.com/v1/companies/{company}/postings` |
| Recruitee | `{company}.recruitee.com/api/offers` |
| Workday | `{tenant}.wdN.myworkdayjobs.com/wday/cxs/{tenant}/{site}/jobs` (+ per-job detail) |
| Personio | `{company}.jobs.personio.de/xml` (and `.com`) |
| Teamtailor | `{careers-host}/jobs.json` (JSON Feed) |
| BambooHR | `{company}.bamboohr.com/careers/list` (+ per-job detail) |

### Company mode: what you can give it

Put one company per line in **Companies**. Any of these work:

- **A job-board URL:** `https://boards.greenhouse.io/stripe`, `https://jobs.lever.co/palantir`, `https://jobs.ashbyhq.com/ramp`, `https://apply.workable.com/huggingface`, `https://jobs.smartrecruiters.com/ServiceNow`, `https://bunq.recruitee.com`
- **The company's own careers page:** `https://www.notion.com/careers`. The Actor finds the board that the page links to or embeds.
- **System and id:** `greenhouse:stripe`, `lever:palantir`, `ashby:ramp`
- **Just a name:** `Hugging Face`. Every system is checked and the company's biggest board is used. Other matches are listed in the run's `SUMMARY` record.

### Company mode examples

Every open job at four companies, newest 25 each:

```json
{
  "companies": [
    "https://boards.greenhouse.io/discord",
    "https://jobs.lever.co/palantir",
    "https://jobs.ashbyhq.com/ramp",
    "workable:huggingface"
  ],
  "maxJobsPerCompany": 25
}
```

A daily feed of new remote engineering roles, run on a schedule:

```json
{
  "companies": ["Stripe", "Notion", "greenhouse:figma", "https://jobs.ashbyhq.com/linear"],
  "keywords": ["engineer", "developer"],
  "remote": "remote",
  "onlyNewJobs": true,
  "monitorId": "remote-eng"
}
```

### Pricing

**$2.00 per 1,000 jobs** returned (pay per event: one `job` event per row). Jobs dropped by your filters are free, and in **Only new jobs** mode you pay only for postings you haven't had before. Example: 50 companies with about 40 open roles each is 2,000 jobs, or $4.00 for the first run; a daily only-new run after that typically returns a few dozen jobs for a few cents.

### Filters

- **Keywords** in the title, optionally in the description too, plus **exclude keywords**.
- **Locations**: "London", "Germany", "US" (US/USA/United States all match).
- **Remote**: any, remote only, remote or hybrid, on-site only.
- **Departments**: "Engineering", "Sales".
- **Posted since**: a date (`2026-10-01`) or an age (`24 hours`, `7 days`).
- **Max jobs per company** (newest first) and **max jobs in total**.

### Only new jobs (monitoring)

Turn on **Only new jobs** and schedule the Actor. Each run returns only postings it hasn't returned before for that monitor, so you get a feed of new roles and pay only for those. The first run returns everything currently open. Use **Monitor name** to keep separate feeds for separate searches.

### Salary

Salary is filled in whenever the company publishes it:

- **Structured** from the ATS: Greenhouse pay-transparency ranges, Ashby compensation, Lever salary range, Recruitee salary.
- **From the description** when the posting states a pay range in its text, such as "base salary range $145,000 - $200,000". `salarySource` tells you which.

### Output

One row per job, with the same fields for every system:

```json
{
  "title": "Engineering Manager, Notifications",
  "company": "Discord",
  "location": "Remote (U.S.)",
  "locations": ["Remote (U.S.)"],
  "country": null,
  "remote": true,
  "workplaceType": "remote",
  "department": "Product Engineering",
  "team": null,
  "employmentType": null,
  "salaryMin": 248000,
  "salaryMax": 310000,
  "salaryCurrency": "USD",
  "salaryPeriod": "year",
  "salaryText": "Base Salary: 248,000 - 310,000 USD",
  "salarySource": "structured",
  "description": "Plain-text job description…",
  "applyUrl": "https://job-boards.greenhouse.io/discord/jobs/…",
  "jobUrl": "https://job-boards.greenhouse.io/discord/jobs/…",
  "postedAt": "2026-10-13T17:02:11.000Z",
  "updatedAt": "2026-10-29T14:42:09.000Z",
  "source": "greenhouse",
  "companyId": "discord",
  "jobId": "8100000002",
  "uniqueId": "greenhouse:discord:8100000002",
  "fetchedAt": "2026-11-03T20:00:00.000Z"
}
```

- `workplaceType` is `remote`, `hybrid`, `onsite` or `null` when the posting doesn't say.
- `employmentType` is normalised to Full-time, Part-time, Contract, Temporary, Internship, Freelance or Apprenticeship.
- `updatedAt` is only available from Greenhouse and Recruitee. The other systems don't publish it.
- Dates are ISO 8601 in UTC.

The run also saves a `SUMMARY` record in its key-value store. For each input it lists which board was used, how many jobs were open, matched and returned, and anything that wasn't found.

### Limits

- **No logins or scraping** — only the official public job-board APIs listed above. If a company hides its board or uses an unsupported ATS (e.g. Jobvite, iCIMS), you get zero rows for that input, not an error for the whole run.
- **Rate limits** — concurrent requests per host are capped (Workable is the strictest). The Actor backs off on HTTP 429/503 and retries a few times.
- **Workday & BambooHR** — one extra API call per returned job for full description (and Workday posting dates).
- **Greenhouse salary** — optional per-job pay-transparency calls (`fetchSalaryDetails`); turn off if you only need titles and links.
- **Caps** — `maxJobsPerCompany` and `maxJobs` stop the run early; in pay-per-event mode you are never charged beyond your run's spending limit.

### Good to know

- Jobs come only from public job-board endpoints that the ATS vendors provide for this purpose. Requests are spread out per host and retried politely when a service asks to slow down.
- Jobvite and iCIMS have no unauthenticated public job JSON/XML feed; they aren't covered.
- For SmartRecruiters, the company id is the part after `jobs.smartrecruiters.com/`. The API answers the same way for an unknown id as for a company with no open jobs, so check the id if a board comes back empty.

# Actor input Schema

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

Leave empty to search every company in the built-in directory (1,250+ companies hiring on Greenhouse, Lever and Ashby) with your keywords and filters. Or list the companies you want, one per line. Any of: a job-board URL (boards.greenhouse.io/stripe, jobs.lever.co/palantir, jobs.ashbyhq.com/ramp, apply.workable.com/huggingface, jobs.smartrecruiters.com/ServiceNow, bunq.recruitee.com, nvidia.wd5.myworkdayjobs.com/…/NVIDIAExternalCareerSite, trading212.jobs.personio.de, careers.embark-studios.com/jobs, disruptive.bamboohr.com/careers), a company’s own careers-page URL (the board it links to is detected), system:id (greenhouse:stripe, workday:nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite, personio:trading212), or just a company name (every system is checked).

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

Keep jobs whose title contains any of these words or phrases (word starts, case-insensitive: engineer also matches Engineering). Leave empty for every job.

## `searchDescription` (type: `boolean`):

Match keywords in the job description too, not only the title.

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

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

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

Keep jobs whose location mentions any of these, e.g. 'London', 'Germany', 'US'. 'Remote' keeps remote jobs.

## `remote` (type: `string`):

Filter by where the work happens.

## `departments` (type: `array`):

Keep jobs whose department or team contains any of these, e.g. 'Engineering', 'Sales'.

## `postedSince` (type: `string`):

Only jobs posted on or after this date (2026-10-01) or within this age ('24 hours', '7 days', '2 weeks').

## `onlyNewJobs` (type: `boolean`):

Return only jobs this monitor has not returned before (the first run returns everything). Schedule the Actor with this on to get a feed of new postings and pay only for those.

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

Keeps separate memories for separate searches in only-new mode. Leave empty to use one memory per list of companies (or per set of search filters).

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

Newest first. Keeps one big employer from filling a search. 0 = no limit.

## `maxJobs` (type: `integer`):

Stop after this many jobs in total. In search mode you get the newest matching jobs across all companies. 0 = no limit.

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

Full job description as plain text. Turn off for smaller, faster results.

## `fetchSalaryDetails` (type: `boolean`):

Greenhouse publishes pay ranges per job. This makes one extra request per returned Greenhouse job to read them. Other systems include salary in the main feed.

## Actor input object example

```json
{
  "keywords": [
    "data engineer"
  ],
  "searchDescription": false,
  "remote": "any",
  "onlyNewJobs": false,
  "maxJobsPerCompany": 25,
  "maxJobs": 100,
  "includeDescription": true,
  "fetchSalaryDetails": true
}
```

# Actor output Schema

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

JSON dataset items from the run (one object per job).

# 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 = {
    "keywords": [
        "data engineer"
    ],
    "maxJobsPerCompany": 25,
    "maxJobs": 100
};

// Run the Actor and wait for it to finish
const run = await client.actor("runix/career-site-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 = {
    "keywords": ["data engineer"],
    "maxJobsPerCompany": 25,
    "maxJobs": 100,
}

# Run the Actor and wait for it to finish
run = client.actor("runix/career-site-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 '{
  "keywords": [
    "data engineer"
  ],
  "maxJobsPerCompany": 25,
  "maxJobs": 100
}' |
apify call runix/career-site-jobs-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,runix/career-site-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/KZlEbDB34Gd2Tf0tf/builds/KijEh9d17yR6dCAz9/openapi.json
