# Greenhouse Job Board Scraper - Company Jobs, Salary, Remote (`yadroo/greenhouse-jobs`) Actor

Open positions from any company's Greenhouse board, read from the public Job Board API - no key, no browser. One row per posting: title, location, department path, office, published salary range, first published and updated time in UTC, apply link and optional description text.

- **URL**: https://apify.com/yadroo/greenhouse-jobs.md
- **Developed by:** [Samat Makatov](https://apify.com/yadroo) (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 $1.05 / 1,000 job posting returneds

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 Job Board Scraper - Company Jobs, Salary, Remote

Give this actor the board token of a company that hires through Greenhouse and get its open positions as flat rows:
job title, the location text the employer typed, the department path from the company's own tree, the office, the
published pay range as two numbers and a currency, when the posting first appeared and when it last changed (both in
UTC), the apply link and — on request — the full description. The data comes from the public Greenhouse Job Board API,
the same one a company's own career page reads. **No API key, no proxy, no browser, no login.**

Coverage is per company, not a search across all employers: you name the companies, the actor reads their boards. That
is the point — the rows are exactly what the employer publishes, with no aggregator in between.

### Use cases

- **Sourcing for one employer (recruiters, agencies)**: every open engineering role at one company, newest change
  first, with the apply link — a shortlist you can refresh every morning.
- **Hiring signals for a B2B pipeline (GTM, sales, lead gen)**: watch a list of accounts with `postedWithinDays: 30`;
  a company that starts hiring data engineers is a company that just bought something.
- **Pay benchmarking (compensation, people analytics)**: `withSalaryOnly: true` gives only the postings with a
  published band, already converted from cents into whole currency units, with the label the employer gave the band.
- **A niche job board or newsletter**: a shortlist of employers plus `titleKeywords` and `remoteOnly`, refreshed on a
  schedule with `onlyNew: true`, so each run writes only what you have not published yet.
- **Feeding an agent or a search index**: `includeDescription: true` adds the full posting as clean text and as the
  published HTML, ready for embeddings, summarisation or keyword search.
- **Org research (analysts, competitor watching)**: `departments` filters on the company's real department tree, so
  "everything under Sales" is a filter, not a guess from job titles.

### Input

Only `boardTokens` is required. Every filter is optional; filters are combined with **AND**, and the values inside one
filter with **OR**.

| Field | Type | Default | Allowed values / notes |
|---|---|---|---|
| `boardTokens` | string\[] | **required** | One or more board tokens, e.g. `["stripe", "duolingo"]`. A full board URL works too — see [Finding the board token](#finding-the-board-token). An unknown token comes back as one row with `found: false`, it does not fail the run. |
| `titleKeywords` | string\[] | all titles | Keep a posting when its title contains at least one of these, case-insensitive: `["engineer", "data"]`. |
| `excludeTitleKeywords` | string\[] | none | Drop a posting when its title contains any of these: `["intern", "contract"]`. Applied after `titleKeywords`. |
| `locationContains` | string\[] | all locations | Match the free-text location: `["London", "India"]`. See [Locations are free text](#locations-are-free-text). |
| `departments` | string\[] | all departments | Match at any level of the company's department tree: `["Sales"]` also returns "Sales > Enterprise Sales". Typos are corrected against the board's own names — see [Departments and offices](#departments-and-offices). |
| `offices` | string\[] | all offices | Match the office a posting hangs under: `["Dublin", "Remote"]`. Switches on the large response (slower, see [Two responses](#two-responses)). |
| `remoteOnly` | boolean | `false` | Keep only postings whose location or office text reads remote. This is a text verdict, not a source flag — see [isRemote is our verdict](#isremote-is-our-verdict). |
| `withSalaryOnly` | boolean | `false` | Keep only postings that publish a pay range. |
| `postedWithinDays` | integer | no limit | 1–365. Keep postings first published in the last N days (UTC). |
| `sinceHours` | integer | no limit | 1–8760. Keep postings changed in the last N hours (UTC), edits and re-posts included. |
| `onlyNew` | boolean | `false` | Emit only postings this actor has not delivered before — see [onlyNew](#onlynew-across-scheduled-runs). |
| `includeDescription` | boolean | `false` | Add `descriptionHtml` and `descriptionText`. Switches on the large response. |
| `sortBy` | string | `updatedDesc` | `updatedDesc`, `publishedDesc`, `titleAsc`, `boardOrder` — see [Sort orders](#sort-orders). |
| `maxItems` | integer | `25` | 1–5000. Total rows written, across all companies. |
| `maxItemsPerBoard` | integer | no cap | 1–2000. Rows taken from each company before the global cap, so one big employer cannot fill the dataset. |
| `fields` | string\[] | all fields | Keep only these output fields, in this order. `boardToken` and `found` are always kept. |

### Reference

#### Finding the board token

The token is the last part of the board address a company links to from its careers page:

| What you have | Token |
|---|---|
| `https://job-boards.greenhouse.io/stripe` | `stripe` |
| `https://boards.greenhouse.io/databricks/jobs/8439170002` | `databricks` |
| `https://boards.greenhouse.io/embed/job_board?for=cloudflare` | `cloudflare` |
| `https://boards-api.greenhouse.io/v1/boards/gitlab/jobs` | `gitlab` |

Paste any of those addresses into `boardTokens` and the token is taken from them. The token is **not always the company
name** (`stripe` works, plenty of others differ), and a link with `?gh_jid=…` on a company's own domain does not contain
it — open the board itself and copy the address. Tokens are matched case-insensitively and written back in lower case.

A token nobody publishes ends as one row with `found: false` and an `error` that says so; the other companies in the
same run are unaffected.

#### Sort orders

| Value | Order |
|---|---|
| `updatedDesc` | Last change first (`updatedAt`) — the default, best for watching a board. |
| `publishedDesc` | Newest posting first (`firstPublishedAt`) — best for hiring signals. |
| `titleAsc` | Job title A→Z. |
| `boardOrder` | Untouched, exactly as the company's board returns them. |

With several companies the rows are sorted across all of them, **after** `maxItemsPerBoard` has been applied per
company.

#### Departments and offices

There is no global list: every company builds its own tree ("Sales > Enterprise Sales", "Engineering > Platform"). To
see what a company uses, run it once without the filter and read `departmentPath` and `offices`. The filter matches at
**any level**, so `["Sales"]` returns the postings of every child department too.

A filter value no department on that board contains is compared with the board's own names: an obvious typo is
corrected and logged (`filter value "Enginering" read as "Engineering"`), and a value nothing resembles is kept as
typed and reported in the log and in the run's `SUMMARY`. A filter never silently widens into the whole board.

#### Two responses

The actor makes **two requests per company**: the board's postings and the department tree. When you ask for
`includeDescription` or an `offices` filter, the first request switches to the variant that carries the full posting
text — for a board with a few thousand openings that is a multi-megabyte answer, so those runs take a few seconds
longer. Nothing else changes the number of requests: one company is one pass, the API returns the whole board at once
and there is no pagination to walk.

#### Locations are free text

`location` is a string the employer typed into Greenhouse, not a structured place. Real examples from one day:
`Dublin`, `Seattle, San Francisco, New York City`, `Remote - California`, `Bengaluru, India; Mumbai, India`,
`US-SF-HQ, US-NYC, US-CHI, US-SEA`, and at one company simply `Hybrid`. Match on a city, state or country word rather
than on an exact string, and expect a posting to name several places at once.

#### isRemote is our verdict

Greenhouse has no remote flag. `isRemote` is `true` when the location or an office name contains wording such as
*remote*, *distributed*, *anywhere*, *work from home* or *virtual*, and `remoteOnly` uses the same test. A posting an
employer describes only as "Hybrid" is therefore not remote here, and a company that hides remote work in the
description text will not be caught.

#### onlyNew across scheduled runs

With `onlyNew: true` the actor remembers company, job id and `updatedAt` of every row it wrote, in its own named
key-value store, and skips those rows next time. An **edited** posting counts as new again, because its `updatedAt`
moved. The first run returns everything it finds; rows cut off by `maxItems` are not remembered, so the next run picks
them up.

### Examples

**Every open engineering role at one employer**

```json
{ "boardTokens": ["stripe"], "titleKeywords": ["engineer"], "sortBy": "updatedDesc", "maxItems": 20 }
```

**Remote openings across a shortlist, fairly shared between companies**

```json
{ "boardTokens": ["duolingo", "gitlab", "databricks"], "remoteOnly": true, "maxItemsPerBoard": 10, "sortBy": "publishedDesc", "maxItems": 25 }
```

**Only the postings with a published pay range**

```json
{ "boardTokens": ["gitlab"], "withSalaryOnly": true, "sortBy": "updatedDesc", "maxItems": 20 }
```

**Hiring signals: what these accounts started advertising in the last 30 days**

```json
{ "boardTokens": ["stripe", "duolingo", "databricks", "cloudflare"], "postedWithinDays": 30, "maxItemsPerBoard": 10, "sortBy": "publishedDesc", "maxItems": 30 }
```

**Everything under Sales, using the company's own department tree**

```json
{ "boardTokens": ["databricks"], "departments": ["Sales"], "sortBy": "updatedDesc", "maxItems": 20 }
```

**Full posting text for a search index or an agent, few columns**

```json
{ "boardTokens": ["duolingo"], "includeDescription": true, "fields": ["company", "title", "url", "descriptionText"], "maxItems": 10 }
```

### Output

One row per posting. A real row, written by the run `uDIlCS5kneZZpDgUs` on 2026-09-20 with
`{"boardTokens": ["gitlab"], "withSalaryOnly": true, "maxItems": 20}`:

```json
{
  "boardToken": "gitlab",
  "company": "GitLab",
  "jobId": 8746461002,
  "title": "Senior Professional Services Engineer- PubSec - DC Metro",
  "url": "https://job-boards.greenhouse.io/gitlab/jobs/8746461002",
  "location": "Remote, United States",
  "isRemote": true,
  "departments": ["Sales", "Consulting Delivery"],
  "department": "Consulting Delivery",
  "departmentPath": "Sales > Consulting Delivery",
  "offices": [],
  "office": null,
  "salaryMin": 136080,
  "salaryMax": 230040,
  "salaryCurrency": "USD",
  "salaryRangeTitle": "United States Salary Range",
  "hasSalary": true,
  "firstPublishedAt": "2026-09-02T18:53:10.000Z",
  "updatedAt": "2026-09-18T20:46:24.000Z",
  "applicationDeadline": null,
  "requisitionId": "6881",
  "internalJobId": 6497900002,
  "language": "en",
  "found": true,
  "error": null,
  "sourceUrl": "https://boards-api.greenhouse.io/v1/boards/gitlab/jobs?pay_transparency=true",
  "fetchedAt": "2026-09-20T19:23:38.640Z"
}
```

| Field | Type | Always filled | Meaning |
|---|---|---|---|
| `boardToken` | string | yes | The company's board token, in lower case |
| `company` | string | for `found: true` | Company name as the board publishes it |
| `jobId` | number | for `found: true` | Greenhouse posting id (the `gh_jid` in apply links) |
| `title` | string | for `found: true` | Job title |
| `url` | string | for `found: true` | Where the posting is published — often the company's own careers site |
| `location` | string | almost always | Free text the employer typed (see [Locations are free text](#locations-are-free-text)) |
| `isRemote` | boolean | yes | Our text verdict, not a source flag |
| `departments` | string\[] | when the tree is readable | Department names from root to leaf |
| `department` | string | when the tree is readable | The leaf department |
| `departmentPath` | string | when the tree is readable | `departments` joined with `>` |
| `offices` | string\[] | only in the large response | Offices the posting hangs under, all of them |
| `office` | string | only in the large response | The first office of `offices` |
| `salaryMin` | number | when published | Lower end of the pay range, whole currency units (the API sends cents) |
| `salaryMax` | number | when published | Upper end of the pay range |
| `salaryCurrency` | string | when published | ISO currency code, e.g. `USD`, `GBP` |
| `salaryRangeTitle` | string | when published | The employer's label for the band, e.g. "United States Salary Range", "Zone 3 Pay Range" |
| `hasSalary` | boolean | yes | Whether a pay range was published for this posting |
| `firstPublishedAt` | string | for `found: true` | When the posting first appeared, ISO 8601 UTC |
| `updatedAt` | string | for `found: true` | When it last changed, ISO 8601 UTC |
| `applicationDeadline` | string | rarely | Deadline the employer set, ISO 8601 UTC |
| `requisitionId` | string | usually | The employer's own requisition number |
| `internalJobId` | number | for `found: true` | Greenhouse's internal id of the job behind the posting |
| `language` | string | usually | Language code of the posting |
| `descriptionHtml` | string | with `includeDescription` | The description as published, HTML |
| `descriptionText` | string | with `includeDescription` | The same text with entities decoded and tags stripped |
| `found` | boolean | yes | `false` marks a board token that returned nothing |
| `error` | string | on `found: false` | Why that token gave no postings |
| `sourceUrl` | string | yes | The exact API URL this row came from |
| `fetchedAt` | string | yes | When the run read the board, ISO 8601 UTC |

A board token the API does not serve produces one row like this instead of failing the run:

```json
{
  "boardToken": "zzz-no-such-board-xyz",
  "found": false,
  "error": "no Greenhouse board is served for the token \"zzz-no-such-board-xyz\" (the API answered 404). Check it against the company's board URL job-boards.greenhouse.io/<token> — the token is often not the company name.",
  "title": null,
  "hasSalary": false,
  "sourceUrl": "https://boards-api.greenhouse.io/v1/boards/zzz-no-such-board-xyz/jobs?pay_transparency=true",
  "fetchedAt": "2026-09-20T19:23:52.437Z"
}
```

Dataset views: **Open positions** (the everyday columns), **Published pay ranges** (only the money columns),
**Recently published** (first published, department, location — the hiring-signal view). A `SUMMARY` record in the
run's key-value store holds what each company returned, which filters were applied or corrected, and what was cut.

### Use it from code / agents

```bash
curl -X POST "https://api.apify.com/v2/acts/yadroo~greenhouse-jobs/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"boardTokens":["stripe"],"titleKeywords":["engineer"],"fields":["company","title","location","url"],"maxItems":20}'
```

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('yadroo/greenhouse-jobs').call({ boardTokens: ['gitlab'], withSalaryOnly: true, maxItems: 20 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

```python
from apify_client import ApifyClient
client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("yadroo/greenhouse-jobs").call(run_input={"boardTokens": ["databricks"], "departments": ["Sales"], "maxItems": 20})
items = client.dataset(run["defaultDatasetId"]).list_items().items
```

The dataset can also be downloaded as JSON, CSV, XLSX or XML from the run's dataset URL, and `fields` keeps the export
to the columns you need.

MCP: add `https://mcp.apify.com` to Claude / Cursor / any MCP client and call the `yadroo/greenhouse-jobs` tool with the
same JSON input.

### Pricing

Pay per event: **$0.001 per run start + $0.0015 per dataset row**. A row is one posting — or the marker row of a board
token that returned nothing. Nothing else is charged; `maxItems` is the hard ceiling on what a run can cost.

| Run | Cost |
|---|---|
| 25 rows (the default) | $0.0385 |
| 200 rows | $0.301 |
| 1000 rows | $1.501 |

Apify's paid plans discount every tier: Bronze −10 %, Silver −20 %, Gold and above −30 % on the per-row price.

### Limits & FAQ

**Which companies can I read?** Only those that publish through Greenhouse and leave the board's public API on — which
is the normal setup, because the company's own careers page reads the same API. There is no search across employers
here: you bring the board tokens.

**How fresh is it?** The API answers live, so a run sees the board as it is at that moment. Closed postings disappear
from the board, and this actor keeps no history — if you need one, run it on a schedule and keep the datasets, or use
`onlyNew`.

**Why does a posting have no pay range?** Because the employer did not publish one. Ranges come from the pay
transparency data an employer fills in, mostly under US, EU or UK disclosure rules; whole boards can be without them
(on 2026-09-20, GitLab published a range on 86 of 216 postings, Stripe on none of 665). `hasSalary` tells you which
case you are in. When an employer publishes several bands for one posting, the row carries the first one and
`salaryRangeTitle` names it.

**Why is `office` empty?** Offices only come with the large response, so a run without `includeDescription` and without
an `offices` filter leaves `offices`/`office` empty. `departmentPath` is always filled — that tree is read separately.

**Rate limits.** Greenhouse documents none for this API. The actor stays polite anyway: it reads the companies one
after another, two requests each, and retries with a growing delay on 429 and 5xx. If a board keeps refusing, that
company ends as a `found: false` row with the reason and the run continues.

**Empty result, no error?** That means the filters matched nothing on boards that were read fine — the log and the
`SUMMARY` record say how many postings each company had and how many the filters cut. Invalid input is different: a
value that is not a board token stops the run with a message naming it, and a token the API does not serve becomes a
`found: false` row.

**What is not collected.** No candidate, recruiter or applicant data — the Job Board API serves company postings only,
and this actor reads nothing else. It never touches the `/embed/` paths that the source's robots.txt disallows, uses no
login, no captcha solving and no proxy.

**If the source changes.** Should Greenhouse restrict or retire the public Job Board API, this actor will be marked
paused here rather than worked around.

***

Made by **Yadroo**. Sibling actors:
[hh-kz-vacancies](https://apify.com/yadroo/hh-kz-vacancies) ·
[github-repo-intel](https://apify.com/yadroo/github-repo-intel) ·
[domain-intel](https://apify.com/yadroo/domain-intel) ·
[google-news-search](https://apify.com/yadroo/google-news-search) ·
[rss-to-json](https://apify.com/yadroo/rss-to-json)

# Actor input Schema

## `boardTokens` (type: `array`):

One or more Greenhouse board tokens, e.g. `stripe`, `duolingo`, `gitlab`, `databricks`. The token is the last part of a company's board URL (`job-boards.greenhouse.io/<token>`, `boards.greenhouse.io/<token>`) - paste the whole URL and the token is taken from it. Each token costs one request; unknown tokens come back as a single row with `found: false` instead of failing the run.

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

Keep a posting when its title contains at least one of these words, case-insensitive, e.g. \["engineer", "data"]. Empty = every open posting of the board.

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

Drop a posting when its title contains any of these words, e.g. \["intern", "contract"]. Applied after `titleKeywords`.

## `locationContains` (type: `array`):

Keep postings whose location text contains one of these, case-insensitive, e.g. \["London", "India", "New York"]. Greenhouse stores the location as free text a company typed ("Seattle, San Francisco, New York City", "Remote - California", "Hybrid"), so match on city, state or country words rather than exact strings.

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

Keep postings whose department contains one of these at any level of the company's own department tree, e.g. \["Sales", "Engineering"]. The tree is read from the board, so a parent name ("Sales") also returns the postings of its children ("Enterprise Sales", "Sales Development").

## `offices` (type: `array`):

Keep postings attached to an office whose name contains one of these, e.g. \["Dublin", "Remote"]. Offices are the company's own office list and are often coarser than the location text. Setting this makes the actor ask for the large response that also carries the descriptions, so the run takes longer.

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

Keep only postings whose location or office text marks them as remote ("remote", "anywhere", "distributed", "work from home"). Greenhouse has no remote flag, so this is a text match on what the company wrote - `isRemote` in the output shows the same verdict for every row.

## `withSalaryOnly` (type: `boolean`):

Keep only postings that carry a pay range. Pay transparency is published per posting and mostly by employers under US, EU or UK disclosure rules, so a whole board can be without ranges.

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

Keep postings first published in the last N days - the "who started hiring recently" filter for hiring-signal and lead pipelines. Empty = no age limit.

## `sinceHours` (type: `integer`):

Keep postings whose `updatedAt` is inside the last N hours (UTC). Use it on a schedule to follow edits and re-posts, not only brand-new jobs. Empty = no limit.

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

Remember board token, job id and `updatedAt` in this actor's key-value store and emit only rows that were not there on the previous run. An edited posting counts as new because its `updatedAt` moved. The first run emits everything it finds.

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

Add `descriptionHtml` (as published) and `descriptionText` (entities decoded, tags stripped, blank lines kept) to every row, and ask the API for the richer response. Off by default because descriptions make rows ~20x larger.

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

Order of the rows. With several board tokens the rows are sorted across all of them, after `maxItemsPerBoard` was applied.

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

Stop after this many rows in total. A mid-size board has 50-500 open postings, the largest ones a few thousand.

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

Cap the rows taken from each board before the global `maxItems`, so one big employer cannot fill the whole dataset when you watch a list of companies. Empty = no per-company cap.

## `fields` (type: `array`):

Keep only these output fields, in this order, e.g. \["company", "title", "location", "url"]. Empty = all fields.

## Actor input object example

```json
{
  "boardTokens": [
    "stripe",
    "duolingo"
  ],
  "remoteOnly": false,
  "withSalaryOnly": false,
  "onlyNew": false,
  "includeDescription": false,
  "sortBy": "updatedDesc",
  "maxItems": 25
}
```

# Actor output Schema

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

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "boardTokens": [
        "stripe",
        "duolingo"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("yadroo/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 = { "boardTokens": [
        "stripe",
        "duolingo",
    ] }

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

```

## MCP server setup

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