# Greenhouse Jobs — Full Export & Changes (`andrey_garanin/greenhouse-job-feed`) Actor

Export public Greenhouse jobs and track new, edited and absent postings across complete snapshots. No API key, browser or proxy needed.

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

## Pricing

from $30.00 / 1,000 complete board scans

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 Jobs — Full Export & Changes

Export public Greenhouse job boards into an Apify dataset. Download the current jobs, run a recurring change feed, and keep posting IDs, source timestamps and coverage visible.

**No Greenhouse API key, browser, proxy, separate website or external subscription is required.** The Actor runs inside Apify.

### What you get

- Complete current job export from one or more company boards.
- New, meaningfully edited, reappeared and observed-absent postings between snapshots.
- Readable descriptions, departments, offices, source location, native posting ID and source timestamps.
- A visible completeness summary for every exported board.
- CSV, Excel and JSON export through Apify's native dataset interface after deployment.
- Recurring execution through Apify Tasks/Schedules; API and Apify integrations are supplied by the platform.

### Quick start in Apify

1. Enter a company board token or public Greenhouse URL, for example `figma` or `https://job-boards.greenhouse.io/figma`.
2. Keep **All current jobs plus changes** for a complete export. Choose **Changes only** for recurring feeds; the first successful run still exports a baseline.
3. Click **Start**. In Output, inspect the job records and board summaries, then download the dataset.

```json
{
  "boards": ["figma"],
  "outputMode": "changes",
  "trackChanges": true,
  "historyName": "my-company-watchlist",
  "includeDescriptions": true,
  "missingConfirmations": 2
}
```

To refresh daily, save the input as an Apify Task, schedule it daily and reuse the same history name. **Do not overlap runs using the same history and filters.** For independent pipelines, use different history names.

### Record types and events

| Record | Meaning |
|---|---|
| `job` / `baseline` | Present in the first snapshot. First observed is not first published. |
| `job` / `new` | Native posting ID was not present in the retained history. |
| `job` / `updated` | Title, location, description or another tracked content field changed. `changed_fields` explains which. Source timestamp churn alone is ignored. |
| `job` / `reappeared` | Previously absent native posting ID returned. This does not prove a newly opened position. |
| `job` / `unchanged` | Current posting, included in All mode. |
| `absence` / `observed_absent` | Missing from the configured number of complete successful snapshots; default two. **Not proof of filled, canceled or closed employment.** |
| `board_summary` | Successful source count, exported count, baseline and event counts. |
| `board_error` | Source, size, schema or history problem; board not exported or scan-charged. |

Use `record_type == "job"` and `matches_filters == true` for current matching jobs. A changed job that leaves your title/location filter is still emitted once with `matches_filters:false`, so a downstream feed can remove it. Absence records are separate.

`OUTPUT` in the default key-value store holds the final run summary, including history-commit results. A dataset summary is delivered before the state commit, so its `history_updated` is `null`; read `OUTPUT` or its `SCAN-*` record for the final value. Partial failures remain visible even if other boards succeed.

### Pricing

**$0.03 per complete successfully exported company board**, including all selected job rows, changes and its summary, up to the configured limit. Ten boards cost $0.30; five boards daily for 30 days cost $4.50. This is not a per-job price or a monthly subscription.

Baseline, unchanged and empty successful scans are billable. Invalid boards, incomplete responses and guard-held boards do not trigger the `board-scan` event. An Apify synthetic start fee of $0.00005 per GB-start (minimum one event per run) is additional. Underlying platform usage is included in this configuration. The Store Pricing panel is authoritative.

Job rows are included in the scan price, without paid per-dataset-row billing. The publisher bears underlying run costs. The Actor checks the remaining scan budget before fetching another board and stops when no complete scan fits.

### Completeness and limits

- Only the official, public **Greenhouse Job Board GET API** is used. No application submissions, candidate records, Harvest credentials, paywalled data or anti-bot bypass.
- Supports tokens and HTTPS `boards.greenhouse.io` / `job-boards.greenhouse.io` URLs. Custom career domains, EU-specific hostnames and other ATS are not auto-detected in this version.
- Maximum 20 boards, 5,000 source jobs per board; default safety limit 2,000. Maximum response 16 MB; bounded retries and timeouts. Exceeding a limit holds the **whole board**, not a truncated success.
- API `meta.total` must equal the received unique posting count. Native `id` is the posting ID; `internal_job_id` is kept separately. Prospect posts can have a null internal job ID.
- A formerly nonempty board becoming empty, or a board of at least ten jobs dropping by over 60%, is held by default. Independently verify the source before enabling `allowLargeDrops`.
- Observed absence requires successful snapshots, not a time interval: two daily scans usually mean two days; two immediate scans do not. History is not a distributed database: avoid overlapping runs. Optimistic checks detect many conflicts but are not a guaranteed cross-run lock.
- Absence tombstones expire after 30 days. A later return may be classified as new after that retention window.
- There is no inferred salary, seniority, remote eligibility, employee count or hiring score. Source location text is preserved. Descriptions can contain advertised salary text but are not parsed into a claimed salary field.
- Filters are exact case-insensitive substrings, not AI judgments. No language model receives the content. Source data can itself be stale or inaccurate; API completeness does not verify hiring intent.

### Storage and privacy

The Actor uses limited Apify permissions. With change tracking enabled, normalized public job metadata and description hashes are saved in a named key-value store under the running customer's Apify account. Full descriptions are exported with current jobs when requested, but are not retained in history; absence rows therefore do not include the old full description. State is partitioned by Actor/history/filter configuration and board. Input, output and retained snapshots follow that customer's Apify storage/access policies. Delete the named store to discard history; the next run becomes a new baseline.

The code makes outbound requests only to the official Greenhouse board API and the Apify SDK/platform. It does not send data to the publisher's own server or an AI provider. It does not collect applications, candidate profiles or contact lists. Avoid adding confidential information to job-board URLs or history names.

### Troubleshooting

**Empty results:** inspect `board_error` and `OUTPUT`, filters and the actual board token. Zero matching job rows with a complete board summary can mean no jobs match the filter; it is not necessarily a failure.

**No changes on the second run:** this is expected if the source content is unchanged. A complete unchanged scan still produces a visible summary and one scan event.

**Missing records / limit error:** increase the safety cap only within supported limits. There is no hidden partial export. Guard-held scans retain the previous baseline.

**A run failed after some output:** inspect saved results and the `SCAN-*` records before retrying. Completed scan charges are not automatically reversed. The engine does not advance a board's history until its export and scan charge complete; a failed state commit can cause repeated records on a later run.

**Support:** use the Actor's Apify Issues tab, include the run URL and expected behavior. Never include API tokens. The publisher still needs to handle genuine bugs; built-in hosting and billing do not make maintenance disappear.

Independent software, not affiliated with Greenhouse or Apify.

# Changelog

This Actor's version history is a separate document: https://apify.com/andrey_garanin/greenhouse-job-feed/changelog.md

# Actor input Schema

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

1–20 tokens or Greenhouse board URLs, e.g. figma or https://job-boards.greenhouse.io/figma. Custom career domains and other ATS are not supported.

## `outputMode` (type: `string`):

Always scans the full board. 'Changes' includes new/updated/reappeared and observed-absent events. Summary rows are always included.

## `trackChanges` (type: `boolean`):

Keeps state in a named Apify key-value store owned by your account. Do not overlap runs using the same history name.

## `historyName` (type: `string`):

Use a different name for independent monitoring pipelines. Reuse the same name for scheduled runs. Maximum 50 letters, digits, underscores or hyphens.

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

Description changes are detected even when descriptions are omitted from exported job rows.

## `titleContains` (type: `string`):

Optional case-insensitive substring. No AI interpretation. Filters output only; the complete board is still checked.

## `locationContains` (type: `string`):

Optional case-insensitive substring of the source location text. Does not infer remote eligibility.

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

If a board exceeds this limit, the board is skipped without a scan charge, no truncated export or removal events, and no history advancement.

## `missingConfirmations` (type: `integer`):

Default 2: a job must be absent from two complete successful snapshots. An absent posting is not proof the position was filled.

## `allowLargeDrops` (type: `boolean`):

Advanced: bypass the hold for a formerly nonempty board going empty, or a board of 10+ jobs losing over 60%. Leave off unless you have verified the source. This still does not infer a filled position.

## Actor input object example

```json
{
  "boards": [
    "figma"
  ],
  "outputMode": "all",
  "trackChanges": true,
  "historyName": "default",
  "includeDescriptions": true,
  "titleContains": "",
  "locationContains": "",
  "maxJobsPerBoard": 2000,
  "missingConfirmations": 2,
  "allowLargeDrops": false
}
```

# Actor output Schema

## `dataset` (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": [
        "figma"
    ]
};

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

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

```

## MCP server setup

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

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/HfTPiXnDdtk97QbXx/builds/Za7VC2fqhJ4aojs4D/openapi.json
