# Greenhouse Jobs Scraper & Job Board API (`steadybyte/greenhouse-jobs`) Actor

Every open job on any Greenhouse job board, via Greenhouse's public Job Board API: departments, offices, location, remote flag, posted date, pay ranges, description. Filters plus a new-jobs-only monitoring mode.

- **URL**: https://apify.com/steadybyte/greenhouse-jobs.md
- **Developed by:** [Steadybyte](https://apify.com/steadybyte) (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 jobs

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 Scraper: Greenhouse Job Board API to JSON, CSV & Excel

**Get every open job from any company's Greenhouse job board in one clean dataset.** Paste Greenhouse board tokens, `job-boards.greenhouse.io` links, careers page URLs or company names. Filter by keyword, department, location, remote and posting date. Turn on **monitoring mode** to get only the jobs that are new since your last run.

Data comes from **Greenhouse's official, public [Job Board API](https://developers.greenhouse.io/job-board.html)** (`boards-api.greenhouse.io`), the same API that powers employers' own Greenhouse careers pages. No login, no API key, no browser, no proxy.

Need Lever, Workday, Ashby, Workable, SmartRecruiters or another ATS too? Use **[ATS Jobs Feed](https://apify.com/steadybyte/ats-jobs-feed)**, which reads 15 ATS (Greenhouse included) into the same schema.

### What does Greenhouse Jobs Scraper do?

- **Reads any public Greenhouse job board** with a single listing request per company, so even boards with 900+ jobs finish in seconds.
- **Flexible input**, one company per line:
  - a Greenhouse board token: `gitlab`, `airbnb`, `figma` (also `greenhouse:gitlab`)
  - a Greenhouse board URL: `https://job-boards.greenhouse.io/gitlab`, `https://boards.greenhouse.io/airbnb`, `job-boards.eu.greenhouse.io/<token>`
  - **one job's URL** returns only that job (and you pay for one job): `https://job-boards.greenhouse.io/gitlab/jobs/123`, or a careers-page link with `?gh_jid=123`
  - a Job Board API or embed URL: `https://boards-api.greenhouse.io/v1/boards/discord/jobs`, `…/embed/job_board?for=gitlab`
  - a company careers page URL that embeds or links its Greenhouse board, e.g. `https://www.figma.com/careers/`
  - a company name: `Scale AI` is tried as `scaleai` and `scale-ai`
- **Everything Greenhouse publishes for a job:** title, every department, every office, location, country code, remote flag, workplace type, first-published and updated dates, job URL, **pay ranges** (with `pay_transparency`), requisition ID, language, application deadline, the employer's published custom fields, and the full description as clean text (HTML optional).
- **Filters:** keywords (title and departments, optionally the description), "title must contain", exclude words, **departments**, locations or countries, remote only, posted within N days.
- **Monitoring mode** (`onlyNewJobs`) remembers delivered jobs per *Monitor ID*. Schedule it daily for a "new Greenhouse jobs" feed and pay only for new jobs.
- **Never fails on a bad company.** A token that doesn't exist, or a company that isn't on Greenhouse, is skipped with a clear warning and listed in the run summary; the other companies are still delivered and the run succeeds.
- **Pay only for delivered jobs.** Jobs removed by filters, duplicates and jobs already seen in monitoring mode are free.

### Use cases

- **Job seekers and job boards:** a daily feed of, say, remote engineering or data roles at a hand-picked list of Greenhouse companies.
- **Sales and recruiting teams:** hiring signals. A company posting five new SDR roles is building a sales team; track which departments are growing.
- **Compensation research:** Greenhouse pay ranges by role, location and company, where employers publish them.
- **Market and competitor research:** open roles, locations and hiring pace across competitors.
- **AI agents and automations:** call it from the Apify API, MCP, Make, n8n or Zapier and send new jobs to Slack, Google Sheets or a database.

### How to find a company's Greenhouse board token

The token is the part after `greenhouse.io/` in the company's job-board URL: `https://job-boards.greenhouse.io/gitlab` → `gitlab`. If the company hosts jobs on its own site, open any job; the link often contains `gh_jid=` or `greenhouse.io/<token>`. You can also just paste the careers page URL and let the Actor find the token.

### Input example

```json
{
    "companies": ["gitlab", "https://job-boards.greenhouse.io/airbnb", "greenhouse:figma", "discord"],
    "departments": ["Engineering"],
    "locations": ["Remote", "United States"],
    "postedWithinDays": 30,
    "onlyNewJobs": true,
    "monitorId": "remote-engineering-feed",
    "includeDescription": true
}
```

Every field is explained in the **Input** tab.

### Output example

One item per job (description and pay note shortened). Export as JSON, CSV, Excel, XML or HTML, or read it through the API.

```json
{
    "id": "greenhouse:airbnb:8231416",
    "ats": "greenhouse",
    "companySlug": "airbnb",
    "companyName": "Airbnb",
    "jobId": "8231416",
    "title": "Associate Principal, Procurement Innovation Supplier Programs",
    "department": "Financial Planning and Analysis",
    "departments": ["Financial Planning and Analysis"],
    "team": null,
    "location": "United States",
    "locations": ["United States"],
    "offices": ["United States"],
    "country": "US",
    "isRemote": true,
    "workplaceType": "remote",
    "employmentType": null,
    "postedAt": "2026-09-24T22:17:39.000Z",
    "updatedAt": "2026-09-30T00:00:54.000Z",
    "url": "https://careers.airbnb.com/positions/8231416?gh_jid=8231416",
    "applyUrl": "https://careers.airbnb.com/positions/8231416?gh_jid=8231416",
    "salaryMin": 102000,
    "salaryMax": 120000,
    "salaryCurrency": "USD",
    "salaryPeriod": null,
    "salaryText": "Pay Range: USD 102,000 – 120,000",
    "payRanges": [
        {
            "title": "Pay Range",
            "min": 102000,
            "max": 120000,
            "currency": "USD",
            "note": "How We'll Take Care of You: …"
        }
    ],
    "requisitionId": "ONE",
    "internalJobId": "3562002",
    "language": "en",
    "applicationDeadline": null,
    "metadata": [
        { "name": "Is this job part of ACC?", "value": false },
        { "name": "Workplace Type", "value": "Remote" }
    ],
    "descriptionText": "Airbnb was born in 2007 when two hosts welcomed three guests to their San Francisco home …",
    "descriptionHtml": null,
    "isNew": null,
    "sourceInput": "airbnb",
    "scrapedAt": "2026-10-31T07:07:08.196Z"
}
```

- **Same schema as [ATS Jobs Feed](https://apify.com/steadybyte/ats-jobs-feed)**, plus Greenhouse-only fields at the end (`departments`, `offices`, `payRanges`, `requisitionId`, `internalJobId`, `language`, `applicationDeadline`, `metadata`). Job `id`s are the same in both (`greenhouse:<token>:<job id>`), so you can merge the two datasets and dedupe.
- `country` is an ISO 3166-1 alpha-2 code (`US`, `GB`, `IN`) when every location names the same country, otherwise `null`. A major city on its own counts (`London` → `GB`, `Berlin - Hybrid` → `DE`); cities that share a name with a well-known place elsewhere (Vancouver, Cambridge, Portland) don't.
- `workplaceType` and `isRemote` use the employer's own published "Workplace Type" / "Location Type" field when there is one (Airbnb publishes it). Otherwise a job is remote when a location says "Remote" or the title has a remote tag ("Engineer (Remote)").
- `salaryMin` / `salaryMax` / `salaryCurrency` are the lowest and highest amounts in the first pay range's currency; `salaryText` lists every range; `payRanges` keeps each range with its title and note. Greenhouse doesn't state the pay period, so `salaryPeriod` is `HOUR` for ranges titled hourly and otherwise `null`.
- `metadata` holds only the custom fields the employer chose to make public on its job board.

A per-company **run summary** is saved as `RUN_SUMMARY` in the run's key-value store: board used, jobs found and delivered, status. `ok` is `false` when any company couldn't be read, and `failedCompanies` lists each one with the reason, so automations can tell broken input from "no new jobs".

### How much does it cost to scrape Greenhouse jobs?

Pricing is **pay per event: $1.50 per 1,000 jobs delivered** ($0.0015 per job), with no start fee. You pay only for jobs written to the dataset. Jobs removed by your filters, duplicates and jobs skipped by monitoring mode are free. Set **Max jobs in total** or a maximum cost per run to cap spend; the run stops exactly at the cap.

Example: tracking 50 Greenhouse companies daily in monitoring mode, at around 100 new matching jobs a day, costs about $0.15 a day.

### How keyword, department and location matching works

- **Keywords** match the title and every department (turn on **Also match keywords in description** to search descriptions too). Single words match whole words; words of 4+ letters also match plain endings (`engineer` matches "Engineers" and "Engineering", but `java` doesn't match "JavaScript" and `sales` doesn't match "Salesforce"). Multi-word terms match as an exact phrase (`Ruby on Rails`). Wrap a word in double quotes for an exact match (`"engineer"`).
- **Departments** use the same word rules against each of the job's Greenhouse departments. `Engineering` keeps a job filed under "Engineering" or "Platform Engineering".
- **Title must contain** checks the title only; **Exclude title keywords** drops titles with those words (`intern` drops "Internship" but not "Internal").
- **Locations** match the job's location, offices or country. Common spellings are the same place (US / USA / United States, UK / United Kingdom, NY / New York, Bangalore / Bengaluru). Two-letter entries that are country codes mean the country (`CA` = Canada); for a US state use its name or `US-CA`. A country also finds jobs listed only by a major city in it (`United Kingdom` finds "London", `Germany` finds "Munich" and "10245 Berlin"); for smaller towns add the town name too.

### FAQ

**Is there an official Greenhouse jobs API?**
Yes. Greenhouse's Job Board API returns a company's published jobs, departments and offices as JSON, and its GET endpoints need no authentication. This Actor calls that API for you, adds filters, monitoring, clean text and a consistent schema, and exports to CSV or Excel.

**Can I get jobs from every company on Greenhouse at once?**
No. Greenhouse has no public directory of boards; you give the companies (tokens, URLs or names). The Actor reads as many as you list in one run.

**Do I need a Greenhouse account or API key?**
No. Only public job-board data is read.

**Why was a company skipped?**
Its token doesn't exist on Greenhouse, or the company uses another ATS. The run log and `RUN_SUMMARY.failedCompanies` say which and why. If the careers page uses Lever, Workday, Ashby or another supported ATS, the log names it; use [ATS Jobs Feed](https://apify.com/steadybyte/ats-jobs-feed) for those.

**Does it include salaries?**
Where the employer publishes pay ranges on Greenhouse. In a local test on 31 Oct 2026, 401 of 621 jobs from GitLab, Airbnb, Figma and Discord had a pay range; boards like Stripe's publish none.

**How do I get only new jobs every day?**
Turn on **Only new jobs (monitoring mode)**, pick a Monitor ID, and add a daily schedule in Apify. The first run returns all matching jobs; later runs return only jobs not delivered before. The list of delivered jobs is kept in a key-value store named `steadybyte-greenhouse-jobs-state` in your Apify account (delete it to start over). Don't start two runs with the same Monitor ID at the same time: the one that finishes last overwrites the other's list, so some jobs may be delivered again.

### Limitations

- Only **published, public** job posts are returned, exactly as Greenhouse's Job Board API exposes them. Internal job boards are not included. Prospect posts ("General Application") are included when the employer publishes them; their `internalJobId` is `null`.
- Greenhouse's Job Board API has no employment type field, so `employmentType` is always `null`; check the title or description.
- Careers page detection reads that one page's HTML for a Greenhouse link or embed. Pages that load the board only via JavaScript are not detected (for example `stripe.com/jobs` and `discord.com/careers`); paste the board token (`stripe`, `discord`) instead.
- A company name is tried as a board token; an unrelated company can own that token. The run summary shows which board was used.
- Application questions (the `questions` parameter) are not included: they need one extra request per job and describe the application form, not the job.

### Is it legal to scrape Greenhouse job boards?

This Actor doesn't scrape web pages for job data. It calls Greenhouse's documented public Job Board API, and Greenhouse's documentation states that job board data is publicly available and that its GET endpoints need no authentication. It never uses the application submission endpoint. Requests are modest (one listing request and one company-name request per board, or one job request for a job URL; three boards at a time, with back-off when the server asks to slow down).

The output contains job postings, not personal data about candidates. You are responsible for using the data in line with applicable laws and each employer's terms.

### Related Actors

- **[ATS Jobs Feed](https://apify.com/steadybyte/ats-jobs-feed)**: the same schema for 15 ATS: Greenhouse, Lever, Workday, Ashby, Workable, Recruitee, SmartRecruiters, BambooHR, Personio, Teamtailor, Breezy HR, Rippling, Pinpoint, JazzHR and Gem.

### Support

Found a Greenhouse board that doesn't work? Open an issue on the **Issues** tab with the company's careers page URL. Built and maintained by **Steadybyte**.

# Changelog

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

# Actor input Schema

## `companies` (type: `array`):

One company per line. Use any of: a Greenhouse board token (gitlab), the same with a prefix (greenhouse:gitlab), a Greenhouse board URL (https://job-boards.greenhouse.io/gitlab, https://boards.greenhouse.io/airbnb, https://job-boards.eu.greenhouse.io/<token>), one job's URL (https://job-boards.greenhouse.io/gitlab/jobs/123 or a careers link with ?gh_jid=123: only that job is returned and charged), a Job Board API or embed URL (https://boards-api.greenhouse.io/v1/boards/gitlab/jobs, ...greenhouse.io/embed/job_board?for=gitlab), a company careers page URL that embeds or links its Greenhouse board, or a company name (tried as a board token: 'Scale AI' is tried as scaleai and scale-ai). Repeated lines are read once. A company that isn't on Greenhouse is skipped with a warning; the run still succeeds. For Lever, Workday, Ashby and 12 other ATS use ATS Jobs Feed (steadybyte/ats-jobs-feed).

## `keywords` (type: `array`):

Keep jobs whose title or any of its departments (plus description, if enabled below) contains at least one of these. Single words match whole words; words of 4+ letters also match plain endings ('engineer' matches 'Engineers' and 'Engineering', but 'java' does not match 'JavaScript' and 'sales' does not match 'Salesforce'), short words match whole words only ('go' does not match 'Google'). Multi-word terms match as an exact phrase, case-insensitive, with spaces or hyphens between words: 'Ruby on Rails' matches 'Ruby on Rails' and 'ruby-on-rails' but not 'payment rails' or 'Ruby and Rails'. Wrap a term in double quotes to match the exact word only: '"engineer"' does not match 'Engineering'. Leave empty to keep all jobs.

## `keywordsInDescription` (type: `boolean`):

Also match Keywords against the full job description (e.g. find Rails roles whose title only says 'Software Engineer'). Descriptions often list many technologies, so prefer phrases like 'Ruby on Rails' here, or combine with Title must contain to keep results relevant.

## `titleKeywords` (type: `array`):

Optional. Keep only jobs whose title contains at least one of these (same word/phrase rules as Keywords). Applied in addition to Keywords: e.g. Keywords \['Ruby on Rails'] with description search plus Title must contain \['engineer','developer'] returns engineering roles that mention Rails. Leave empty to not restrict titles.

## `excludeKeywords` (type: `array`):

Drop jobs whose title contains any of these words, e.g. intern, senior. Whole words plus plain endings: 'intern' drops 'Intern', 'Interns' and 'Internship' but keeps 'Internal' and 'International'.

## `departments` (type: `array`):

Keep only jobs filed under a Greenhouse department that matches at least one of these, e.g. Engineering, Sales, Marketing (same word/phrase rules as Keywords: 'engineer' matches 'Engineering'). A job with several departments matches if any of them does. Leave empty to keep all departments.

## `locations` (type: `array`):

Keep jobs whose location, any other location, or country matches at least one of these, e.g. India, London, US, Remote. Common spellings are treated as the same place: US / USA / United States, UK / United Kingdom, NY / New York / NYC, Bengaluru / Bangalore, Munich / München. Two-letter entries that are country codes mean the country (CA = Canada, IN = India); for a US state use its name (California) or US-CA. 'Georgia' is the country; use US-GA for the state. 'US' also finds US jobs written 'City, State'. A country also finds jobs listed only by a major city in it ('United Kingdom' finds 'London', 'Germany' finds 'Munich').

## `remoteOnly` (type: `boolean`):

Keep only remote jobs: the employer's own 'Workplace Type' / 'Location Type' field when they publish one on Greenhouse, otherwise 'Remote' in any of the job's locations or a remote tag in the title, e.g. 'Engineer (Remote)'.

## `postedWithinDays` (type: `integer`):

Keep only jobs first published in the last N days. 0 = no limit. Jobs without a publish date are dropped when this is set.

## `onlyNewJobs` (type: `boolean`):

Remember which jobs were already delivered and return only jobs not seen in earlier runs with the same Monitor ID. The first run returns all matching jobs. Schedule the Actor daily to get a feed of new openings.

## `monitorId` (type: `string`):

Name for the list of already-delivered jobs used by monitoring mode. Use a different ID for each independent feed. IDs are exact: 'feed 1' and 'feed_1' are different feeds. Must be text.

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

Add the full job description as clean plain text.

## `includeDescriptionHtml` (type: `boolean`):

Also add the original description HTML.

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

Stop after this many delivered jobs per board. 0 = no limit. Greenhouse lists a whole board in one request, so this limits what is delivered and charged, not what is downloaded.

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

Stop after this many delivered jobs in total. 0 = no limit. You are charged only for delivered jobs, never more than this number.

## Actor input object example

```json
{
  "companies": [
    "gitlab",
    "https://job-boards.greenhouse.io/airbnb",
    "greenhouse:figma",
    "discord"
  ],
  "keywords": [],
  "keywordsInDescription": false,
  "titleKeywords": [],
  "excludeKeywords": [],
  "departments": [],
  "locations": [],
  "remoteOnly": false,
  "postedWithinDays": 0,
  "onlyNewJobs": false,
  "monitorId": "default",
  "includeDescription": true,
  "includeDescriptionHtml": false,
  "maxJobsPerCompany": 0,
  "maxJobs": 0
}
```

# Actor output Schema

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

Every field of every delivered job.

## `summary` (type: `string`):

Per-company status, plus failedCompanies with the reason each company could not be read (ok is false when any could not be read).

# 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 = {
    "companies": [
        "gitlab",
        "https://job-boards.greenhouse.io/airbnb",
        "greenhouse:figma",
        "discord"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("steadybyte/greenhouse-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 = { "companies": [
        "gitlab",
        "https://job-boards.greenhouse.io/airbnb",
        "greenhouse:figma",
        "discord",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("steadybyte/greenhouse-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 '{
  "companies": [
    "gitlab",
    "https://job-boards.greenhouse.io/airbnb",
    "greenhouse:figma",
    "discord"
  ]
}' |
apify call steadybyte/greenhouse-jobs --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,steadybyte/greenhouse-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/bmWJKGpNrQGLxS1XG/builds/xCzxHKm2dXV3ha7hZ/openapi.json
