# Greenhouse Scraper (`mlg14/greenhouse-scraper`) Actor

Scrape public Greenhouse job boards by board token or URL. Export current openings, locations, descriptions, departments, offices, and posting dates with search filters.

- **URL**: https://apify.com/mlg14/greenhouse-scraper.md
- **Developed by:** [MLG Data](https://apify.com/mlg14) (community)
- **Categories:** Jobs, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 results

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 Scraper

Scrape Greenhouse job openings from public employer boards and export the results to JSON, CSV, or a spreadsheet format. This Greenhouse jobs API alternative collects current listings, descriptions, locations, departments, offices, and posting dates from the boards you choose.

Enter one board token or URL for a single employer, or enter several to build one dataset across employers. Search within those boards by job title, location, employer, description, and publication date. Each result includes the direct job URL so a reader can inspect the original posting or begin an application on the employer's site.

### What data can you extract from Greenhouse?

Each dataset row represents one public job posting. The actor keeps source values where possible and uses camelCase field names for export. A location is the employer's original location string; it is not converted into city, region, and country components.

| Field | Description | Example |
| --- | --- | --- |
| `id` | Public posting identifier | `8211770` |
| `internalJobId` | Internal identifier exposed by the board | `3553880` |
| `requisitionId` | Employer's requisition reference | `2026 - P3186` |
| `url` | Direct posting or employer career URL | `https://job-boards.greenhouse.io/greenhouse/jobs/8211770?gh_jid=8211770` |
| `title` | Job title as published | `Senior Program Manager, GTM & Field Enablement` |
| `organization` | Employer name in the source listing | `Greenhouse` |
| `organizationUrl` | Public board URL for that employer | `https://job-boards.greenhouse.io/greenhouse` |
| `boardToken` | Board identifier used for collection | `greenhouse` |
| `location` | Original location text | `Anywhere in Ireland` |
| `locations` | Array containing the original location text when present | `["Anywhere in Ireland"]` |
| `departments` | Department names attached to the posting | `["RevOps Sales"]` |
| `offices` | Office names attached to the posting | `["Republic of Ireland"]` |
| `datePosted` | First publication timestamp, including source timezone | `2026-09-25T09:41:52-04:00` |
| `dateModified` | Last update timestamp, including source timezone | `2026-09-25T09:41:52-04:00` |
| `applicationDeadline` | Deadline when the board provides one | `null` |
| `descriptionText` | Readable job description with markup removed | `Our mission ...` |
| `descriptionHtml` | Decoded HTML for headings, links, and lists | `<div class="content-intro">...` |
| `language` | Language code supplied by the board | `en` |
| `metadata` | Custom job metadata when supplied | `null` |
| `source` | Source platform identifier | `greenhouse` |
| `sourceType` | Source category | `ats` |
| `sourceDomain` | Hostname of the direct posting URL | `job-boards.greenhouse.io` |

The first publication date and the last update date describe different events. An old opening can have a recent update, so use `datePosted` for newly published jobs and `dateModified` for changes to existing jobs. The actor does not infer a publication date when the source omits it.

### How to scrape Greenhouse jobs

1. Find the public job board URL for each employer you want to monitor. A board token is the path segment after the board hostname. You may paste the complete board URL instead.
2. Put the tokens or URLs in **Job boards**. Add as many as you need. They are processed in the order entered.
3. Set **Maximum jobs**, then add any title, location, employer, description, or publication date filters. Leave filters empty to collect every available opening up to the limit.
4. Run the actor. It fetches each public board, applies your filters, and writes one dataset row per matching job.
5. Open the dataset to download JSON, CSV, or a spreadsheet file. Use `url` to return to the original job posting.

For a quick first run, enter `greenhouse` as the board token and keep the default settings. If the board has fewer jobs than your maximum, the dataset contains only the jobs currently available on that board. The maximum is a cap, not a target count.

### Input

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `boards` | string array | Required | Public board tokens or board URLs; one or several employers. |
| `maxItems` | integer | `100` | Maximum matching jobs across all boards. `0` means no item cap. |
| `includeDescription` | boolean | `true` | Fetch both plain text and HTML descriptions. |
| `sortBy` | string | `newest` | `newest` sorts each board by first publication date; `source` preserves source order. |
| `titleSearch` | string array | Empty | Keep titles containing any listed phrase. |
| `titleExclusionSearch` | string array | Empty | Remove titles containing any listed phrase. |
| `locationSearch` | string array | Empty | Keep source location strings containing any listed phrase. |
| `locationExclusionSearch` | string array | Empty | Remove locations containing any listed phrase. |
| `descriptionSearch` | string array | Empty | Keep descriptions containing any listed phrase. |
| `descriptionExclusionSearch` | string array | Empty | Remove descriptions containing any listed phrase. |
| `organizationSearch` | string array | Empty | Keep employer names containing any listed phrase. |
| `organizationExclusionSearch` | string array | Empty | Remove employer names containing any listed phrase. |
| `postedAfter` | date string | Empty | Keep jobs first published on or after `YYYY-MM-DD`. |
| `postedBefore` | date string | Empty | Keep jobs first published on or before `YYYY-MM-DD`. |
| `proxyConfiguration` | object | Enabled | Proxy settings for board requests. |

Example input for a focused search on the public board:

```json
{
  "boards": ["greenhouse"],
  "maxItems": 100,
  "includeDescription": true,
  "sortBy": "newest",
  "titleSearch": ["manager"],
  "postedAfter": "2026-01-01"
}
```

Within a search field, multiple phrases use **any** matching phrase. Across fields, all active conditions must match. Exclusions remove a job if any exclusion phrase appears. Matching ignores letter case and looks for the phrase as a substring; it does not perform stemming, geographic normalization, or Boolean search syntax. For example, a title phrase `manager` can match `Engineering Manager`, while a location phrase `Ireland` matches the source text `Anywhere in Ireland`.

The date filters compare the calendar date portion of `datePosted`. They do not filter by `dateModified`. If you use either description filter, keep `includeDescription` enabled so there is description text to search. Setting the maximum to `0` removes the actor's output cap; it does not create jobs beyond those currently exposed by the selected boards.

### Output example

This item comes from a successful run on 26 September 2026. The two description fields are shortened here for readability; dataset rows contain the complete description.

```json
{
  "id": 8211770,
  "internalJobId": 3553880,
  "requisitionId": "2026 - P3186",
  "url": "https://job-boards.greenhouse.io/greenhouse/jobs/8211770?gh_jid=8211770",
  "title": "Senior Program Manager, GTM & Field Enablement",
  "organization": "Greenhouse",
  "organizationUrl": "https://job-boards.greenhouse.io/greenhouse",
  "boardToken": "greenhouse",
  "location": "Anywhere in Ireland",
  "locations": ["Anywhere in Ireland"],
  "departments": ["RevOps Sales"],
  "offices": ["Republic of Ireland"],
  "datePosted": "2026-09-25T09:41:52-04:00",
  "dateModified": "2026-09-25T09:41:52-04:00",
  "applicationDeadline": null,
  "descriptionText": "Our mission at Greenhouse is to make hiring work for everyone ...",
  "descriptionHtml": "<div class=\"content-intro\"><p>Our mission ...</p></div>",
  "language": "en",
  "metadata": null,
  "source": "greenhouse",
  "sourceType": "ats",
  "sourceDomain": "job-boards.greenhouse.io"
}
```

The golden run requested 50 jobs from two boards and returned 50. Every required identifier, URL, title, employer, location, publication timestamp, modification timestamp, description, and board token was filled in that run. Those fill rates describe the tested boards on that date; other employers may leave optional fields empty.

### Use cases

- Recruiting teams can monitor openings at a defined set of employers and compare changes in hiring activity over time.
- Job seekers can collect openings from chosen boards, filter titles and locations, and keep direct links to the original postings.
- Researchers can track department and office labels to study how an employer presents teams and geography in its public listings.
- Editorial teams can build a current jobs feed from known boards while keeping source links and publication dates visible.
- Operations teams can schedule repeated collections and compare posting IDs between runs to find newly opened or removed listings.
- Data teams can join a board token and posting ID to their own employer lists and enrich the records in a separate workflow.

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

The configured price is **$1.00 per 1,000 delivered jobs**, or **$0.001 per dataset item**. The result count after filtering determines the per-result charge. Platform usage is included in that price for this actor. A smaller maximum or narrower filters can lower the number of delivered records and therefore the charge.

| Example run | Delivered jobs | Result charge |
| --- | ---: | ---: |
| Small board collection | 50 | $0.05 |
| Several boards with filters | 250 | $0.25 |
| Larger multi-board collection | 1,000 | $1.00 |

These are arithmetic examples at the current per-result price, not promises that a given board has that many jobs. A board with 19 live jobs produces 19 rows at most even if `maxItems` is 100. Conversely, `maxItems: 0` can produce more rows if several large boards are supplied. Check your run's dataset item count for the actual number delivered.

### Tips for best results

Start with the board URL seen on a public careers page. Some board URLs redirect visitors to a custom career site, but the board token may still work with the public job feed. Use a full board URL or its token, then verify that the returned titles and employer match what you intended. A regular corporate homepage URL is not a board URL and cannot be used as an input.

Put your highest priority board first when `maxItems` is smaller than the combined number of matches. The actor processes boards in input order and stops once the cap is reached. With `sortBy: newest`, jobs are sorted within each board, not globally across all boards. To make a combined dataset ordered across employers, sort the exported `datePosted` column after collection.

Turn off descriptions when you only need a title, location, posting date, and link. The response is then smaller, and the description fields are null. Keep descriptions enabled for any description search. Use specific phrases in the appropriate field: a title search checks the title, while a description search checks the full readable posting body and can match words unrelated to the role name.

Use date filters to focus on recently *published* openings. A modification time may change when an employer edits a posting, but it does not mean the role was newly opened. For monitoring, store the numeric `id` with `boardToken` and compare the pair across scheduled runs. That pair is a practical key for a job within the selected board.

### Limits

The actor reads public jobs from board tokens that you supply. It does not discover every employer that uses Greenhouse and cannot run a global search across all boards from one keyword. This differs from a service backed by its own multi-employer job database. To search more employers, add their public board tokens to the input.

A board response currently exposes its available jobs as one listing rather than a paginated sequence. In testing, one board returned more than 700 jobs and another returned more than 160. No fixed page cap was observed in those public responses. Very large boards with descriptions can return several megabytes of data, so run time and memory use increase with board size and the number of boards entered.

The source location can be a city, a region, several locations in one string, or wording such as `Anywhere in Ireland`. The actor preserves that wording and does not geocode it. `locations` is an array holding that original string, not a list of independently normalized places. Department and office values are labels from the board and may vary across employers.

The source can omit deadlines, custom metadata, requisition IDs, language, and descriptions. A null value means the board did not provide that field in the selected response. Salary, employment type, remote arrangement, company size, and inferred skills are not added because the public listing does not consistently provide structured values for them. Jobs that require a private login are outside this actor's public-board scope.

If a board token is wrong, the board request can return an error and the run logs will identify the affected board. Other supplied boards can still produce results. The actor does not submit applications or collect applicant information. Direct job URLs sometimes lead to an employer's own career page rather than a hosted board page; the `sourceDomain` field reflects the actual URL hostname.

### Use with agents

The input and output fields also work in automated workflows that select boards and inspect returned rows. Keep the instruction specific about employers, limits, and filters. Two example prompts are:

> Collect up to 100 currently open jobs from the `greenhouse` board. Return titles, locations, publication dates, and direct URLs.

> From my saved list of public board tokens, collect jobs published this year whose titles contain `engineer`, then summarize the departments represented in the returned records.

A workflow can pass board tokens, read the resulting dataset, and retain direct source links for verification. Generated summaries should treat missing values as missing source data. In particular, the actor's location string should not be presented as a normalized city or country without a separate validation step.

### FAQ

#### Is scraping these jobs legal?

The actor collects publicly available job postings. Check the terms that apply to your use, respect privacy and data protection rules, and avoid using exported data for personal-data misuse. The actor does not access candidate accounts or application submissions.

#### Do I need to configure a proxy?

The default input uses a proxy for board requests. The successful golden run required no special challenge handling. Network conditions can change, so leave the default enabled unless your environment calls for a different permitted configuration.

#### How fast is a run?

The measured golden run collected 50 jobs from two boards and completed successfully in about 24 seconds after startup. Time varies with board size, description size, network conditions, and the number of boards. A larger run should not be estimated by multiplying that single measurement.

#### Can I schedule and monitor collections?

Yes. Save an input configuration, schedule repeated runs, and compare the resulting datasets. Keep each run's board tokens, `id` values, and timestamps so you can distinguish a new posting from an update to an existing one.

#### Can I export the jobs to a spreadsheet?

Yes. Download the dataset as a spreadsheet file or CSV, or request JSON for another workflow. The flat fields are intended to make filtering and joining straightforward. Description columns can be long, so omit descriptions when the spreadsheet only needs summary fields.

#### Why is a field empty?

Employers choose what to publish on their boards. Deadlines and custom metadata are often absent, and some boards may provide less detail than the two used in the golden run. A null field indicates missing source data or an intentionally disabled description, rather than a guessed value.

#### Why did I receive fewer jobs than `maxItems`?

`maxItems` is an upper bound on matching rows. The selected boards may have fewer live openings, or your phrases and dates may filter out many jobs. Remove filters temporarily to see how many public jobs a board currently exposes.

### Integrations

Use the dataset URL in an application, a scheduled export, or a webhook-driven process. JSON supports custom data pipelines, while CSV and spreadsheet exports support manual review. A saved run configuration can feed a recurring jobs page or an internal hiring report; retain the original `url` field so readers can confirm that each opening is still live.

### Support

Open an issue on the Issues tab; we reply within 24h and add fields on request.

# Actor input Schema

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

Board tokens or public board URLs to scrape. Each board is one employer; enter multiple boards to combine employers.

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

Stop after this many matching jobs across all boards. Set 0 to collect all matches.

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

Fetch full job descriptions as both HTML and plain text. Turn off for smaller responses when descriptions are not needed.

## `sortBy` (type: `string`):

Sort each board by publication date or keep the source order.

## `titleSearch` (type: `array`):

Include jobs whose title contains any listed phrase, ignoring case.

## `titleExclusionSearch` (type: `array`):

Exclude jobs whose title contains any listed phrase, ignoring case.

## `locationSearch` (type: `array`):

Include jobs whose source location text contains any listed phrase, ignoring case.

## `locationExclusionSearch` (type: `array`):

Exclude jobs whose source location text contains any listed phrase, ignoring case.

## `descriptionSearch` (type: `array`):

Include jobs whose plain text description contains any listed phrase, ignoring case. Requires descriptions.

## `descriptionExclusionSearch` (type: `array`):

Exclude jobs whose plain text description contains any listed phrase, ignoring case. Requires descriptions.

## `organizationSearch` (type: `array`):

Include jobs whose employer name contains any listed phrase, ignoring case.

## `organizationExclusionSearch` (type: `array`):

Exclude jobs whose employer name contains any listed phrase, ignoring case.

## `postedAfter` (type: `string`):

Include jobs first published on or after this date, in YYYY-MM-DD format.

## `postedBefore` (type: `string`):

Include jobs first published on or before this date, in YYYY-MM-DD format.

## `proxyConfiguration` (type: `object`):

Use an Apify proxy for board requests. The default configuration starts with datacenter routing.

## Actor input object example

```json
{
  "boards": [
    "greenhouse"
  ],
  "maxItems": 100,
  "includeDescription": true,
  "sortBy": "newest",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Matching job postings in the default dataset.

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

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

```

## MCP server setup

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