# Greenhouse Job Groups — All Locations per Role (`natstandridge/greenhouse-job-groups`) Actor

Read public Greenhouse boards and group postings by the underlying job ID. Preserve every location, title and application link, with explicit completeness reports.

- **URL**: https://apify.com/natstandridge/greenhouse-job-groups.md
- **Developed by:** [Nathaniel Standridge](https://apify.com/natstandridge) (community)
- **Categories:** Jobs
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 delivered job groups

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 Job Groups

Get one result per underlying Greenhouse job, with every public posting, location,
title and application link preserved inside it. Use it to populate a curated job
feed or compare hiring activity across a watchlist of employers.

Greenhouse distinguishes a job from a job post: one job can have several posts
for different locations, languages or titles. This Actor groups only on the source
`internal_job_id`, within a board. Jobs that happen to share a title stay separate.
Prospect posts, whose internal job ID is null, remain separate individual results.

### Input

```json
{
  "boards": ["stripe", "anthropic", "figma"],
  "maxGroups": 2000
}
```

Use the board token from a public Greenhouse URL. For
`https://job-boards.greenhouse.io/stripe`, use `stripe`. Up to 10 boards are accepted;
duplicates are checked once. Other ATS providers, custom career domains and URLs
are not accepted. There is no company directory or keyword discovery.

Defaults: up to 1,000 groups per run, 5,000 source posts per board, a 15-second
request timeout, and a 120-second work budget. Set the platform timeout at least
120 seconds higher than the work budget for bounded storage calls and shutdown.
Limits are explicit in the output summary.

### Results

Each dataset row contains:

- A stable `groupId`, `boardToken`, `internalJobId` and `groupingBasis`.
- `primaryTitle`, all distinct `titles`, all supplied `locations`, and
  `requisitionIds`. The primary title is the title of the smallest posting ID.
- `postingCount` and a `postings` array preserving every posting ID, title,
  location, language, requisition string, source update time and application URL.
- `kind` (`job` or `prospect`) and `observedAt` (this run's observation time).

The `OUTPUT` key-value record reports each board's source posts, groups, duplicate
rows, failures, skipped work and delivered results. Always read it before treating
the dataset as a complete board export. A valid empty board is successful with no
result rows. Failed boards and status summaries are not placed in the paid dataset.

Example: a job with three location posts produces one group containing all three
posts. Two jobs with the same title but different internal IDs produce two groups.
Null internal IDs are never combined. Locations are source labels; a label may
itself list multiple places, and this Actor does not guess how to split it.

Abbreviated example of one group (synthetic data):

```json
{
  "groupId": "greenhouse:example:job:42",
  "kind": "job",
  "internalJobId": "42",
  "primaryTitle": "Software Engineer",
  "locations": ["London", "New York"],
  "postingCount": 2,
  "postings": [
    {"postingId": "100", "title": "Software Engineer", "location": "London", "url": "https://example.com/jobs/100"},
    {"postingId": "101", "title": "Software Engineer", "location": "New York", "url": "https://example.com/jobs/101"}
  ]
}
```

### Reliability and limits

A source list must match Greenhouse's declared total and pass field validation.
An oversized or malformed board produces no groups and a precise status code.
Source responses are capped at 8 MiB. Each complete group must fit a 4 MiB
delivery batch; the frozen plan is capped at 8 MiB. Reduce board count or
`maxGroups` if the plan is too large.
The Actor performs at most three attempts for transient failures; it does not
follow redirects, use proxies, or work around access restrictions.

The result plan is frozen before delivery. If the same run resumes, it reuses that
plan and reads existing dataset group IDs before writing remaining rows. Separate
runs are new snapshots and can produce and charge for the same jobs again. Do not
run multiple processes against the same default dataset. If a dataset write has
an uncertain outcome, the run stops; it does not blindly repeat that write. Writes
contain at most 50 complete groups. A partly confirmed batch needs reconciliation
before resuming; it is not automatically resubmitted.

Only the published job list is read. Descriptions, applicant records and private
Greenhouse APIs are not accessed. No AI service, browser or external paid data
provider is required. The operator's deployment token is never included in source.

Grouped jobs are not hires, headcount or vacancies: one job can have multiple
openings. Source update times are not first publication dates. This version is a
current snapshot; it does not infer closures, track changes across runs, verify
that a role remains actively recruited, or merge across employers/ATS providers.

### Pricing

**$1.00 per 1,000 delivered groups**, including prospect groups ($0.001 per row),
with no separate start fee. Failed boards, empty results and the summary have no
result fee. Each delivered dataset row is one billable group, including all of
its location and posting variants.

Use Apify's maximum charge setting to cap result spend. The Actor checks the
remaining affordable row count before each write and reports undelivered groups.

### Source contract

- [Greenhouse Job Board API](https://docs.greenhouse.io/job-board.html): public
  GET endpoint, job/post IDs, null prospect IDs and source fields.
- [Multiple posts for one job](https://support.greenhouse.io/hc/en-us/articles/217267766-Create-multiple-job-posts-for-a-single-job): different locations, languages and titles.
- [Requisitions and openings](https://support.greenhouse.io/hc/en-us/articles/360037953951-Requisition-and-openings): a job is not a headcount measure.
- [Apify event pricing](https://docs.apify.com/actors/monetize/pay-per-event): native dataset-item billing.

Independent tool; not affiliated with Greenhouse. Source availability and fields
can change. For a defect report, include the run ID, board token and sanitized
`OUTPUT` summary; never include credentials or applicant data.

# Actor input Schema

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

1–10 tokens from public Greenhouse board URLs. For job-boards.greenhouse.io/stripe, enter stripe. Duplicate tokens are checked once. URLs and other ATS providers are unsupported.

## `maxGroups` (type: `integer`):

Maximum delivered job/prospect groups across all boards. Remaining results and unvisited boards are marked incomplete in OUTPUT.

## `maxPostingsPerBoard` (type: `integer`):

Reject a board above this size instead of grouping a truncated list. The rejected board produces no paid groups.

## `requestTimeoutSeconds` (type: `integer`):

Per-attempt timeout. Temporary errors receive at most two retries.

## `maxRunSeconds` (type: `integer`):

Fetching and new writes stop at this work budget. Allow at least 120 additional seconds in the platform timeout for bounded storage calls and shutdown.

## Actor input object example

```json
{
  "boards": [
    "greenhouse"
  ],
  "maxGroups": 1000,
  "maxPostingsPerBoard": 5000,
  "requestTimeoutSeconds": 15,
  "maxRunSeconds": 120
}
```

# Actor output Schema

## `groups` (type: `string`):

No description

## `summary` (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"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("natstandridge/greenhouse-job-groups").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"] }

# Run the Actor and wait for it to finish
run = client.actor("natstandridge/greenhouse-job-groups").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"
  ]
}' |
apify call natstandridge/greenhouse-job-groups --silent --output-dataset

```

## MCP server setup

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

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/lPj21GS6WkOBcqbC4/builds/ZL4DYfEXzpoZ4bC1V/openapi.json
