# Greenhouse Jobs Scraper & API - 4,600 Boards Built In (`rationalistic_candle_ucn/greenhouse-jobs-directory`) Actor

Open job postings from Greenhouse ATS boards, read live from the official Job Board API. Name the companies, or use the built-in directory of 4,600 verified board slugs and see which companies are hiring without supplying one. One row per posting, pay per row.

- **URL**: https://apify.com/rationalistic\_candle\_ucn/greenhouse-jobs-directory.md
- **Developed by:** [Bruce McGinley](https://apify.com/rationalistic_candle_ucn) (community)
- **Categories:** Jobs, Lead generation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.50 / 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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Greenhouse Jobs Directory

Open job postings from any company that hires through Greenhouse, read live from Greenhouse's official Job Board API. Name the companies you want, or leave the list empty and read from the built-in directory: 4,636 boards verified live on 2026-09-08, so you can pull a broad slice of the Greenhouse corpus without supplying a single slug.

One dataset item per open posting. One charge per item. No proxies, no browser, no guessing: every row comes from `boards-api.greenhouse.io`, the endpoint Greenhouse publishes for exactly this purpose.

### How it works

1. **Boards.** If `companySlugs` has entries, those are the boards. Each may be a board token (`stripe`) or a board URL (`https://boards.greenhouse.io/stripe`, `https://job-boards.greenhouse.io/stripe/jobs/123`). Anything on another host is refused, not fetched. If the list is empty, the Actor takes boards from its directory, optionally filtered by `directoryFilter`, up to `maxCompanies`.
2. **Quote.** Before the first request the log states the most the run can cost: boards × `maxJobsPerCompany` × the per-row price. Real runs cost less, since most boards have far fewer than 200 open postings.
3. **Read.** One request per board to `/v1/boards/{token}/jobs?content=true`, at most four requests a second across the whole run, four boards at a time, with a descriptive User-Agent. Redirects are not followed. A 404 means the board is gone and is logged, not charged. 429 and 5xx are retried twice with backoff, and a `Retry-After` header is honoured up to 30 seconds; any other failure is logged and not charged. A board is not even requested once the run's charge limit has no room for its first row.
4. **Filter.** `firstPublishedAfter` and `updatedAfter` keep only new or changed postings, so a daily run on the same boards charges only for what moved. Filtering happens before charging.
5. **Rows.** Each kept posting is charged and pushed as one item. When `includeCompanyMeta` is on, the board's own description is added to every row of that board (one extra request per board with postings).

The run's status message states the totals: jobs, boards, postings outside the filters, boards read in part when a charge limit cut the run, boards gone, boards that could not be read.

### Directory

The directory is built by `scripts/harvest.mjs` in two stages:

- **Harvest.** The Common Crawl CDX index is read for every URL it has seen on `boards.greenhouse.io` and `job-boards.greenhouse.io` across the four newest crawls: CC-MAIN-2026-21, CC-MAIN-2026-25, CC-MAIN-2026-30, CC-MAIN-2026-34. The first path segment of each URL is a board token. No token is guessed, none is copied from another dataset.
- **Validate.** Each token is read once against `/v1/boards/{token}` at four requests a second. A 200 keeps it, with the company name Greenhouse returns; a 404 drops it.

`src/directory.json` carries the crawl ids, the harvest date, the validation date and the counts, so you can see exactly how old the list is. On 2026-09-08, 6,142 tokens were seen and 4,636 were live.

Two limits, stated plainly. The directory is **incomplete**: Common Crawl only indexes boards that some page linked to, so companies that embed Greenhouse on their own site and never link the hosted board are missing. And between rebuilds it goes **stale** by months: boards close and open every week, which is why the Actor never trusts the list and reads every board live. Give your own tokens for anything the directory lacks. The directory is rebuilt from newer crawls, and every rebuild revalidates every token; `validatedAt` in `directory.json` is the date that counts.

### Pricing

Pay per event. The one event is **job**, charged once per posting row at $0.002 on the free tier. There is no start fee. Boards that turn out empty, gone, filtered out or unreadable cost nothing beyond the Apify platform usage of the run itself.

Worked cases. The first figure of each is the ceiling the log quotes before a run starts; the last is what to expect.

- Three named boards, default `maxJobsPerCompany` of 200: at most 600 rows, so at most $1.20. A typical three-company read returns well under that.
- The default directory run, `maxCompanies` 500: at most 100,000 rows, so at most $200.00. In practice a run with the default input on 2026-09-08, the first 500 boards of the directory in token order, returned 12,958 rows, so $25.92.
- A daily delta on the same 500 boards with `updatedAfter` set to yesterday: only the postings that changed, usually a few hundred rows.

Raise the run's maximum total charge in Apify if you want the whole directory in one run; the Actor stops cleanly at the limit and says how many boards were left.

### Input

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `companySlugs` | string\[] | `[]` | Board tokens or board URLs. Empty means use the directory. |
| `directoryFilter` | string | `""` | Case-insensitive match on token or company name, directory mode only. |
| `maxCompanies` | integer | `500` | Boards read this run. |
| `maxJobsPerCompany` | integer | `200` | Rows kept per board. |
| `firstPublishedAfter` | ISO date | `""` | Keep postings first published after this. |
| `updatedAfter` | ISO date | `""` | Keep postings updated after this. |
| `includeContent` | boolean | `false` | Add the posting's description HTML to each row. |
| `includeCompanyMeta` | boolean | `true` | Add the board's description to each row. |

Input example, three companies with descriptions:

```json
{
  "companySlugs": ["stripe", "https://boards.greenhouse.io/figma", "duolingo"],
  "includeContent": true
}
```

Directory mode, boards whose token or name contains "pay", daily delta:

```json
{
  "directoryFilter": "pay",
  "maxCompanies": 200,
  "updatedAfter": "2026-09-07T00:00:00Z"
}
```

### Output

One item per posting. Field names follow the Job Board API's own, so any row can be checked against its `absoluteUrl`.

```json
{
  "jobId": 4001,
  "boardSlug": "acme",
  "boardName": "Acme Inc",
  "title": "Data Engineer",
  "absoluteUrl": "https://job-boards.greenhouse.io/acme/jobs/4001",
  "location": "Remote - US",
  "departments": ["Engineering"],
  "offices": ["Boston"],
  "firstPublished": "2026-08-20T09:00:00-04:00",
  "updatedAt": "2026-09-05T10:00:00-04:00",
  "language": "en",
  "applicationDeadline": null,
  "requisitionId": "R-77",
  "internalJobId": 900,
  "metadata": [{ "id": 1, "name": "Team", "value": "Data", "value_type": "single_select" }],
  "content": null,
  "boardContent": "<p>About Acme</p>",
  "aiOptOutRequestUrl": null,
  "source": "boards-api.greenhouse.io",
  "fetchedAt": "2026-09-08T12:00:00.000Z"
}
```

Field notes. `boardName` is the `company_name` Greenhouse returns on the posting itself. `content` is the description HTML when `includeContent` is on, otherwise null. `boardContent` is the board's description when `includeCompanyMeta` is on. `metadata` is the board's custom fields as Greenhouse returns them. `language`, `applicationDeadline`, `requisitionId`, `internalJobId` and `aiOptOutRequestUrl` are passed through from the API unchanged, the last so you can honour it. No applicant data is on this API, and nothing about people is extracted from postings.

### Use cases

- **Hiring signals.** Which companies opened engineering roles this week, by department and office.
- **Job aggregation.** A daily delta feed of new and changed postings for a job board or newsletter.
- **Market research.** Titles, locations and departments across a sector, filtered from the directory by name.
- **Recruiting tools.** Watch a list of target companies and alert on matching titles.

### FAQ

**Is this allowed?** The Job Board API is Greenhouse's public, keyless, documented interface for reading boards, and the Actor reads it the way its documentation describes: one request per board, under five a second, identified by User-Agent. It does not touch the hosted board pages, does not use a proxy, and does not read anything but the API host.

**Why does a board return nothing?** Either it has no open postings, or it was gone (404) when read; the log names each. Boards that Greenhouse serves under a custom domain but not at `boards.greenhouse.io` still work if you give their token.

**Why is my company not in the directory?** The directory only holds boards that Common Crawl saw linked from somewhere. Give the token in `companySlugs` and it is read like any other.

**Can I read every board in the directory at once?** Set `maxCompanies` to the directory size and raise the run's maximum total charge. The log quotes the ceiling first; the Actor stops at the limit rather than overrunning it. That is the directory, which is smaller than Greenhouse itself: see the limits above.

**Where do I report a problem?** The Issues tab of this Actor.

# Actor input Schema

## `companySlugs` (type: `array`):

Greenhouse board tokens or board URLs (boards.greenhouse.io/<token> or job-boards.greenhouse.io/<token>), one per line. Leave empty to read from the built-in directory of verified boards instead.

## `directoryFilter` (type: `string`):

Only used when Companies is empty: a case-insensitive text match on the board token or company name. Empty means every board in the directory, up to Max companies.

## `maxCompanies` (type: `integer`):

Boards read in this run. Each board is one request; the whole directory is a deliberate choice, so raise this on purpose. The log states the run's maximum cost before the first request.

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

Rows kept per board.

## `firstPublishedAfter` (type: `string`):

Delta mode: keep only postings first published after this date or time (ISO 8601, e.g. 2026-09-01 or 2026-09-01T00:00:00Z). Empty keeps everything.

## `updatedAfter` (type: `string`):

Delta mode: keep only postings updated after this date or time (ISO 8601). Empty keeps everything.

## `includeContent` (type: `boolean`):

Put the full posting description (HTML) in each row. Rows get several times larger; the price per row does not change.

## `includeCompanyMeta` (type: `boolean`):

Add the board's own description text to each row (one extra request per board that has postings).

## Actor input object example

```json
{
  "companySlugs": [
    "stripe",
    "figma",
    "duolingo"
  ],
  "directoryFilter": "",
  "maxCompanies": 500,
  "maxJobsPerCompany": 200,
  "firstPublishedAfter": "",
  "updatedAfter": "",
  "includeContent": false,
  "includeCompanyMeta": true
}
```

# Actor output Schema

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

One item per open posting: board, title, URL, location, departments, offices, dates, and optionally the description HTML.

# 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 = {
    "companySlugs": [
        "stripe",
        "figma",
        "duolingo"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("rationalistic_candle_ucn/greenhouse-jobs-directory").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 = { "companySlugs": [
        "stripe",
        "figma",
        "duolingo",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("rationalistic_candle_ucn/greenhouse-jobs-directory").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 '{
  "companySlugs": [
    "stripe",
    "figma",
    "duolingo"
  ]
}' |
apify call rationalistic_candle_ucn/greenhouse-jobs-directory --silent --output-dataset

```

## MCP server setup

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

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/I5hQWJncURpVUcpax/builds/3uUAkArekhLjZZCQL/openapi.json
