Greenhouse Job Board API

Read Greenhouse job postings from any boards.greenhouse.io site as structured JSON. Greenhouse is the ATS behind the career pages of thousands of companies, including Stripe, Airbnb, GitLab and Anthropic, and every posting it serves carries the employer's own first-published and last-updated timestamps. That makes this the rare jobs feed with honest change detection: set updatedAfter to a window like 25h on a daily schedule and each run returns only what the employer touched since yesterday, with no seen-list or delta store to maintain. Give it board tokens or URLs, or nothing at all and it sweeps a bundled directory of 4,400 plus verified Greenhouse boards to map who hires through the platform, live-verified with current open-role counts.

MEDIAN LAGon demand
PARAMETERS11
TOTAL USERS1
MONTHLY ACTIVE1
TOTAL RUNS48
RATINGno ratings yet
LAST MODIFIED2026-08-27
PUBLISHED2026-08

Input parameters

PARAMETERTYPEREQDEFAULTDESCRIPTION
companies string[] no Greenhouse board tokens or URLs, mixed freely. A bare token (stripe), a board URL on boards.greenhouse.io or job-boards.greenhouse.io, an embed URL, or a single job URL all work. Empty sweeps the bundled directory instead.
outputMode enum no jobs jobs · urlsOnly · companiesOnly. Full records, the cheap link index, or the company discovery directory.
updatedAfter string no The change-detection cutoff. A window like 24h or 7d, or an ISO date, compared against the employer's own last-updated timestamp. A daily schedule with 25h returns only what changed since the last run, with no state to manage anywhere.
publishedAfter string no Same grammar, against the first-published date, for a genuinely-new-roles feed rather than an any-change feed.
titleKeywords string[] no Keep only jobs whose title contains any of these. All filters run before billing, so a filtered job costs nothing.
departments string[] no Keep only jobs in matching departments, using the board's own department objects.
locationKeywords string[] no Keep only jobs whose location or office names contain any of these.
includeDescriptionMarkdown boolean no true The posting converted to clean Markdown, the format AI pipelines ingest without cleanup. HTML and plain-text variants are separate toggles.
includeQuestions boolean no false Fetch each job's application form questions, plus the compliance, demographic and location question sets, at one extra request per job. — drives your bill
maxJobs integer no 100 Hard ceiling on rows across the whole run, the main cost control. 0 means unlimited. — drives your bill
maxCompanies integer no 25 Cap on companies processed in discovery sweeps. — drives your bill

Worked examples

Basic — two boards, engineering roles only
{
  "companies": ["gitlab", "stripe"],
  "titleKeywords": ["engineer"],
  "maxJobs": 25
}
Daily change feed — the shape to put on a schedule, zero state
{
  "companies": ["gitlab", "stripe", "duolingo"],
  "updatedAfter": "25h",
  "maxJobs": 200
}
Companies using Greenhouse — the live directory, largest boards first
{
  "outputMode": "companiesOnly",
  "maxCompanies": 100
}
Markdown for an AI agent — descriptions the model can read cheaply
{
  "companies": ["anthropic"],
  "includeDescriptionMarkdown": true,
  "maxJobs": 25
}
Application questions — the form fields behind each posting
{
  "companies": ["gitlab"],
  "includeQuestions": true,
  "maxJobs": 10
}
POWER-USER TIP
updatedAfter is the feature to build on — Greenhouse publishes the employer's own last-updated and first-published timestamps on every posting, which most ATS feeds do not. That is what lets a daily schedule with updatedAfter set to 25h behave as a complete change feed with nothing to store between runs. Use publishedAfter instead when you only want genuinely new roles and edits should not wake your pipeline.
POWER-USER TIP
Salary fields are honest, and often empty — Employers rarely publish structured pay ranges on Greenhouse, so salaryRaw passes through only what the board actually serves, and salaryDerived is a deterministic parse of the description text with its source labelled. Expect derived coverage on a minority of postings, and read the source field before trusting a number. Nothing is inferred by a model.
POWER-USER TIP
EU-hosted boards are detected, not scraped — A small number of Greenhouse customers sit on the EU data silo at job-boards.eu.greenhouse.io, which the public API base does not serve. Those boards come back as clearly labelled error rows rather than empty results, and error rows are never charged, so an EU tenant in your list costs nothing and tells you exactly why it produced no jobs.

Tasks

Saved runs with the inputs already filled in — each one a standalone page and a working configuration example.

Request a task →

Code

curl

curl -X POST "https://api.apify.com/v2/acts/johnvc~greenhouse-job-board-api/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"companies":["gitlab","stripe"],"titleKeywords":["engineer"],"maxJobs":25}'

Python

from apify_client import ApifyClient

client = ApifyClient("APIFY_TOKEN")
run = client.actor("johnvc/greenhouse-job-board-api").call(
    run_input={
        "companies": ["gitlab", "stripe"],
        "updatedAfter": "7d",
        "maxJobs": 50,
    }
)
for job in client.dataset(run.default_dataset_id).iterate_items():
    print(job.get("dateUpdated"), job.get("title"), "-", job.get("companyName"))

What people use it for

Alternatives