# Greenhouse, Lever & Ashby Job Board Scraper (`jmspwr/greenhouse-lever-ashby-jobs`) Actor

Read public job postings from Greenhouse, Lever and Ashby company boards and get them in one schema: titles, locations, posting dates, apply links and plain-text descriptions. Public endpoints only, no login.

- **URL**: https://apify.com/jmspwr/greenhouse-lever-ashby-jobs.md
- **Developed by:** [James Power](https://apify.com/jmspwr) (community)
- **Categories:** Jobs
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 job postings

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 Job Board Scraper

Read public job postings from Greenhouse, Lever and Ashby company boards and get them in one schema. Give it a list of board names (for example `greenhouse:airbnb`) and it returns one row per posting: title, location, department, posting date, apply link and plain-text description. Use it for job-market monitoring, tracking hiring at a shortlist of employers, or feeding a job alert.

It uses only the public, unauthenticated endpoints those ATS platforms document for reading published postings. No login, no API key, no browser, and no HTML scraping of career pages.

### Use case

- Track open roles at a company that hosts its jobs on Greenhouse, Lever or Ashby.
- Compare hiring across a shortlist of employers in one table.
- Monitor one board for new postings, with or without descriptions.

### Sample output

One dataset item per posting (abridged; real rows carry every field below):

```json
{
  "source": "ashby",
  "board": "notion",
  "jobId": "1fc309c8-da20-4ff2-84c7-8b863ece2b0a",
  "title": "Software Engineer, Developer Platform",
  "location": "San Francisco, California",
  "department": "Engineering",
  "team": "Engineering",
  "employmentType": "FullTime",
  "workplaceType": "Hybrid",
  "postedAt": "2026-08-24T14:44:49Z",
  "updatedAt": null,
  "jobUrl": "https://jobs.ashbyhq.com/notion/1fc309c8-da20-4ff2-84c7-8b863ece2b0a",
  "applyUrl": "https://jobs.ashbyhq.com/notion/1fc309c8-da20-4ff2-84c7-8b863ece2b0a/application",
  "descriptionText": "WHO WE ARE Notion is the collaborative AI workspace where teams and agents think together ... (abridged; email addresses in the text would read [email removed])",
  "salarySummary": null,
  "scrapedAt": "2026-10-12T19:00:00Z"
}
```

### Input

| Field | Type | Default | Meaning |
|---|---|---|---|
| `boards` | list of strings | required | One entry per company board, written `ats:token`. `ats` is `greenhouse`, `lever` or `ashby`. The token is the public board name in the ATS URL: `greenhouse:airbnb` is boards.greenhouse.io/airbnb. 1 to 50 entries. Duplicates are removed. |
| `maxJobs` | integer | 200 | Stop after this many postings across all boards (1 to 5000). |
| `includeDescription` | boolean | true | Include plain-text job description. Off gives titles, places, dates and links only. |

Any other field is rejected with `unknown field`, so a misspelt option never runs silently with the default.

### Output

One dataset item per posting, with exactly these fields (no others):

`source`, `board`, `jobId`, `title`, `location`, `department`, `team`, `employmentType`, `workplaceType`,
`postedAt`, `updatedAt`, `jobUrl`, `applyUrl`, `descriptionText`, `salarySummary`, `scrapedAt`.

Dates are UTC ISO 8601 with a `Z`. `updatedAt` is filled for Greenhouse only. `salarySummary` is filled only
when the ATS publishes a salary. Fields the ATS does not provide are `null`, never guessed.

Greenhouse compliance, metadata and office objects are dropped. No contact field is read or returned. Email addresses that appear inside a description (for example an accommodation or recruiting inbox, or a bare company email domain such as `@company.com`) are replaced with `[email removed]`.

### Examples

Three boards, 100 postings at most:

```json
{
  "boards": ["greenhouse:airbnb", "lever:outreach", "ashby:notion"],
  "maxJobs": 100,
  "includeDescription": true
}
```

Titles, places and links only, for a quick monitor of one board:

```json
{
  "boards": ["ashby:notion"],
  "maxJobs": 500,
  "includeDescription": false
}
```

### Errors

Errors are reported in the run status message and cost nothing:

- `boards must be a non-empty list` / `accepts at most 50 entries`: fix the list.
- `unsupported ATS 'x'`: use `greenhouse`, `lever` or `ashby`. Workday, SmartRecruiters and others are not read here.
- `invalid board token`: letters, digits, dot, dash and underscore only.
- `maxJobs must be a whole number from 1 to 5000`, `includeDescription must be true or false`, `unknown field 'x'`.
- `no board found for ...; check each token on its ATS page`: every board returned not found. Check the token.
- `no board could be read: ...`: the ATS returned errors after retries (rate limit or outage). Try again later.

A board that is not found, while others work, is listed as `not_found` in the run output and the run continues.

### Pricing

Pay per event: `job-found`, **$0.001 per stored posting** (about $1 per 1,000). Nothing is charged for an
input error, a missing board, an empty board or a run that stores no postings. Each posting is pushed to the
dataset first and then charged once. If a spend limit stops the run, the one posting that hit the limit is
stored without a charge and nothing after it is stored.

### Limits (honest)

- Reads only what each ATS publishes publicly. Postings that are unpublished, internal or behind a login are
  not returned. Ashby postings marked `isListed: false` are skipped.
- Each ATS returns a board in one response, so there is no paging to follow. `maxJobs` caps the total and is
  shared fairly: each board gives up to an equal share first, then any unused room is filled from the boards in
  order. A board with fewer postings than its share leaves the room to the others.
- Requests to the same host are at least 0.5 seconds apart.
- Greenhouse, Lever and Ashby only. For Workday, SmartRecruiters, Recruitee or Personio, use a different Actor.
- No enrichment: no company data, no LinkedIn data, no AI summaries, no salary estimates.
- Descriptions are the ATS's own text converted to plain text; formatting and images are removed.
- Freshness is the ATS's publish state at run time. A posting removed after the run stays in your dataset.
- A slow or rate-limited ATS shows as an error for that board; the run keeps the boards that worked.

### Sources and terms

Greenhouse Job Board API (`boards-api.greenhouse.io/v1/boards/{token}/jobs`, no authentication for the job list),
Lever Postings API (`api.lever.co/v0/postings/{token}?mode=json`; Lever's own documentation says published
postings are public), and the Ashby public job posting API (`api.ashbyhq.com/posting-api/job-board/{name}`).
Each posting remains the property of the hiring company. Use the data within the rights you hold, and check each
company's terms before redistributing postings. This Actor does not submit applications and does not use any
ATS write endpoint.

### Support

Issues and failed runs are reviewed on the Actor's Issues tab.

# Actor input Schema

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

One entry per company board, written ats:token. ats is greenhouse, lever or ashby. The token is the public board name in the ATS URL (for example greenhouse:airbnb for boards.greenhouse.io/airbnb). Up to 50 entries; duplicates are removed.

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

Stop after this many postings across all boards (1 to 5000). Each stored posting is one billable event.

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

Plain-text job description. Turn off to get titles, places, dates and links only.

## Actor input object example

```json
{
  "boards": [
    "greenhouse:airbnb",
    "lever:outreach",
    "ashby:notion"
  ],
  "maxJobs": 200,
  "includeDescription": true
}
```

# Actor output Schema

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

Every posting stored by the run (one dataset item per posting).

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

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

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jmspwr/greenhouse-lever-ashby-jobs"
        }
    }
}
```

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/hDBYNJXPAQjMLgEJb/builds/Vvfo3ScYzh51NxqpI/openapi.json
