# Greenhouse, Lever & Ashby Jobs API – New Job Alerts (`hapol/job-boards`) Actor

Greenhouse, Lever and Ashby job scraper built on their official public job-board APIs. Get every open job in one schema, salary where published, title/location/remote filters, and a new-since-last-run mode for daily job alerts. $1 per 1,000 jobs. No proxy, no login.

- **URL**: https://apify.com/hapol/job-boards.md
- **Developed by:** [Haydon Apolinar](https://apify.com/hapol) (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.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

## Greenhouse, Lever & Ashby Jobs API – New Job Alerts

A **job scraper** for company career boards on **Greenhouse, Lever and Ashby**. It pulls **every open job** through the **Greenhouse jobs API**, the **Lever job board API** and the **Ashby job board API**, the official public endpoints each ATS provides. You get one clean, standard schema for all three, published salary ranges, filters, and a **new-since-the-last-run** mode for daily job alerts.

No HTML scraping, no proxy, no login: these are the same public JSON endpoints the companies' own career pages use.

| ATS | Official public endpoint |
|---|---|
| Greenhouse | `https://boards-api.greenhouse.io/v1/boards/<board>/jobs` |
| Lever | `https://api.lever.co/v0/postings/<company>?mode=json` (EU: `api.eu.lever.co`) |
| Ashby | `https://api.ashbyhq.com/posting-api/job-board/<board>?includeCompensation=true` |

### Use cases: job alerts, job boards, recruiting and hiring signals

- **Recruiters and sourcers** tracking openings at target companies.
- **Job boards and newsletters** aggregating roles from many company boards.
- **Sales and market-intel teams** using hiring as a buying signal (new team, new market, new tool).
- **Job seekers** running a daily "new roles at my 50 favourite companies" alert.

### How to find a company's Greenhouse, Lever or Ashby board

Open the company's careers page and look at where the job links point:

| Job link looks like | Board to enter |
|---|---|
| `boards.greenhouse.io/acme` or `job-boards.greenhouse.io/acme` | `greenhouse:acme` |
| `jobs.lever.co/acme` | `lever:acme` |
| `jobs.ashbyhq.com/acme` | `ashby:acme` |

You can also paste the full URL. Not sure which ATS a company uses? Enter just the name (e.g. `acme`) and the Actor tries Greenhouse, then Lever, then Ashby. Every board is checked first, and missing or misspelled boards are reported for free in the **Board status** output.

### Input

Give boards in any of these forms:

- `greenhouse:gitlab`, `lever:palantir`, `ashby:ashby`
- board URLs: `https://boards.greenhouse.io/gitlab`, `https://job-boards.greenhouse.io/gitlab`, `https://jobs.lever.co/palantir`, `https://jobs.ashbyhq.com/ashby`
- a bare name such as `gitlab`: the Actor tries Greenhouse, then Lever, then Ashby

```json
{
  "boards": ["greenhouse:gitlab", "https://jobs.lever.co/palantir", "ashby"],
  "titleKeywords": ["engineer", "manager"],
  "locations": ["Remote", "London"],
  "remoteOnly": false,
  "postedWithinDays": 30,
  "includeDescription": false,
  "onlyNew": true,
  "maxJobs": 1000
}
```

**Company names:** Greenhouse returns the company name. The Lever and Ashby job-board APIs don't, so the board token is turned into a name (`acme-labs` → "Acme Labs"); `companySource` tells you which was used, and `companySlug` always holds the token. To set exact names, add `"companyNames": {"lever:palantir": "Palantir Technologies"}`.

**Board-exists check:** each board is checked first. Missing or misspelled boards are listed in the **Board status** output (the `BOARDS` record) with the reason. They cost nothing.

### Output (one standard schema)

Real record from GitLab's Greenhouse board (description fields omitted):

```json
{
  "jobId": "greenhouse:gitlab:8860302002",
  "ats": "greenhouse",
  "board": "gitlab",
  "company": "GitLab",
  "companySlug": "gitlab",
  "companySource": "api",
  "id": "8860302002",
  "title": "Account Executive - France",
  "department": "EMEA - Commercial",
  "location": "Remote, France",
  "locations": ["Remote, France", "France"],
  "workplaceType": "remote",
  "isRemote": true,
  "postedAt": "2026-10-02T15:31:50.000Z",
  "updatedAt": "2026-10-02T15:31:50.000Z",
  "jobUrl": "https://job-boards.greenhouse.io/gitlab/jobs/8860302002",
  "isNew": true,
  "scrapedAt": "2026-10-09T22:00:00.000Z"
}
```

Ashby and some Lever boards also publish pay. You get `salaryMin`, `salaryMax`, `salaryCurrency` and `salaryInterval`, plus Ashby's `compensationSummary` (e.g. "€110K – €185K • Offers Equity • Offers Bonus").

With `includeDescription` you also get `descriptionHtml` and `descriptionText` (clean text with bullet points).

The dataset has three views: **Jobs**, **Salary** and **Descriptions**.

### Greenhouse, Lever & Ashby job alerts (new jobs only)

Turn on `onlyNew` and schedule the Actor daily. Each run remembers the job IDs it has seen in a key-value store named **`job-boards-state`** in your account, keyed by your board list or by `stateKey`. The next run outputs only jobs that are new since the last run, and every job also carries `isNew`.

- The first run outputs everything and records it.
- Use a different `stateKey` for each separate schedule.
- If a run stops early (max jobs or your spending limit), the state isn't advanced, so nothing gets skipped.

To get alerts:

1. Save your input as a task and turn on `onlyNew`.
2. Create an Apify **schedule** for the task (e.g. daily at 8:00).
3. Send each run's results to Slack, email, Google Sheets or your own endpoint with Apify [integrations](https://docs.apify.com/platform/integrations) or [webhooks](https://docs.apify.com/platform/integrations/webhooks).

### How much does it cost?

| Event | Price | Per 1,000 |
|---|---|---|
| Job (`job`) | $0.001 | **$1.00** |
| Full description add-on (`job-description`) | $0.0005 | **+$0.50** |
| Missing boards, filtered-out and already-seen jobs | free | free |

Example (illustrative): 50 boards × ~100 open jobs = 5,000 jobs ≈ $5.00 on the first run. With `onlyNew`, later runs bill only the new jobs, often just a few dollars a month.

Set a **max cost per run** to cap your spend; the Actor stops cleanly when the cap is reached.

### Limits & good to know

- Only boards hosted on Greenhouse, Lever or Ashby are supported (not Workday, iCIMS, SmartRecruiters, etc.).
- `postedAt` is the ATS's own date: Greenhouse `first_published`, Lever `createdAt`, Ashby `publishedAt`.
- Greenhouse doesn't expose salary in the public jobs list. Lever only does when the company publishes a salary range.
- Each board is 1–2 API requests, so even large boards finish in seconds.

### Why not call the Greenhouse API yourself?

You can: the job-board APIs are free and public. This Actor saves you the glue code:

- one standard schema across Greenhouse, Lever and Ashby;
- title, location, department, remote and date filters;
- salary fields normalised where the ATS publishes them;
- state for new-jobs-only output, so schedules don't repeat jobs;
- board-exists checks, retries, and maintenance when an API changes.

### Comparison with other ATS job scrapers

| | This Actor |
|---|---|
| ATS coverage | Greenhouse, Lever, Ashby |
| Price | $1.00 per 1,000 jobs, +$0.50 per 1,000 for full descriptions |
| New-jobs-only mode | Yes (`onlyNew`) |
| Board discovery | Bare-name fallback + free board-exists check (no company directory) |

Other Actors in the Store cover more ATSs (some cover Workday, SmartRecruiters and many more) or include company directories. If you only need the big three, this one keeps it simple.

### Responsible use

This Actor reads **public job postings** that companies publish through their ATS's official public job-board API. It doesn't log in, submit applications, or collect candidate data. Postings belong to the companies that published them:

- link back to the original `jobUrl` / `applyUrl`;
- don't present postings as your own;
- remove jobs when they close (re-run, or use `onlyNew` plus your own expiry).

Check each ATS's terms for your use case. Recruiter or contact names aren't collected, but if a description contains personal data, handle it under the laws that apply to you (e.g. GDPR).

### Related Actors

- [Shopify Store Leads Scraper – Emails, Socials & Products](https://apify.com/hapol/shopify-store-leads): verified Shopify stores with product counts, socials and public business emails.
- [WooCommerce Store Leads – Wix, Squarespace & BigCommerce](https://apify.com/hapol/ecommerce-store-leads): the same for WooCommerce, Wix, Squarespace and BigCommerce stores.

***

Built and maintained by **Hapol Data** (Web Data. Automated.). More Actors: [apify.com/hapol](https://apify.com/hapol).

# Actor input Schema

## `boards` (type: `array`):

Greenhouse, Lever or Ashby boards: "greenhouse:gitlab", "lever:palantir", "ashby:ashby", a board URL (boards.greenhouse.io/gitlab, job-boards.greenhouse.io/gitlab, jobs.lever.co/palantir, jobs.ashbyhq.com/ashby) or a bare name (all three ATSs are tried). Missing boards are reported in the BOARDS record and are free.

## `companyNames` (type: `object`):

Override the company name per board, e.g. {"lever:palantir": "Palantir Technologies", "ashby": "Ashby Inc."}. Keys can be the board token, "ats:token" or the board as entered. Without an override, Greenhouse uses the company name from its API; Lever and Ashby APIs don't return one, so the board token is humanized ("acme-labs" → "Acme Labs").

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

Keep jobs whose title contains any of these (case-insensitive), e.g. "engineer", "sales". Empty = all.

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

Keep jobs whose location or country contains any of these, e.g. "Remote", "London", "US". Empty = all.

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

Keep jobs whose department or team contains any of these. Empty = all.

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

Keep only jobs marked remote by the ATS (or with "Remote" in the Greenhouse location).

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

Keep only jobs first published in the last N days (where the ATS publishes a date).

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

Adds descriptionHtml and descriptionText. Extra charge per job.

## `onlyNew` (type: `boolean`):

Output only jobs not seen in a previous run with the same boards (or the same state key). Seen job IDs are kept in your named key-value store "job-boards-state". The first run outputs everything. Ideal for scheduled daily alerts.

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

Optional name for the new-since-last-run state. Default: derived from the board list. Use different keys for different schedules.

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

Maximum jobs to output in this run.

## Actor input object example

```json
{
  "boards": [
    "greenhouse:gitlab",
    "ashby:ashby"
  ],
  "remoteOnly": false,
  "includeDescription": false,
  "onlyNew": false,
  "maxJobs": 20
}
```

# Actor output Schema

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

No description

## `salary` (type: `string`):

No description

## `descriptions` (type: `string`):

No description

## `boards` (type: `string`):

Which boards exist, ATS detected, jobs found, errors (BOARDS record).

## `stats` (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 = {
    "boards": [
        "greenhouse:gitlab",
        "ashby:ashby"
    ],
    "maxJobs": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("hapol/job-boards").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 = {
    "boards": [
        "greenhouse:gitlab",
        "ashby:ashby",
    ],
    "maxJobs": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("hapol/job-boards").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 '{
  "boards": [
    "greenhouse:gitlab",
    "ashby:ashby"
  ],
  "maxJobs": 20
}' |
apify call hapol/job-boards --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,hapol/job-boards"
        }
    }
}
```

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/SOdYxrpLUtMPyfpk0/builds/NGnTnocu05GQ18w8z/openapi.json
