# Workday & Greenhouse Jobs API: Lever, Ashby, Workable +4 (`northpine-studio/ats-jobs-aggregator`) Actor

Job board API: open jobs from Greenhouse, Lever, Ashby, Workable, Recruitee, SmartRecruiters and Personio in ONE schema (location, remote, department, salary, posted date, apply link). New-jobs-only alerts, daily job history and hiring trends. Official public job-board APIs, no scraping.

- **URL**: https://apify.com/northpine-studio/ats-jobs-aggregator.md
- **Developed by:** [Northpine Studio](https://apify.com/northpine-studio) (community)
- **Categories:** Jobs, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 result (job)s

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

## Workday & Greenhouse Jobs API: Lever, Ashby, Workable, Recruitee, SmartRecruiters, Personio, Teamtailor

Need a job board API or a scheduled new-jobs alert across Greenhouse, Lever, Ashby, Workable, Recruitee, SmartRecruiters, Personio, Teamtailor and Workday? Give it company slugs and get normalized openings; schedule it for recruiting and job-alert monitoring.

One Actor, one clean schema, nine applicant tracking systems. Give it company board slugs and get every open job as normalized rows.

> Tip: the `bundled:workday` list is large (about 25 minutes, 25k rows) and `bundled:greenhouse` about 13 minutes for 100k rows; set the run timeout to at least 1 hour for bundled lists.

**Data source:** the public job-board APIs that Workday, Greenhouse, Lever, Ashby, Workable, Recruitee, SmartRecruiters, Personio and Teamtailor provide for embedding postings on company career pages. No scraping of websites, no logins, no recruiter or candidate data.

### Use cases

- Job-alert and recruiting-intelligence feeds: schedule it and get only new postings
- Hiring-trend and salary research across companies
- Building a niche job board from many company career pages
- Lead generation: find companies hiring for a role you sell into

### Example input

```json
{"companies":["greenhouse:stripe","lever:spotify","ashby:ashby"],"keywords":["engineer"],"remoteOnly":true,"maxItems":100}
```

Tip: pass just `"stripe"` and the Actor finds which ATS hosts that board.

### Output fields

ats, company, companyName, jobId, title, department, team, location, locations, country, remote, workplaceType, employmentType, salaryMin, salaryMax, salaryCurrency, salaryInterval, salaryAnnualMin, salaryAnnualMax (annualised, same currency, no FX conversion), postedAt, updatedAt, url, applyUrl, and optionally descriptionText.

### Features

- Normalized location, remote flag, department/team and ISO dates across all seven systems

- Salary range when the company publishes it (Ashby, Lever, Greenhouse pay transparency)

- Filters: title keywords, remote only, location, department, posted within N days, minimum salary

- Cross-board dedupe: the same company, title and location posted on two systems is returned once (toggle `dedupeAcrossBoards`)

- Annualised salary fields make hourly, monthly and yearly pay comparable

- New-jobs-only mode: schedule the Actor and receive only postings you have not seen before

- Job history (set `trackHistory`, schedule daily): every board is remembered between runs. Each job gets `status` (`new`, `open`, `changed`, `reopened`, `removed`), `firstSeenAt`, `lastSeenAt`, `daysOpen` and a `changes` list (title, location, department, salary edits). Jobs that disappear are returned once with `removedAt`. Add `onlyChanges` for cheap daily monitoring.

- Hiring trend per company: open jobs, new/removed since the last run, and 7 and 30 day change, written to the `ats-hiring-trends` dataset and the `HIRING_TRENDS` record. Trend labels need about a week of daily runs; the first run is a baseline.

- **Workday** boards of large employers, pinned as `workday:tenant/wdN/site` (e.g. `workday:nvidia/wd5/NVIDIAExternalCareerSite`; copy the three parts from the careers URL `https://tenant.wdN.myworkdayjobs.com/site`). `bundled:workday` covers ~30 big employers. Workday caps one search at 2000, so large boards are automatically re-queried per job category (and location) to get past the cap (nvidia: ~2,660 jobs, takes ~4.5 min). Posted date is day-accurate (exact start date and full description when `includeDescription` is on, which fetches one extra page per job); no salary field.

- **Teamtailor** career sites (`teamtailor:career` = career.teamtailor.com): public JSON feed, location and description, no salary.

- SmartRecruiters and Personio boards (`smartrecruiters:Canva`, `personio:holidu`; SmartRecruiters slugs are case-sensitive). Their public feeds carry no salary and no description text, so those fields are null there.

### Notes

Only boards a company has made public are read. Salary and remote data depend on what each company publishes; missing values are null. Workable boards have no salary field. Workable rate-limits per IP, so very large Workable lists are fetched slowly. Not affiliated with Greenhouse, Lever, Ashby, Workable, Recruitee, SmartRecruiters or Personio.

# Actor input Schema

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

Company board slugs. Use "bundled" to search all 8,000+ verified company boards we ship (or "bundled:<ats>" for one system: greenhouse, lever, ashby, workable, recruitee, smartrecruiters, personio, teamtailor, workday); mix with your own slugs. Or use "stripe" to auto-detect the ATS (tries Greenhouse, Lever, Ashby, Workable, Recruitee, SmartRecruiters, Personio, Teamtailor in that order; pin it when a name is common) or pin it: "greenhouse:stripe", "lever:spotify", "ashby:ashby", "smartrecruiters:Canva", "personio:holidu", "teamtailor:career", "workday:nvidia/wd5/NVIDIAExternalCareerSite" (Workday is always pinned: tenant/wdN/site from the careers URL https://TENANT.WDN.myworkdayjobs.com/SITE; boards over 2000 jobs are split by facet automatically; "bundled:workday" runs ~30 large employers; with includeDescription on it also fetches each job page for exact start date and description). The slug is the last part of the company jobs URL (boards.greenhouse.io/SLUG, jobs.lever.co/SLUG, jobs.ashbyhq.com/SLUG, apply.workable.com/SLUG, SLUG.recruitee.com, jobs.smartrecruiters.com/SLUG (case-sensitive), SLUG.jobs.personio.de, SLUG.teamtailor.com).

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

Keep jobs whose title contains any of these (case-insensitive). Empty = all.

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

Only jobs flagged or labelled remote.

## `locationContains` (type: `string`):

e.g. Berlin, United States.

## `departmentContains` (type: `string`):

e.g. Engineering, Sales.

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

0 = no limit.

## `minSalary` (type: `integer`):

Keep jobs whose published max (or min) salary is at least this. Only jobs that publish pay can match.

## `newOnly` (type: `boolean`):

Return only jobs not seen in earlier runs. Use with a schedule to monitor for new postings.

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

Add plain-text job description.

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

Total cap across all companies.

## `dedupeAcrossBoards` (type: `boolean`):

If the same company posts the same title and location on more than one ATS (for example Greenhouse and Workable), return it once.

## `trackHistory` (type: `boolean`):

Remember every board between runs. Each job gets status (new, open, changed, reopened, removed), firstSeenAt, lastSeenAt, daysOpen and a changes list (title, location, department or salary edits). Removed jobs are returned once, with removedAt. A per-company hiring trend (open jobs, new/removed since last run, 7 and 30 day change) goes to the 'ats-hiring-trends' dataset and the HIRING_TRENDS record. The first run is a baseline (status baseline). Schedule it daily.

## `onlyChanges` (type: `boolean`):

With 'Track job history', return only new, changed, reopened and removed jobs (the baseline run returns nothing but still records the snapshot). Use for cheap daily monitoring.

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

With 'Track job history', return jobs that disappeared since the last run (once each).

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

With 'New jobs only', separates the remembered-jobs state. By default each distinct search (boards + filters) keeps its own state.

## Actor input object example

```json
{
  "companies": [
    "greenhouse:stripe",
    "lever:spotify",
    "ashby:ashby"
  ],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "minSalary": 0,
  "newOnly": false,
  "includeDescription": false,
  "maxItems": 200,
  "dedupeAcrossBoards": true,
  "trackHistory": false,
  "onlyChanges": false,
  "includeRemoved": true
}
```

# Actor output Schema

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

No description

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

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

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

```

## MCP server setup

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

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/6WfMSlF2cvLGnSUMw/builds/wCJtwfaWHfLE5M3vZ/openapi.json
