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

Returns every open job from a company's Greenhouse job board, for job boards, recruiters and hiring research: title, department, offices, published and updated dates, description and apply link. Paste a board URL or company name. Can return only postings new since the last run.

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

## Pricing

from $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.
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

Get every open job from a company's **Greenhouse job board** as clean, structured data: **title,
department, locations and offices, published and updated dates, full description and apply link**.
Paste a board address such as `https://boards.greenhouse.io/stripe`, or just the board name. The
easiest way to try it is to keep the example input and press Start: you get the first 25 jobs from
two example boards.

The Actor reads the job-board endpoint Greenhouse offers for showing a company's jobs on other
sites. It does not use a browser or proxies, and one request returns a whole board, so runs are fast.

### What you can do with Greenhouse job data

- **Monitor companies for new openings.** Turn on *Only jobs that are new since the last run*, add a
  schedule, and each run returns just the postings that appeared since the previous one.
- **Build a job board or a newsletter** from the companies you choose, for example data science jobs
  at a list of tech companies.
- **Track hiring as a signal.** Departments and offices show which teams a company is growing, for
  sales prospecting, investing or competitor research.
- **Spot edited postings.** Every job carries the date it was last updated next to the date it was
  first published.
- **Feed an AI agent or a spreadsheet** with current job postings in one fixed format.

### How to scrape Greenhouse job postings

1. Add one entry per company under **Greenhouse job boards**. Any of these work:
   - a board address: `https://boards.greenhouse.io/stripe` or
     `https://job-boards.greenhouse.io/airbnb`
   - the address of a single job on a board; the Actor reads the whole board
   - a company careers page that embeds a Greenhouse board: `https://www.figma.com/careers/`
   - just the board name: `stripe`
2. Optionally narrow the results with title keywords, location keywords or *Remote jobs only*.
3. Run the Actor and export the results as JSON, CSV or Excel, or read them through the API.

The board name is the word after `greenhouse.io/` in the address of a company's job pages. It is
usually the company name in lower case, but not always, so use the address when a name is not found.

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

You pay per job returned: **$2.50 per 1,000 jobs** ($0.0025 each) on the Free and Starter (Bronze) plans,
$2.00 on Scale (Silver) and $1.50 on Business (Gold) and above, plus $0.00005 per run start.
Platform usage is included, so there is nothing else to pay.

- 10 boards with 150 open jobs each: 1,500 jobs, $3.75.
- 20 boards capped at 25 jobs each: 500 jobs, $1.25.
- Daily monitoring with *Only jobs that are new since the last run*: you pay only for new postings.
  15 new jobs a day is about $0.04 a day.
- A run that finds nothing new costs $0.00005.

You can set a maximum charge per run. The Actor stops when it is reached, and the jobs it held
back are returned by a later run.

### Input

| Field | What it does |
|---|---|
| Greenhouse job boards | Board addresses, careers pages that embed a board, or board names. Required. |
| Title keywords | Keep jobs whose title contains any of these. Not case sensitive. |
| Location keywords | Keep jobs whose location or office contains any of these. Not case sensitive. |
| Remote jobs only | Keep only jobs with the word remote in the location text. |
| Exclude title keywords | Drop jobs whose title contains any of these. |
| Department keywords | Keep jobs whose department or team contains any of these. |
| Employment types | Keep full-time, part-time, contract, temporary or internship jobs. Jobs whose type the job system does not state are kept. |
| Seniority levels | Keep jobs whose title states one of the chosen ranks, such as senior, staff or director. |
| Posted within days | Keep jobs published in the last N days. |
| Only jobs with a salary | Keep jobs that state pay. |
| Include job description | Add the description as plain text and HTML. Default: on. |
| Only jobs that are new since the last run | Return only postings that appeared since the previous run. |
| Monitor name | A name for this search's memory of seen jobs, so two searches on the same company do not share it. |
| Maximum jobs per company | Stop after this many jobs for each board. 0 means no limit. |

Example:

```json
{
    "companies": ["https://boards.greenhouse.io/stripe", "airbnb", "https://www.figma.com/careers/"],
    "titleKeywords": ["data scientist", "analyst"],
    "locationKeywords": ["remote", "dublin"],
    "onlyNewSinceLastRun": true
}
```

### Output

One item per job posting. Two items from test runs, with the descriptions shortened:

```json
[
    {
        "company": "airbnb",
        "companyName": "Airbnb",
        "ats": "greenhouse",
        "jobId": "8031901",
        "title": "Data Scientist - Algorithms, Community Support",
        "seniority": null,
        "department": "Data Science",
        "team": null,
        "location": "Remote - USA",
        "locations": [
            "Remote - USA",
            "United States"
        ],
        "isRemote": true,
        "workplaceType": "remote",
        "employmentType": null,
        "employmentTypeText": null,
        "salaryMin": null,
        "salaryMax": null,
        "salaryCurrency": null,
        "salaryInterval": null,
        "salaryText": null,
        "salarySource": null,
        "publishedAt": "2026-06-28T17:01:24.000Z",
        "updatedAt": "2026-10-02T21:23:05.000Z",
        "jobUrl": "https://careers.airbnb.com/positions/8031901?gh_jid=8031901",
        "applyUrl": "https://careers.airbnb.com/positions/8031901?gh_jid=8031901",
        "descriptionText": "Airbnb was born in 2007 when two hosts welcomed three guests to their San Francisco home ...",
        "descriptionHtml": "<div class=\"content-intro\"><p><span style=\"font-family: helvetica ...",
        "scrapedAt": "2026-10-04T11:36:39.594Z"
    },
    {
        "company": "stripe",
        "companyName": "Stripe",
        "ats": "greenhouse",
        "jobId": "8123027",
        "title": "Account Executive, Commercial (Grower)",
        "seniority": null,
        "department": "1651 Velocity - Account Executives (NA)",
        "team": null,
        "location": "Chicago",
        "locations": [
            "Chicago",
            "US"
        ],
        "isRemote": null,
        "workplaceType": null,
        "employmentType": null,
        "employmentTypeText": null,
        "salaryMin": null,
        "salaryMax": null,
        "salaryCurrency": null,
        "salaryInterval": null,
        "salaryText": null,
        "salarySource": null,
        "publishedAt": "2026-08-11T23:45:35.000Z",
        "updatedAt": "2026-09-25T20:44:54.000Z",
        "jobUrl": "https://stripe.com/jobs/search?gh_jid=8123027",
        "applyUrl": "https://stripe.com/jobs/search?gh_jid=8123027",
        "descriptionText": "Who we are About Stripe Stripe is a financial infrastructure platform for businesses ...",
        "descriptionHtml": "<h2><strong>Who we are</strong></h2> <h3><strong>About Stripe</strong> ...",
        "scrapedAt": "2026-10-04T11:42:29.519Z"
    }
]
```

A field is `null` when Greenhouse does not provide it. What to expect from a Greenhouse board:

| Field | What Greenhouse provides |
|---|---|
| `company` | The board name, for example `stripe`. |
| `companyName` | The company's display name, for example `Stripe`. |
| `jobId` | The Greenhouse job ID. |
| `department` | The job's department. When a job has several, the first one. |
| `team` | Always `null`. |
| `location`, `locations` | The location text of the job, followed by the offices it belongs to. |
| `isRemote`, `workplaceType` | `true` and `remote` when the location text contains the word remote. Otherwise `null`, which means unknown, not on-site. |
| `employmentType` | Always `null`. |
| Salary fields | Always `null`. Pay that a company writes into the posting is part of the description. |
| `publishedAt` | When the job was first published. |
| `updatedAt` | When the job was last changed. |
| `jobUrl`, `applyUrl` | The address the company set for the posting, often a page on its own website. Both fields hold the same address. |

### How to monitor a Greenhouse job board for new jobs

Turn on *Only jobs that are new since the last run* and put the Actor on a schedule. It keeps the
IDs of the jobs that were open on the previous run in a key-value store named
`greenhouse-jobs-scraper-state` in your account, and returns only postings whose ID is not in that
list. The first run returns everything. Delete that store to start over.

Postings held back by *Maximum jobs per company*, or by the maximum charge you set for a run, are not
marked as seen, so a later run returns them.

Each board has its own record, saved as soon as its jobs are stored, so a run that is stopped halfway
does not report the same jobs as new the next time. Runs started from different saved tasks keep
separate memories; set *Monitor name* to keep several searches apart within one task or through the API.
If a board that had postings suddenly returns none, the list is kept as it was.

### Limits

- Greenhouse does not publish salary, employment type or a remote flag through its job-board
  endpoint. *Remote jobs only* therefore relies on the location text.

- Only public boards can be read. A board the company has not published is reported as not found.

- Greenhouse returns a whole board in one answer. *Maximum jobs per company* limits what is saved
  and charged, not what is downloaded, so it does not make a large board faster.

- A careers page on the company's own domain works when its HTML references the Greenhouse board.
  Pages that load the board only through JavaScript may not be detected; use the board address in
  that case.

- No AI-written fields. Skills, benefits, visa sponsorship and requirement summaries are not returned;
  seniority, employment type and pay are read by fixed rules from what the posting states.

- No company profile from LinkedIn or Crunchbase (size, industry, funding) and no hiring-manager name or
  email. The Actor returns what the company publishes on its own job board, and no personal data.

- Seniority comes from words in the title (senior, staff, lead, director and so on). A title that states
  no rank has no seniority, and level numbers such as II or L4 are not interpreted, because they mean
  different ranks at different companies.

- When the job system publishes no pay figures, the Actor reads a pay range from the description if one
  is written there, and sets `salarySource` to `description`. Bonuses, budgets and single figures are
  not read as pay.

- Changing a filter does not bring back postings an earlier run already marked as seen. Use a new
  *Monitor name* for a new search.

### FAQ

**A company was "not found". Why?**
The name did not match a public Greenhouse board. Open the company's careers page and click a job.
If the address contains `greenhouse.io`, paste that address instead of the name. If it does not, the
company may use a different system.

**The Actor says my URL runs on another system. Why?**
This Actor reads Greenhouse only. A Lever, Ashby or Workday address is reported as a failed company;
see *Other systems* below.

**Why do the job links point to the company's own website?**
Greenhouse returns the address the company chose for each posting. Many companies show their
Greenhouse jobs on their own careers site, and the link goes there.

**Is it legal to scrape Greenhouse job postings?**
The Actor reads public job postings through the endpoint Greenhouse offers for displaying a
company's jobs on other sites. It collects no personal data. You are responsible for how you use
the results.

**Something broke or a field is missing.**
Open an issue on the Issues tab with the company you ran. Fixes usually ship within two days.

**What happens if a run is interrupted?**
Progress is saved after every batch of jobs. If Apify moves or restarts the run, it continues where it
stopped, and no job is stored or charged twice. Every run also writes a `RUN_SUMMARY` record to its
key-value store with the result for each company: saved, not found or failed, and why. You pay for the
jobs a run stored, whether or not it finished; a company that failed or was not reached costs nothing.

### Other systems

For Workday, Greenhouse, Lever and Ashby together in one run and one format, use
[ATS Job Postings](https://apify.com/enisbodlli/ats-job-postings) by the same developer. It accepts
the same addresses and returns the same fields.

### More Actors from this developer

Jobs:

- [Company Jobs Search](https://apify.com/enisbodlli/company-jobs-search)
- [ATS Job Postings: Workday, Greenhouse, Lever & Ashby](https://apify.com/enisbodlli/ats-job-postings)
- [Workday Jobs Scraper](https://apify.com/enisbodlli/workday-jobs-scraper)
- [Lever Jobs Scraper](https://apify.com/enisbodlli/lever-jobs-scraper)
- [Ashby Jobs Scraper](https://apify.com/enisbodlli/ashby-jobs-scraper)

Company registers:

- [Handelsregister Scraper: German Company Register](https://apify.com/enisbodlli/handelsregister-scraper)
- [North Data Scraper: German & European Companies](https://apify.com/enisbodlli/northdata-company-scraper)
- [European Company Registry Search](https://apify.com/enisbodlli/eu-company-registry-search)
- [US Business Entity Search & New Business Filings](https://apify.com/enisbodlli/us-business-registry-search)
- [Brazil CNPJ Scraper: Company Search & Lookup](https://apify.com/enisbodlli/brazil-cnpj-company-search)

Contacts and lists:

- [Website Contact Scraper](https://apify.com/enisbodlli/website-contact-scraper)
- [Email Validator & List Cleaner](https://apify.com/enisbodlli/email-validator)

# Actor input Schema

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

One entry per company. Use the Greenhouse job board URL (for example https://boards.greenhouse.io/stripe or https://job-boards.greenhouse.io/airbnb), a company careers page that embeds a Greenhouse board, or just the board name (for example stripe). The board name is the word after greenhouse.io/ in the address of any of the company's job pages.

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

Keep only jobs whose title contains at least one of these words or phrases. Not case sensitive. Leave empty to keep every job.

## `excludeTitleKeywords` (type: `array`):

Drop jobs whose title contains any of these words or phrases, for example intern or manager. Not case sensitive.

## `locationKeywords` (type: `array`):

Keep only jobs whose location or office contains at least one of these words or phrases, for example Dublin or United States. Not case sensitive. Leave empty to keep every location.

## `departmentKeywords` (type: `array`):

Keep only jobs whose department or team contains at least one of these words, for example engineering or sales. Workday lists no department, so its jobs do not pass this filter.

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

Keep only remote jobs. Greenhouse has no remote flag, so a job counts as remote when its location text contains the word remote.

## `employmentTypes` (type: `array`):

Keep only jobs of these types. A job whose type the job system does not state is kept, because Greenhouse states none.

## `seniorityLevels` (type: `array`):

Keep only jobs whose title states one of these ranks. A title without a rank word, such as "Software Engineer", has no seniority and is left out when this filter is set.

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

Keep only jobs published in the last N days, for example 7. 0 switches the filter off. Jobs with no published date are left out when it is on.

## `hasSalary` (type: `boolean`):

Keep only jobs that state pay, either in the job system's salary field or as a range written in the description.

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

Add the full job description as plain text and as HTML. Turn off for smaller results.

## `onlyNewSinceLastRun` (type: `boolean`):

Remember which jobs were open on the previous run and return only postings that appeared since. The first run returns everything. Use with a schedule to monitor Greenhouse job boards for new openings.

## `monitorName` (type: `string`):

Used with "Only jobs that are new since the last run". A name for this search's memory of seen jobs, for example engineers-berlin. Runs with different names keep separate memories, so two searches on the same company do not hide each other's jobs. Saved tasks are kept apart automatically; set this when you start runs through the API or keep several searches in one task.

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

Stop after this many jobs for each job board. 0 means no limit.

## Actor input object example

```json
{
  "companies": [
    "https://boards.greenhouse.io/stripe",
    "https://boards.greenhouse.io/airbnb"
  ],
  "titleKeywords": [],
  "excludeTitleKeywords": [],
  "locationKeywords": [],
  "departmentKeywords": [],
  "remoteOnly": false,
  "employmentTypes": [],
  "seniorityLevels": [],
  "postedWithinDays": 0,
  "hasSalary": false,
  "includeDescription": true,
  "onlyNewSinceLastRun": false,
  "monitorName": "engineers-berlin",
  "maxJobsPerCompany": 25
}
```

# Actor output Schema

## `results` (type: `string`):

One item per job posting, in the run's default dataset.

## `runSummary` (type: `string`):

The result for each company: saved, not found or failed, with the reason and any notes.

# 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": [
        "https://boards.greenhouse.io/stripe",
        "https://boards.greenhouse.io/airbnb"
    ],
    "maxJobsPerCompany": 25
};

// Run the Actor and wait for it to finish
const run = await client.actor("enisbodlli/greenhouse-jobs-scraper").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": [
        "https://boards.greenhouse.io/stripe",
        "https://boards.greenhouse.io/airbnb",
    ],
    "maxJobsPerCompany": 25,
}

# Run the Actor and wait for it to finish
run = client.actor("enisbodlli/greenhouse-jobs-scraper").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": [
    "https://boards.greenhouse.io/stripe",
    "https://boards.greenhouse.io/airbnb"
  ],
  "maxJobsPerCompany": 25
}' |
apify call enisbodlli/greenhouse-jobs-scraper --silent --output-dataset

```

## MCP server setup

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

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/R5xzI77xWj8hT58Yt/builds/BqJX0hacN1KD4XjNE/openapi.json
