# Greenhouse Hiring Analytics (`vixendata/greenhouse-hiring-analytics`) Actor

Analyze public Greenhouse job boards with normalized listings, department and location summaries, keyword signals, and hiring trends.

- **URL**: https://apify.com/vixendata/greenhouse-hiring-analytics.md
- **Developed by:** [Vixendata](https://apify.com/vixendata) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 public job posts

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 Hiring Analytics

Turn a list of known Greenhouse job boards into clean job-post exports and board-level hiring snapshots. Built for company research, recruiting operations and labor-market analysis where you need counts by department, location and your own role keywords without assembling them manually.

### What you get

- One CSV-friendly dataset row per public job post, deduplicated within each board
- Board token, company name (when supplied), title, location, departments, offices, job URL, post/requisition IDs and source timestamps
- Optional readable job descriptions
- Per-board department/location counts and overlapping title-keyword role buckets
- Counts of currently-open exported posts by first-publication month and within a chosen lookback
- Explicit completeness, cap, error and unknown-date indicators so partial data stays visible

This is a current snapshot. It cannot measure total historical posting frequency, jobs filled, hiring growth or removals from one run. Save repeated snapshots yourself for longitudinal research; a missing post alone is not proof of closure. A post is not necessarily a distinct role, requisition or hire, and prospect/talent-pool posts may appear.

### Quick start

Use a known board token, for example `greenhouse`, `figma` or `airbnb`. Supported URLs include `https://job-boards.greenhouse.io/figma` and `https://boards.greenhouse.io/figma/jobs/123`. Company career-domain URLs, embedded board URLs and EU-hosted boards are not supported. The Actor never requests input URLs directly: it extracts a validated token and uses only the fixed public Greenhouse API host.

```json
{
  "boards": ["greenhouse", "figma", "airbnb"],
  "maxJobsPerBoard": 500,
  "maxTotalJobs": 2000,
  "includeDescriptions": false,
  "lookbackDays": 30,
  "roleBuckets": {
    "management": ["manager", "director"],
    "engineering": ["engineer", "developer"]
  }
}
```

The board list is required in the input form. The code fallback for empty input is the Greenhouse company's public board.

### Where to find results

Open **Dataset** for individual jobs and export as CSV, Excel or JSON. Open the run's **Key-value store → SUMMARY** for all board-level analytics and coverage. The same report is also saved as `OUTPUT`.

Each summary contains `reportedTotalPosts`, `receivedPosts`, `uniqueReceivedPosts`, `exportedPosts`, `completeSnapshot`, `status`, counts, date coverage and scope notes. Status is `complete`, `partial`, `failed`, `blocked` or `skipped`. `completeSnapshot` requires matching API total and raw count, valid unique IDs, and no output cap. Unknown totals, duplicates and invalid rows prevent a completeness claim. Successful empty boards can be complete. Failed and skipped boards never receive a zero-job business interpretation.

Analytics count only exported posts. A board that hits a cap has partial statistics, even though the API's reported total is retained. Departments and role buckets may overlap, so those counts may sum above the number of posts. Role buckets use case-insensitive whole-word/phrase matches in titles; they are buyer-defined heuristics, not verified seniority, employment level or a validated frontline/management classification. No buckets are assumed by default.

`firstPublishedAt` is the source's first-publication timestamp when available. `updatedAt` is kept separately and is never used as a new-job date. Missing or invalid dates stay unknown; future dates are excluded from recent-publication counts. Publication-month counts concern currently-open exported posts only and include a separate future-date warning count. `observedAt` is the per-board processing observation time in UTC. `isProspectPost` is true only when the source explicitly returns a null internal job ID, false for a supplied non-null ID and null when absent.

### Limits and access behavior

- 1–20 boards per run, sequential requests; duplicate tokens are removed
- 1–2,000 exported posts per board; 1–10,000 exported posts per run
- One jobs-list request per board with `content=true` to obtain department/office data, even when descriptions are not exported
- Full list responses are downloaded before output caps are applied; boards with more than 16 MiB of response data are stopped
- A 100-second collection budget, 15-second socket timeout, at least one second between requests and any stricter robots delay/rate
- Reads robots.txt first; unavailable or disallowing robots policy blocks collection
- Redirects and other HTTP errors are not followed/retried; HTTP 401, 403 or 429 halts all remaining boards on the API host; no authentication, proxies, stealth or CAPTCHA handling
- Each description is capped at 50,000 characters and marked when truncated

The Actor makes public GET requests only. It does not fetch applicants, resumes, application questions or private recruiting data, and does not submit applications. Optional descriptions are public employer-provided text and may contain ordinary contact information as part of that text. Unknown or removed boards commonly return 404. Find the correct board token on the company's public careers page.

Partial runs retain useful successful boards with clear errors for other boards. If every board fails, is blocked or is skipped, the run fails after storing the error summary. Keep the platform run timeout at least 120 seconds. A platform kill before output is saved can prevent final result storage.

### Source and responsible use

Uses the documented [Greenhouse Job Board API](https://docs.greenhouse.io/job-board.html), whose public GET endpoints do not require authentication. Greenhouse is a third-party service; this Actor is independently maintained and is not affiliated with Greenhouse. Public accessibility does not grant unrestricted reuse: follow applicable site terms, privacy requirements and your intended-use obligations. Source fields and availability can change.

### Validation

Unit tests cover token validation and host boundaries, input bounds, deduplication, caps and partial counts, date uncertainty, keyword overlap, description decoding, robots rules, redirects and no-retry behavior. Run `python -m unittest -v` from the project folder. See `LIVE_CHECK.json` in the source package for the dated three-board sample and environment note. Live board counts change over time. Deployment verified on Apify on October 4, 2026: build `0.0.1` (`df7OSN3Uf7e9nprAq`) succeeded. Direct-network cloud run `egXvuHrPUoKJqZ3fd` succeeded in approximately 9 seconds and exported all 18 Greenhouse posts, with complete coverage, no cap, no duplicates and no invalid rows. Its input used `boards: ["greenhouse"]`, per-board and total caps of 50, descriptions disabled, 30-day lookback, and management/frontline keyword examples. Both dataset records and the SUMMARY key-value report were inspected. This cloud smoke covered one board; the separate local three-board API smoke exported 334 posts (Greenhouse 18, Figma 162, Airbnb 154) using the workspace outbound gateway. These are dated verification counts, not a promise about future counts.

The source ZIP is a backup of the implementation, tests and validation evidence. The Actor is already deployed; no ZIP upload is needed to run it.

# Actor input Schema

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

1–20 known public boards, for example figma or https://job-boards.greenhouse.io/greenhouse. Custom company career URLs are not supported. Duplicate boards are removed.

## `maxJobsPerBoard` (type: `integer`):

1–2000. Full API response is downloaded, but output and analytics are capped. Capped boards are explicitly partial.

## `maxTotalJobs` (type: `integer`):

1–10000 total output cap. Later boards are skipped when reached.

## `includeDescriptions` (type: `boolean`):

Optional public description text capped at 50000 characters per post. No applicant records or application forms are collected.

## `lookbackDays` (type: `integer`):

Counts exported currently-open posts with first_published in this lookback. This is not historical posting frequency or hiring velocity.

## `roleBuckets` (type: `object`):

Optional labels mapped to keyword arrays, e.g. {"management":\["manager","director"],"frontline":\["associate","representative"]}. Whole-word/phrase, case-insensitive title matches; overlapping and heuristic, not verified seniority. At most 12 buckets with 30 keywords each.

## Actor input object example

```json
{
  "boards": [
    "greenhouse"
  ],
  "maxJobsPerBoard": 500,
  "maxTotalJobs": 2000,
  "includeDescriptions": false,
  "lookbackDays": 30,
  "roleBuckets": {}
}
```

# 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 = {
    "boards": [
        "greenhouse"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("vixendata/greenhouse-hiring-analytics").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("vixendata/greenhouse-hiring-analytics").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 vixendata/greenhouse-hiring-analytics --silent --output-dataset

```

## MCP server setup

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

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/FDAvSPWlk3otJ3p36/builds/nFqlS1yrivcyMHzTU/openapi.json
