# Greenhouse Jobs Scraper API — Full Descriptions (`wickfeed/greenhouse-jobs-scraper`) Actor

Track new Greenhouse job postings with full descriptions, from 872 verified boards built in — or just type a company name. Clean spreadsheet, no login. $1 per 1,000 jobs.

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

## Pricing

$1.00 / 1,000 job fetcheds

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).


# README

## Greenhouse Jobs Scraper API — Full Descriptions

*Unofficial — not affiliated with, endorsed by, or connected to Greenhouse. "Greenhouse" is a trademark of Greenhouse Software, Inc., used here only to describe what this tool reads.*

*Data note: job postings are employer-published public data and may contain recruiter or contact details inside the description text; treat that as personal data and don't use it for spam or unsolicited outreach.*

**Version 0.2.6 · live since 2026-07-15 · 872 Greenhouse boards verified 2026-07-15 · $1 per 1,000 jobs, $0 to start.**

**Live status:** https://api.apify.com/v2/key-value-stores/WickFeed~wickfeed-public-status/records/status.json — real self-test results for every wickfeed scraper (pass/fail plus 30-day success rate), updated hourly, no login needed. Opens as raw data (JSON).

**Choosing among the WickFeed tools:**
- Mixed systems, or you're not sure which one a company uses → [ATS Job Aggregator](https://apify.com/wickfeed/ats-job-aggregator)
- Greenhouse only → **Greenhouse Jobs Scraper (this page)**
- Ashby only, including published salary ranges → [Ashby Jobs Scraper](https://apify.com/wickfeed/ashby-jobs-scraper)
- Workable only → [Workable Jobs Scraper](https://apify.com/wickfeed/workable-jobs-scraper)
- Companies and their live open-job counts, rather than individual job rows → [ATS Company Discovery](https://apify.com/wickfeed/ats-company-discovery)

Paste a Greenhouse careers URL or a bundled company name, press **Start**, and get one row per open job — the description included as plain text (capped at 20,000 characters), and the source URL on every row for the complete posting. The 3-company demo costs at most **$0.15**. No Greenhouse login or API key is required — an Apify account is all you need to run the tool.

**From first click to download, four steps:**
1. Click **Try for free** and sign in to your Apify account.
2. Leave the demo boards exactly as prefilled and click **Start**.
3. When the run finishes, open **Dataset** to see one row per job.
4. Click **Export** to download the rows as CSV, Excel, or JSON.

### What it does

Every open job at the Greenhouse companies you choose, as a fresh snapshot each run — one tidy row per job, **the job description in the same row**. Pick boards, press Start. It reads Greenhouse's own **public Job Board API** (the same JSON a careers page loads) and writes a normalized row for each posting.

**Descriptions inline, in a single pass — at no extra cost.** Greenhouse returns the whole board, every posting with its description, in one response. This actor asks for that (`content=true`) and fills `descriptionText` on every row in that same fetch — so you get the write-up (responsibilities, requirements, and the rest) as plain text, **capped at 20,000 characters** as a memory-safety guard; each row also carries the source `url`, the route to the complete posting when a description runs past that cap. Turning descriptions on doesn't change the price: a flat **$1 per 1,000 jobs**. Dated, deduplicated rows with the description already attached are the substance here — you're not re-fetching a title and a link one posting at a time.

> **Demo cost, in full: at most about $0.15.** The form arrives prefilled with 3 demo boards (`gitlab`, `databricks`, `figma`). Press **Start** and the first run over those three boards costs a few cents — up to roughly **$0.15** — every penny of which Apify's **$5 monthly free credit** absorbs, so your first runs are free. And you pay strictly for jobs delivered: an empty, dead, or misspelled board is never charged.

Each run returns a **fresh, full snapshot** of every open role, not a comparison against yesterday. To have only the *new and changed* jobs pulled out automatically, use the [ATS Job Aggregator](https://apify.com/wickfeed/ats-job-aggregator) — the diff monitor for the family.

Run a few boards and you get rows like these (real postings, fetched 2026-07-15):

| Company | Title | Location | Remote | Posted |
|---|---|---|---|---|
| Stripe | Account Executive, AI Sales (Grower) | San Francisco, CA | — | 2026-06-02 |
| GitLab | AI Engineer | Remote, US | yes | 2026-05-29 |
| GitLab | Account Executive - Italy | Remote, Italy | yes | 2026-04-17 |
| Databricks | Account Executive | Bengaluru, India; Mumbai, India | — | 2023-12-12 |

*"—" means unknown: Greenhouse's public feed has no remote flag, so the actor marks `remote: yes` only when the location text itself says remote, and leaves it blank otherwise.*

Each row also carries a stable `id`, `department`, the public application `url`, the plain-text `descriptionText`, and a fetch timestamp.

### Paste a link, type a name — no token hunting

You don't need to know a company's "board token." In the **Companies** box, put whatever you already have — one company per line:

- **A careers page address.** Copy it straight from the browser: `https://boards.greenhouse.io/gitlab` (the `job-boards.greenhouse.io/...` form works too). This is the reliable path — the board id is right there in the address, so it always works.
- **A company name.** Type `GitLab`. Typed names are matched against the bundled list of **872 verified Greenhouse companies** — a starter directory, not every Greenhouse company. If yours isn't in it, paste the careers page address instead.
- **A bare board token.** `gitlab`, exactly as before. Existing setups and saved runs keep working unchanged.

```json
{ "sources": ["GitLab", "https://boards.greenhouse.io/databricks", "figma"] }
````

If an entry can't be read — a blank line, an address for a *different* hiring system (a Workable or Ashby link pasted here is caught and named), or a company name that isn't in the bundled list — that one line is skipped with a plain-English note in the run summary telling you exactly what to paste instead. It never silently runs empty, and the rest of your list still runs. The run only fails when *nothing* in the box could be used.

### Quick start

1. **Add companies.** Just press **Start** to use the three demo boards already filled in (`gitlab`, `databricks`, `figma`). Or paste careers-page addresses, type company names, or switch *Company list* to "Starter" (100 highest-volume boards) or "All verified" (all 872).
2. **(Optional) Narrow it.** Add keywords (e.g. `engineer`, `product`) or locations (e.g. `new york`, `remote`) to keep only matching jobs.
3. **Press Start.** By default each board delivers up to 50 jobs — the first 50 that match your filters (or just the first 50 it finds, if you set none) — so the first pass stays fast and inexpensive across however many boards you listed. Increase *Max results per board* once you've reviewed the output and want the complete set.

> **Calling the API?** Sending an empty input `{}` runs the very same three demo boards you see in the form, each capped at 50 jobs (about **$0.15** in total), so a first API call behaves exactly like pressing Start — never an empty run, and never a surprise-priced one.

### Bundled company lists

Choose a preset and press Start — no token hunting:

- **Starter — 100 boards**: the highest-volume Greenhouse boards (the ones with the most open jobs), for a rich sample run.
- **All verified — 872 boards**: every Greenhouse board in the bundle, each confirmed to return live postings against Greenhouse's own API on **2026-07-15**.

It's a starter corpus, not an exhaustive directory of every Greenhouse company. You can add your own boards by hand in the **Companies** box; the two lists merge and duplicates are removed.

### Output schema

One row per job, the same fields every time:

| Field | Meaning |
|---|---|
| `id` | Stable id `greenhouse:<token>:<jobId>` — deduplicate on this. |
| `ats` | Always `greenhouse` — the hiring system this row came from (kept so the schema matches the multi-ATS aggregator). |
| `companyToken` | The Greenhouse board slug this row came from (e.g. `gitlab`). |
| `company` | Greenhouse's `company_name` when present, else the title-cased token. |
| `title` | Job title. |
| `department` | First department when published, else null. |
| `location` | Location string as Greenhouse provides it. |
| `remote` | `true` only when the location text says remote; null when unknown (see note above). |
| `url` | Public application URL (`absolute_url`). |
| `postedAt` | Publish time (ISO-8601), or null. |
| `updatedAt` | Last update time (ISO-8601), or null. |
| `salaryRaw` | Always null — the public feed carries no pay field (see Limitations). |
| `descriptionText` | Plain text, capped at ~20,000 chars. |
| `fetchedAt` | When this run fetched the row. |
| `schemaVersion` | The output-record schema version (a number, currently `1`). See **Output contract** below. |

Turn on *Include raw HTML description* to also get `descriptionHtml`.

Two real records from a run over the `stripe` board (fetched 2026-07-15) — every field present on every row, honest `null`s where Greenhouse's public feed doesn't expose a value:

```json
[
  {
    "id": "greenhouse:stripe:7954688",
    "ats": "greenhouse",
    "companyToken": "stripe",
    "company": "Stripe",
    "title": "Account Executive, AI Sales",
    "department": "Account Executives (AI)",
    "location": "San Francisco, CA",
    "remote": null,
    "url": "https://stripe.com/jobs/search?gh_jid=7954688",
    "postedAt": "2026-06-02T12:58:57.000Z",
    "updatedAt": "2026-07-13T18:37:36.000Z",
    "salaryRaw": null,
    "descriptionText": "Stripe builds the economic infrastructure for the internet. As an Account Executive on the AI Sales team you will …",
    "fetchedAt": "2026-07-15T18:00:00.000Z",
    "schemaVersion": 1
  },
  {
    "id": "greenhouse:stripe:8042978",
    "ats": "greenhouse",
    "companyToken": "stripe",
    "company": "Stripe",
    "title": "Staff Software Engineer",
    "department": "Engineering",
    "location": "Remote - North America",
    "remote": true,
    "url": "https://stripe.com/jobs/search?gh_jid=8042978",
    "postedAt": "2026-07-02T07:24:50.000Z",
    "updatedAt": "2026-07-03T01:13:43.000Z",
    "salaryRaw": null,
    "descriptionText": "As a Staff Software Engineer you will design and build the systems that move money for millions of businesses …",
    "fetchedAt": "2026-07-15T18:00:00.000Z",
    "schemaVersion": 1
  }
]
```

`remote` is `true` on the second row only because its location text says "Remote"; the first row's is `null` (unknown) — Greenhouse's public feed has no explicit remote flag. `descriptionHtml` is added only when *Include raw HTML description* is on.

#### Output contract (schema stability)

A shipped update on our end shouldn't break your spreadsheet or your pipeline, so here is the promise set down in writing:

- **We never rename or remove an existing field within a major version.** The columns above — `id`, `title`, `url`, `descriptionText`, and the rest — keep both their names and their meaning, so you can build on every one of them safely.
- **A `null` is a placeholder, never a dropped field.** Everywhere Greenhouse's public feed exposes no value (`remote`, `salaryRaw`, and sometimes `department` or `postedAt`), the field is still present and set to `null` — it is never dropped, so a column you read is always there to read.
- **New fields are added, never forced.** We may *add* a field over time; adding one never disturbs the fields you already read, so your existing code keeps working untouched.
- **A breaking change is spelled out up front, not slipped in.** If a field ever had to be renamed or removed, that would be a major version bump — spelled out in the **Changelog** (and the Issues tab) before it lands, so it never turns up unannounced on your next scheduled run.
- **`schemaVersion` is a breaking-contract version.** Every row carries it (and it's in `RUN_SUMMARY` too). It is not the release number and not an exact-shape fingerprint: **adding a new field does not bump it** — it changes only when an existing field is renamed, removed, or retyped (a breaking change, which also bumps the major version). So while this number holds, every field you already read keeps its name, type, and meaning; new fields may have been added alongside. It is `1` today.
- **The field list is published for machines too.** The actor ships a machine-readable output schema (a JSON Schema of every field above), so a pipeline or AI agent can discover the exact row shape without parsing this table.

### Pricing

You pay only for jobs delivered: **$1.00 per 1,000 jobs** — that's **$0.001 a job** (the `job-fetched` event). Starting a run costs nothing, boards that are dead or return nothing are never charged, and you're billed for delivered rows only — not for run time, retries, or empty boards. Descriptions come inline in the same request, so turning them on doesn't change what you pay.

**In plain numbers:** at $0.001 a job, a run that returns **500 jobs costs about $0.50**, and **2,000 jobs costs about $2.00**. A board that's dead, misspelled, or has no open roles returns nothing and is **never charged**, so you only ever pay for jobs actually delivered.

**Free to try.** Apify's free plan ships with monthly usage credit that fully covers a first demo run, so you can review a complete sample — rows, descriptions, and all — before paying anything.

**For comparison.** Comparable single-system Greenhouse job actors on the store run about **$1.15–$4 per 1,000** (multi-system feeds run higher still), and most add a per-run start fee on top; this one is a flat **$1 per 1,000 with no start fee**. A few listings do price lower per job and charge a fixed start fee instead — for example `fetch_cat/greenhouse-jobs-scraper` (about **$0.03 per 1,000** plus a **$0.005 start fee**) and `memo23/greenhouse-jobs-scraper` (about **$0.79 per 1,000** plus a **$0.007 start fee**), both checked 2026-07-16 — so on a big pull they can come in under this one, while on a very small run the fixed fee can tip it the other way. Prices move, so compare on the current listings. Store prices as listed 2026-07-15; competitor prices checked 2026-07-16.

**If a charge ever looks wrong,** open an issue on the **Issues tab** with your run ID, and we investigate the same day. If we over-billed you, we file a credit request with Apify to put that amount back on your account. (Apify pays seller compensation as account credit rather than cash, so account credit is what we can promise and what we will do — not a guarantee about how Apify itself handles it.)

### Limitations (read this)

- **Public postings only.** It reads what a company publishes on its Greenhouse board — not private ATS data, internal reqs, or candidate information.
- **No structured salary.** Greenhouse's public feed has no salary field, so `salaryRaw` is always null. If a company writes pay into the job text, it appears inside `descriptionText`, not as a parsed number. If you need published salary ranges as a field, the *Ashby Jobs Scraper* below exposes them directly.
- **Remote is inferred, not declared.** Greenhouse's public feed has no explicit remote flag, so `remote` is `true` only when the location text says remote, and null otherwise — it is a best-effort read, not a guarantee.
- **You supply the companies.** No API lists every Greenhouse company, and this actor does not crawl. Use a bundled list, or read a token off a careers URL.
- **Unknown or misspelled tokens are reported as a per-board error, not silently swallowed.** A token that isn't a real Greenhouse board returns HTTP 404, so the actor isolates that one board as an error — it's skipped, never charged, and the rest of the run finishes normally — and lists it in the per-board error output. Check there if a board you expected is missing (a typo'd slug shows up as a 404). A *valid* board that simply has no open jobs is different: it returns a clean empty result, not an error.
- **Whole board in one response.** Greenhouse returns the entire board — every posting, with full descriptions — in a single response, so it isn't paginated. The actor handles the largest real boards (thousands of jobs, tens of MB) with a generous size guard; a pathologically huge board beyond that guard is isolated and reported, never silently truncated.
- **No server-side search.** The keyword, location, remote and date filters run over the whole board after fetching, and *Max results per board* then caps the matching jobs delivered (so a match is never hidden past the cap).
- **Politeness.** Requests are paced at about 1.3 per second with retries and backoff. Greenhouse rate-limits its API (HTTP 429); the actor stays under that limit and retries any 429 it does hit, so a throttled board still returns its jobs rather than being dropped. Very large runs take proportionally longer.
- **Endpoint drift.** If Greenhouse changes its response shape, the reader may need an update; a bad board is isolated and reported, never fatal to the rest of the run.

#### What it does not do

- **It does not touch private or candidate data.** It reads only jobs a company has published publicly on its Greenhouse board — never applicant data, internal reqs, or anything behind a login. It stays on the public `boards-api.greenhouse.io` endpoint and never touches the candidate portal.
- **It does not log in or use any key.** Greenhouse's Job Board API is public: no account, no password, no API key.
- **It does not discover companies for you.** No public index lists every Greenhouse company, and this actor does not crawl to find them — you supply the board tokens (or use a bundled list).
- **It does not invent salary.** Greenhouse's public jobs feed has no structured pay field, so the `salaryRaw` column is always empty (see the salary limitation above).
- **It does not break on page redesigns.** It reads the JSON feed, not the rendered HTML, so a careers-page restyle doesn't affect it.

### FAQ

#### I got few or zero jobs back — is it broken?

Almost never a real fault — nearly always a filter or the per-board cap. Take the plain checks first, in order: clear your **Keywords** and **Locations**, turn **Remote only** off, and raise **Max results per board** (it defaults to 50 per board). Next, open the run's own **Status** and **Logs** and look for a mistyped board token or a board that returned nothing. If it still looks wrong after that, open the **Issues tab** with your run link and we'll look into it.

*Advanced diagnostics: the complete per-board breakdown — fetched, matched, delivered, filtered, and every board error — is written to the run's key-value store as `RUN_SUMMARY`, and any rows that failed schema validation are collected in `REJECTS`; a typo'd token shows up there as a 404. When a board has more matching jobs than the cap, its row shows how many matched, so you can raise *Max results per board* to get the rest.*

#### Does it scrape HTML?

No. It calls Greenhouse's public Job Board API and reads JSON directly — the same call a browser makes when it renders a careers page, and nothing more. No proxies, no headless browser, no rendered-page parsing.

#### Why is the salary column empty?

Because Greenhouse's public feed has no salary field. This actor never guesses or fabricates pay, so `salaryRaw` is always null. For published salary ranges as a real field, use the *Ashby Jobs Scraper*.

#### Do I get the job description?

Yes — the actor requests the board with descriptions included, so `descriptionText` is filled in on every row in a single pass, at no extra cost. It's plain text, capped at 20,000 characters as a memory-safety guard; if a posting's description runs longer than that, the row's source `url` links to the complete posting.

#### Is this allowed?

It reads the very same public JSON feed your browser loads when it opens a Greenhouse careers page — no login, no password, no API key, and no HTML scraping. It only ever touches jobs a company chose to publish publicly on its Greenhouse board, never candidate or private data. You decide how you use the results, and you are responsible for that use — for example, don't use it for spam or unsolicited outreach. This is a description of how the tool works, not legal advice.

#### Can I schedule daily runs?

Yes, with Apify's scheduler. Each run re-fetches from scratch, so a daily schedule delivers a fresh, complete snapshot of every open role every day — it is a full re-pull, not a comparison against yesterday. If you'd rather receive only the *new and changed* jobs, pulled out for you automatically (true alerts that charge only for what changed), the [Jobs Scraper API](https://apify.com/wickfeed/ats-job-aggregator) is the diff monitor for that.

#### What happens to a dead or misspelled board?

A misspelled or non-Greenhouse token returns HTTP 404, so that one board is reported as an error in the per-board results (skipped, never charged) while the rest of the run finishes normally — check the per-board errors if a board you expected is missing. A real board that just has no open jobs comes back as a clean empty result instead. Either way, you're never charged for a board that delivered no jobs.

### Use with AI agents (MCP)

AI agents can drive this actor as a tool — **Claude Desktop, Cursor, n8n, LangGraph, CrewAI**, or any other MCP-compatible client — through **Apify's hosted MCP server** at `mcp.apify.com`. An agent locates it with `search-actors`, inspects its full input with `fetch-actor-details`, and runs it with `call-actor`. Point your MCP client at Apify's server:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com"
    }
  }
}
```

That form uses OAuth sign-in; to pass a token instead, add a `"headers": { "Authorization": "Bearer <YOUR_APIFY_TOKEN>" }`. Output is flat, stable JSON built for machine consumption, and agentic pay-per-event billing is enabled.

### Integrations

Download the full results as **CSV, Excel, or Google Sheets — no code required**. Or wire it up: it works with n8n, Make, and Zapier through Apify's native integrations, so you can route the dataset into Slack, a webhook, a Google Sheet, or a pipeline of your own.

### Changelog

- **0.2.6 — 2026-07-16** — Added a public live-status link (real self-test results, no login).
- **0.2.5 — 2026-07-16** — **Filters now run over the whole board before the cap, plus honesty fixes.** *Max results per board* used to cap the raw job list *before* your keyword, location, remote, and date filters ran, so a matching job sitting past the cap could be silently missed. Now the filters run over the whole board first and the cap limits the *matching* jobs delivered, so a match is never hidden past the cap. Runs with no filters behave exactly as before (still the first N jobs), the 64 MB large-board size guard is untouched, and every per-board cost ceiling is the same (at most the cap × $0.001 per board; the 3-board demo still tops out around $0.15). An unreadable *Posted after* date is now rejected with a plain-English message instead of quietly turning the date filter off. The actor also publishes a machine-readable output schema (JSON Schema) so a pipeline or agent can discover the row shape, and the `schemaVersion` wording is corrected to a breaking-contract version (adding a field does not bump it). No price, count, or cap value changed.
- **0.2.4 — 2026-07-16** — **Honest description limits, a lowercase-name fix, and a benefit-first page.** The description claim is now qualified everywhere: `descriptionText` (and the optional `descriptionHtml`) is plain text capped at 20,000 characters as a memory-safety guard, and each row's source `url` is named as the route to the complete posting when a description runs past that cap. A company name typed in lowercase now resolves to the same board as its normal-case spelling — previously a lowercase one-word name could be taken as a raw board token and miss the bundled listing. The page now leads with the benefit and moves the "what it does not do" list down under Limitations; added a four-step click-to-file walkthrough, a WickFeed tool chooser, plain snapshot-vs-diff wording (pointing to the ATS Job Aggregator for new-and-changed alerts), and a novice-first troubleshooting answer. No price, count, cap value, or run behavior changed — the 20,000-character cap has always been in the code.
- **0.2.3 — 2026-07-16** — **Each page now speaks in its own voice.** Rewrote the sections this README used to share word-for-word with the Ashby and Workable pages — pricing, the output contract, the FAQ, the AI-agents (MCP) and integrations sections, and the footer — in this scraper's own thorough voice. Every fact, price, and promise stays the same, and nothing about how a run behaves changed.
- **0.2.2 — 2026-07-16** — **Error messages now point you to the right scraper.** Paste a careers link for a different hiring system and the message now names the product that reads it — our Ashby or Workable Jobs Scraper for those, or our all-in-one Jobs Scraper + Job Monitor that covers all five systems (Greenhouse, Lever, Ashby, Workable, Recruitee) in one run; paste your company's own website and it points you to that aggregator, which can take the company name and look for the board. The Companies box now also states the first-run cost ceiling (about $0.15 for the prefilled 3-board demo). No change to how a valid run behaves.
- **0.2.1 — 2026-07-16** — **Paste a link or type a name — no token hunting.** The Companies box now accepts a pasted careers-page address (`https://boards.greenhouse.io/gitlab`, or the `job-boards.greenhouse.io` form) or a plain company name (matched against the 872 bundled verified companies), alongside the bare board tokens it always took — existing setups and saved runs are unchanged. A link for a different hiring system (Workable, Ashby, Lever, …), a company homepage, junk, or a name that isn't bundled is caught and reported in plain English with the exact thing to paste instead, never a silent empty run. Also: every output row now carries a `schemaVersion` field (currently `1`) and this README documents the **output contract** (existing fields are never renamed or removed within a major version; `null` means honestly-absent, not missing) — additive, so existing pipelines are unaffected. And a bare `{}` API call now runs the same three demo boards the form prefills (≈ $0.15) instead of failing, so a first call can't surprise-bill.
- **0.1.0** — Initial release. Reads Greenhouse's public Job Board API with `content=true` so full descriptions arrive inline in one pass; one normalized row per job, 872 verified-live boards bundled (plus a 100-board starter preset), keyword / location filters, per-board 404 isolation, 429-aware pacing (~1.3 requests/second) with retries and backoff, a large-board size guard, schema-validated output, and pay-per-event billing at $1 per 1,000 jobs ($0 to start a run).

### Related actors

- **Need 5 hiring systems in one feed?** The [Jobs Scraper API](https://apify.com/wickfeed/ats-job-aggregator) covers Greenhouse, Lever, Ashby, Workable and Recruitee in this same schema. Start with its **`starter-500` preset** — 500 verified-live boards in one click — and turn on **diff mode** to track only new and changed jobs on a daily schedule (and pay **$0** on a quiet day).
- **Want published salary ranges as a field?** The [Ashby Jobs Scraper](https://apify.com/wickfeed/ashby-jobs-scraper) uses this same clean schema and carries salary when the company publishes it.
- **Just want Workable companies?** The [Workable Jobs Scraper](https://apify.com/wickfeed/workable-jobs-scraper) uses the same schema for Workable.
- **Not sure which companies use a given system?** [ATS Company Discovery](https://apify.com/wickfeed/ats-company-discovery) finds live boards and their current open-job counts across five systems.

**Need help? Open an Issue with your run link.** If a run ever misbehaves or a charge looks off, open the **Issues tab** and paste your run link — we fix buyer-facing breakages the same day, so that's the fastest route to a fix.

# Actor input Schema

## `preset` (type: `string`):

Optional verified board list. Leave this on "None" for the 3-company demo in the Companies box below. Choose "Starter" for the 100 highest-volume boards, or "All verified" for all 872 live Greenhouse boards. A preset is merged with anything you add in the Companies box (duplicates removed). The dropdown shows the maximum first-run cost at the current 50-job cap; lower "Max results per board" to reduce it.

## `sources` (type: `array`):

One company per line — however you already have it, no board-token hunting required. Paste a careers page address (like https://boards.greenhouse.io/gitlab), type a company name (like GitLab), or enter a bare board token (gitlab) — the actor works out which and finds the board. A typed name is matched against the bundled list of 872 verified Greenhouse companies (a starter directory, not every Greenhouse company); if yours isn't in it, paste its careers page address — that always works. Optional when a preset is selected above — the two lists are merged (duplicates removed). A blank line, an address for a different hiring system, or a name that isn't in the list is skipped with a plain-English note (never fatal); the run fails only when nothing usable remains. Each board is capped at 50 jobs per run by default at $1 per 1,000 jobs, so the prefilled 3-board run tops out around $0.15.

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

Keep only jobs whose title, department or description contains ANY of these (case-insensitive). Leave empty for no keyword filtering.

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

Keep only jobs whose location contains ANY of these substrings (case-insensitive). Leave empty for no location filtering.

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

Keep only jobs whose location text says remote (Greenhouse's public boards feed has no explicit remote flag, so this matches on the location string). Heads-up: jobs whose location doesn't mention remote are also dropped while this is on, so a board that doesn't spell out "remote" can come back empty — leave this off and filter by Location if you're not sure.

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

Pick a date (year-month-day, e.g. 2026-01-01). Keeps only jobs posted on or after it. Jobs with no known post date are left out while this is set, so leave it blank to keep everything. An unrecognized date is rejected with a plain-English message, never silently ignored.

## `maxResultsPerSource` (type: `integer`):

Cap on the number of MATCHING jobs delivered per board. Your keyword, location, remote and date filters run over the whole board first, then the matches are capped — so a matching job is never hidden past the cap. Defaults to 50 (matching the prefilled value), so a first run stays fast and cheap — and clearing this field falls back to 50, never a surprise larger run. Raise it once you're happy with the output.

## `includeHtml` (type: `boolean`):

Also include the original HTML job description as descriptionHtml (capped at 20,000 characters, the same memory-safety guard as the plain text). Off by default to keep records lean. The plain-text descriptionText is always included.

## Actor input object example

```json
{
  "preset": "none",
  "sources": [
    "GitLab",
    "https://boards.greenhouse.io/databricks",
    "figma"
  ],
  "keywords": [
    "engineer",
    "product"
  ],
  "locations": [
    "remote",
    "new york"
  ],
  "remoteOnly": false,
  "maxResultsPerSource": 50,
  "includeHtml": false
}
```

# 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 = {
    "preset": "none",
    "sources": [
        "gitlab",
        "databricks",
        "figma"
    ],
    "maxResultsPerSource": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("wickfeed/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 = {
    "preset": "none",
    "sources": [
        "gitlab",
        "databricks",
        "figma",
    ],
    "maxResultsPerSource": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("wickfeed/greenhouse-jobs-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "preset": "none",
  "sources": [
    "gitlab",
    "databricks",
    "figma"
  ],
  "maxResultsPerSource": 50
}' |
apify call wickfeed/greenhouse-jobs-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=wickfeed/greenhouse-jobs-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Greenhouse Jobs Scraper API — Full Descriptions",
        "description": "Track new Greenhouse job postings with full descriptions, from 872 verified boards built in — or just type a company name. Clean spreadsheet, no login. $1 per 1,000 jobs.",
        "version": "0.2",
        "x-build-id": "Mgb4mxO48G0B2lDH0"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/wickfeed~greenhouse-jobs-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-wickfeed-greenhouse-jobs-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/wickfeed~greenhouse-jobs-scraper/runs": {
            "post": {
                "operationId": "runs-sync-wickfeed-greenhouse-jobs-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/wickfeed~greenhouse-jobs-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-wickfeed-greenhouse-jobs-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "preset": {
                        "title": "Company list",
                        "enum": [
                            "none",
                            "starter",
                            "all"
                        ],
                        "type": "string",
                        "description": "Optional verified board list. Leave this on \"None\" for the 3-company demo in the Companies box below. Choose \"Starter\" for the 100 highest-volume boards, or \"All verified\" for all 872 live Greenhouse boards. A preset is merged with anything you add in the Companies box (duplicates removed). The dropdown shows the maximum first-run cost at the current 50-job cap; lower \"Max results per board\" to reduce it.",
                        "default": "none"
                    },
                    "sources": {
                        "title": "Companies (name, careers URL, or board token)",
                        "type": "array",
                        "description": "One company per line — however you already have it, no board-token hunting required. Paste a careers page address (like https://boards.greenhouse.io/gitlab), type a company name (like GitLab), or enter a bare board token (gitlab) — the actor works out which and finds the board. A typed name is matched against the bundled list of 872 verified Greenhouse companies (a starter directory, not every Greenhouse company); if yours isn't in it, paste its careers page address — that always works. Optional when a preset is selected above — the two lists are merged (duplicates removed). A blank line, an address for a different hiring system, or a name that isn't in the list is skipped with a plain-English note (never fatal); the run fails only when nothing usable remains. Each board is capped at 50 jobs per run by default at $1 per 1,000 jobs, so the prefilled 3-board run tops out around $0.15.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "keywords": {
                        "title": "Keywords",
                        "type": "array",
                        "description": "Keep only jobs whose title, department or description contains ANY of these (case-insensitive). Leave empty for no keyword filtering.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "locations": {
                        "title": "Locations",
                        "type": "array",
                        "description": "Keep only jobs whose location contains ANY of these substrings (case-insensitive). Leave empty for no location filtering.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "remoteOnly": {
                        "title": "Remote only",
                        "type": "boolean",
                        "description": "Keep only jobs whose location text says remote (Greenhouse's public boards feed has no explicit remote flag, so this matches on the location string). Heads-up: jobs whose location doesn't mention remote are also dropped while this is on, so a board that doesn't spell out \"remote\" can come back empty — leave this off and filter by Location if you're not sure.",
                        "default": false
                    },
                    "postedAfter": {
                        "title": "Posted after",
                        "type": "string",
                        "description": "Pick a date (year-month-day, e.g. 2026-01-01). Keeps only jobs posted on or after it. Jobs with no known post date are left out while this is set, so leave it blank to keep everything. An unrecognized date is rejected with a plain-English message, never silently ignored."
                    },
                    "maxResultsPerSource": {
                        "title": "Max results per board",
                        "minimum": 1,
                        "maximum": 100000,
                        "type": "integer",
                        "description": "Cap on the number of MATCHING jobs delivered per board. Your keyword, location, remote and date filters run over the whole board first, then the matches are capped — so a matching job is never hidden past the cap. Defaults to 50 (matching the prefilled value), so a first run stays fast and cheap — and clearing this field falls back to 50, never a surprise larger run. Raise it once you're happy with the output.",
                        "default": 50
                    },
                    "includeHtml": {
                        "title": "Include raw HTML description",
                        "type": "boolean",
                        "description": "Also include the original HTML job description as descriptionHtml (capped at 20,000 characters, the same memory-safety guard as the plain text). Off by default to keep records lean. The plain-text descriptionText is always included.",
                        "default": false
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
