# Greenhouse Jobs Scraper (`boncross/greenhouse-jobs`) Actor

Scrape every open job from a Greenhouse careers board. Give the board URL and get the job title, employer, location, team, posting date, the published pay range and the full advert. Filter by title, location or remote working, and export to CSV, JSON or Excel.

- **URL**: https://apify.com/boncross/greenhouse-jobs.md
- **Developed by:** [BonCross API](https://apify.com/boncross) (community)
- **Categories:** Jobs, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.70 / 1,000 job-details

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

### What is Greenhouse Jobs Scraper?

Stripe's Greenhouse board had 654 open jobs when we measured it. Greenhouse Jobs Scraper gets you all of them, each with the full advert, the date it was first posted, the date it was last edited and the pay range where one is published. Sixteen boards read in full came to 3,801 jobs in 17 seconds.

Paste a board URL such as `job-boards.greenhouse.io/company` and that's the run. It suits anyone tracking hiring at the companies that recruit on Greenhouse, with Stripe, Airbnb, Figma and GitLab among them.

What you get that a quick scrape of the page doesn't give you:

- **Pay as numbers.** Minimum, maximum, currency and period wherever the employer publishes a range. That was 108 of Figma's 163 jobs.
- **Two dates per job.** `postedAt` is when the advert first went up and `updatedAt` is when it was last edited. They're often months apart, and telling them apart is how you spot a role that's been open since February.
- **The advert at no extra wait.** Greenhouse returns the full advert with the job list, so a whole board is a matter of seconds.
- **Your board filters travel with the link.** Filter by department or office on the board's own page, copy the URL, and the same filters apply here.

**BonCross Verified** means we tested this actor against the live site before release and kept the record:

- 26 of 26 live checks on its inputs and filters passed on 7 October 2026
- 5 boards run the same day, one made up: 88 jobs, 1 error row
- 8 of 8 sampled adverts matched the employers' own pages, text and link
- Charges matched the rows: 1 start, 4 boards, 88 jobs

### What data can you extract from a Greenhouse board?

| Field | Field |
|---|---|
| 💼 Job title | 🏢 Employer |
| 📍 Location | Team or department |
| 📄 Full advert text | Advert HTML, optional |
| Last edited date | Remote flag |
| 📅 Posted date | 🔗 Advert link |
| 💷 Salary from and to | Salary currency and period |
| Job id | Board name |
| Board URL | When the row was collected |
| 🏠 On-site, hybrid or remote, where published | |

### How to scrape a Greenhouse board

1. [Sign up for Apify](https://console.apify.com/sign-up) (free) and open [Greenhouse Jobs Scraper](https://apify.com/boncross/greenhouse-jobs).
2. Paste one or more board URLs. Both `job-boards.greenhouse.io/company` and `boards.greenhouse.io/company` work.
3. Add a keyword or title, location and work-type filters if you don't want everything.
4. Press Start. A 20-job run took 2 to 4 seconds.
5. Download the board's jobs as Excel, CSV or JSON.

If one of your URLs belongs to a different recruitment platform, you get a row saying so and the other boards are read as normal.

### ⬇️ Input

Board URLs are the only thing you have to give. Start URLs takes the same links in bulk, from a file or a Google Sheet.

"Max jobs per board" caps each board and "Max jobs for the whole run" caps the run. Leave both at 0 and you get every job.

**One board**

```json
{
  "boardUrls": ["https://job-boards.greenhouse.io/stripe"],
  "maxItemsPerBoard": 200
}
```

**Engineering roles only, several boards**

```json
{
  "boardUrls": [
    "https://job-boards.greenhouse.io/stripe",
    "https://job-boards.greenhouse.io/figma"
  ],
  "titleIncludes": ["engineer", "developer"],
  "maxItems": 500
}
```

**Hybrid or remote jobs only**

```json
{
  "boardUrls": ["https://job-boards.greenhouse.io/airbnb"],
  "workplaceTypes": ["Hybrid", "Remote"],
  "includeUnknownWorkplace": false
}
```

**This week's remote postings**

```json
{
  "boardUrls": ["https://job-boards.greenhouse.io/stripe"],
  "onlyRemote": true,
  "maxDaysOld": 7
}
```

**A daily check that returns only what's new on the board**

```json
{
  "boardUrls": ["https://job-boards.greenhouse.io/stripe"],
  "onlyNew": true,
  "dedupStoreName": "greenhouse-seen"
}
```

What the filters do on Greenhouse:

- **Keyword** keeps jobs whose title or advert contains the word, as the board's own search does. If your board link already has a search word in it, that one is kept and the keyword then matches titles only.
- **Work type** keeps jobs whose `workplaceType` is On-site, Hybrid or Remote. Greenhouse has no work-type field of its own, so it's stated only where the employer adds one. Airbnb does on every job. Stripe, Figma and GitLab don't. Jobs with no stated arrangement are kept unless you turn off "Keep jobs with no stated work type", and one whose location or title says remote counts as Remote.
- **Title must contain / must not contain** keep or drop by words in the title, ignoring case.
- **Location must contain** keeps a job only if its location has one of your words, for example "London".
- **Remote jobs only** keeps jobs the employer marked as remote. Jobs that say nothing about it are dropped, not assumed.
- **Posted within (days)** keeps jobs posted in the last N days. 0 keeps everything.
- **Include advert text** (on by default) and **Include advert HTML** (off) decide whether the advert arrives as plain text, original HTML or both. HTML makes rows much larger.

#### For developers

- Keys: `boardUrls`, `startUrls`, `maxItemsPerBoard`, `maxItems`, `keyword`, `titleIncludes`, `titleExcludes`, `locationIncludes`, `onlyRemote`, `workplaceTypes`, `includeUnknownWorkplace`, `maxDaysOld`, `includeDescription`, `includeDescriptionHtml`, `onlyNew`, `dedupStoreName`, `maxConcurrency`, `delayMs`.
- A link copied after filtering on the board's own page keeps its department, office and search word, matched the way the board matches them.
- `onlyNew` with a `dedupStoreName` skips jobs that an earlier run with the same store name returned. With no store name, nothing is remembered. Delivered jobs are saved to the store every minute.
- Boards are read one after another, about 2 seconds a board on average and 18 seconds for the largest in a 40-board sample.
- `maxConcurrency` (default 8) is the fastest level measured clean. Drop it only when a run reports refusals or errors. `delayMs` (default 0) adds a pause before each request.
- Rows are saved as they're collected. A run that reaches its timeout keeps what it delivered, and a run the platform restarts continues without delivering or charging any job twice.

### ⬆️ Output

One row per job. The Overview view shows title, employer, location, team, posting date and advert link.

```json
{
  "id": "8172510",
  "url": "https://stripe.com/jobs/search?gh_jid=8172510",
  "title": "Abuse Investigator",
  "company": "Stripe",
  "location": "Seattle, San Francisco, New York City",
  "department": "8611 Security Analytics",
  "isRemote": null,
  "postedAt": "2026-09-09T14:50:29.000Z",
  "updatedAt": "2026-09-25T20:45:00.000Z",
  "salaryMin": null,
  "salaryMax": null,
  "salaryCurrency": null,
  "salaryPeriod": null,
  "description": "Who we are\n\nAbout Stripe\n\nStripe is a financial infrastructure platform…",
  "ats": "greenhouse",
  "tenant": "stripe",
  "scrapedAt": "2026-09-26T17:08:44.892Z",
  "boardUrl": "https://job-boards.greenhouse.io/stripe"
}
```

- `url` is the advert link the employer publishes, which is often on the employer's own site.
- `workplaceType` (`On-site`, `Hybrid` or `Remote`) appears where the employer publishes a work type on its board, and `isRemote` follows it. Otherwise `isRemote` is true or false only where the location or title says so, and null if they don't.
- `boardUrl` is the board each row came from, which keeps a multi-board run separable.
- The salary columns are filled wherever the employer publishes a pay range.
- Greenhouse boards publish no contract type and no closing date. Those two columns aren't returned at all, which is tidier than returning them empty.

### How big a board, and how fast?

All of them, unless you set a cap. Timings are whole runs on Apify, 2 October 2026:

| Run | Result |
|---|---|
| 1 board, 20 jobs | 20 rows in 2 to 4 seconds |
| 8 boards, 50 jobs each | 390 rows in 4 to 9 seconds |
| 16 boards in full | 3,801 rows in 17 seconds |
| Greenhouse boards inside a 110-board mixed run, 20 jobs each | about 2 seconds a board on average |

At that rate a run inside the default one-hour timeout handles roughly **1,700 boards**.

Every row had the job id, link, title, employer, posting date and advert.

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

| Event | Free plan | Starter | Scale | Business |
|---|---|---|---|---|
| `actor-start`, per run | $0.001 | $0.001 | $0.001 | $0.001 |
| `board-scanned`, per careers board read | $0.0002 | $0.0002 | $0.0002 | $0.0002 |
| `job-detail`, per job with the full advert | $0.001 | $0.0009 | $0.0008 | $0.0007 |
| `job-listed`, per job without the full advert | $0.0005 | $0.00045 | $0.0004 | $0.00035 |

Enterprise plans pay the Business price.

**$1.00 per 1,000 jobs** with the full advert on the Free plan, $0.70 on Business. Without the advert it's $0.50 per 1,000 on the Free plan and $0.35 on Business. No proxy is needed, so you won't see proxy charges, and platform usage is in the price.

- 500 jobs with full adverts from one board: $0.001 + $0.0002 + 500 × $0.001 = **$0.5012** ($0.3512 on Business).
- The same 500 without the advert: $0.001 + $0.0002 + 500 × $0.0005 = **$0.2512** ($0.1762 on Business).

A job is billed once. It's `job-detail` when the row has the advert, meaning "Include advert text" or "Include advert HTML" is on. It's `job-listed` when both are off. `board-scanned` is charged once per board read.

On Apify's Free plan, the $5 of credits you get each month is about 4,900 Greenhouse jobs with adverts, or about 9,700 without.

### FAQ

#### Does it return salary?

Wherever the employer publishes a pay range: minimum, maximum, currency and, where the range says so, the period. US employers usually publish one. Elsewhere those columns are empty.

#### How do I find jobs that have been open a long time?

Compare `postedAt` with today, and ignore `updatedAt` for this purpose. An employer editing an advert moves `updatedAt` and leaves `postedAt` alone.

#### How do I get only one department or office?

Open the board in your browser, filter it there, and paste the filtered link. Or use "Title must contain" and "Location must contain" here.

#### Why does the work-type filter return so little on some boards?

Because many employers on Greenhouse don't state a work type. With "Keep jobs with no stated work type" off, only jobs that state one, or whose location or title says remote, survive.

#### What if I paste a board from a different platform?

The run tells you which platform it recognised and carries on with your other URLs. For mixed lists, [ATS Jobs Scraper](https://apify.com/boncross/ats-jobs) reads nine platforms from one input.

#### Do I need a proxy or a Greenhouse account?

You don't. Nothing needs configuring.

#### What are the limits?

- Greenhouse boards only.
- Location and team are only as good as the employer's own listing.
- Salary depends on the employer publishing a range.
- It reads jobs. It can't apply for them.

#### Is scraping Greenhouse boards legal?

A Greenhouse board is a public page an employer puts up to attract applicants, and this actor reads it without logging in. That kind of public data collection is generally lawful in the UK and EU. Using it responsibly is down to you.

### Scheduling it and calling it from code

Schedule a daily run with "Only jobs not seen before" on and you have a feed of new openings at the companies you follow. Send it to Slack, Google Sheets, Zapier, Make or Airbyte, or to your own endpoint with a webhook.

Developers can call Apify's run-sync-get-dataset-items endpoint to run the actor and get the rows back in one request, using `apify-client` for Python or JavaScript.

For AI agents, point an MCP-capable client at `https://mcp.apify.com` and the actor is available as a tool. An agent can read a company's board and tell you which teams it's growing.

### More from BonCross

- [Ashby Jobs Scraper](https://apify.com/boncross/ashby-jobs): Every open job on an Ashby careers board.
- [ATS Jobs Scraper](https://apify.com/boncross/ats-jobs): Jobs from any company careers page.
- [Lever Jobs Scraper](https://apify.com/boncross/lever-jobs): A Lever careers board for every open job.
- [Workday Jobs Scraper](https://apify.com/boncross/workday-jobs): Every open job from a Workday careers board.
- [hiring.cafe Job Scraper](https://apify.com/boncross/hiring-cafe-jobs): Every job hiring.cafe lists for your filters.
- [Indeed Job Scraper](https://apify.com/boncross/indeed-jobs): Indeed job listings for a role and location.

### Your feedback

Found a Greenhouse board that returns fewer jobs than its page shows, or a pay range we missed? Post the board URL on the Issues tab in Apify Console. We check every report against the live board.

# Changelog

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

# Actor input Schema

## `boardUrls` (type: `array`):

One or more Greenhouse careers boards, for example https://job-boards.greenhouse.io/stripe

## `startUrls` (type: `array`):

The same thing, but able to bulk-load Greenhouse boards from a file or a Google Sheet.

## `maxItemsPerBoard` (type: `integer`):

Row limit for each board. 0 means no limit.

## `maxItems` (type: `integer`):

Row ceiling across every board in the run. 0 means no limit.

## `keyword` (type: `string`):

Keeps jobs whose title or advert contains this word, as the board's own search does. A board link that already carries its own search word keeps it, and this then matches titles only.

## `titleIncludes` (type: `array`):

Keep a job only if its title contains at least one of these words. Case-insensitive.

## `titleExcludes` (type: `array`):

Drop a job if its title contains any of these words. Case-insensitive.

## `locationIncludes` (type: `array`):

Keep a job only if its location contains one of these words, for example "London".

## `onlyRemote` (type: `boolean`):

Keep only jobs the employer marked as remote. Jobs where the board says nothing about remote working are dropped, not assumed.

## `workplaceTypes` (type: `array`):

Keep only jobs with these working arrangements, as the employer states them (the workplaceType column). Leave empty for all. Jobs whose board does not say are kept unless you turn off the option below; a job with no stated arrangement whose location or title says remote counts as Remote.

## `includeUnknownWorkplace` (type: `boolean`):

Only used with Work type. On (the default): jobs whose board says nothing about the arrangement are kept, because many employers never state it. Off: only jobs that state one of the chosen arrangements are kept.

## `maxDaysOld` (type: `integer`):

Keep only jobs posted in the last N days. 0 keeps everything.

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

Include the job advert as plain text.

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

Also include the advert's original HTML. Makes rows considerably larger.

## `onlyNew` (type: `boolean`):

Skip jobs returned by an earlier run that used the same store name below.

## `dedupStoreName` (type: `string`):

Named key-value store that remembers job ids between runs. Leave empty to remember nothing.

## `maxConcurrency` (type: `integer`):

How many requests run at once across the whole run. The default is the fastest level measured clean; lower it only if a run reports refusals or errors.

## `delayMs` (type: `integer`):

Optional pause before each call, in milliseconds. 0 (the default) runs as fast as the parallel limit allows.

## Actor input object example

```json
{
  "boardUrls": [
    "https://job-boards.greenhouse.io/stripe"
  ],
  "startUrls": [],
  "maxItemsPerBoard": 0,
  "maxItems": 0,
  "keyword": "",
  "titleIncludes": [],
  "titleExcludes": [],
  "locationIncludes": [],
  "onlyRemote": false,
  "workplaceTypes": [],
  "includeUnknownWorkplace": true,
  "maxDaysOld": 0,
  "includeDescription": true,
  "includeDescriptionHtml": false,
  "onlyNew": false,
  "dedupStoreName": "",
  "maxConcurrency": 8,
  "delayMs": 0
}
```

# Actor output Schema

## `dataset` (type: `string`):

Every job the run returned.

## `stats` (type: `string`):

Counts for the run.

# 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 = {
    "boardUrls": [
        "https://job-boards.greenhouse.io/stripe"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("boncross/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 = { "boardUrls": ["https://job-boards.greenhouse.io/stripe"] }

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

```

## MCP server setup

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