Last checked
How to get all job postings from a Greenhouse job board
Greenhouse publishes every public job on a board through a keyless JSON API. Request https://boards-api.greenhouse.io/v1/boards/{board_token}/jobs?content=true and the response is one object per posting, with the title, location, absolute URL, and, when you pass content=true, the description HTML plus departments and offices.
The board token is the slug in the careers URL. For https://job-boards.greenhouse.io/gitlab the token is gitlab. No API key is required. This is the public Job Board API, not Greenhouse Harvest.
In short
- The token is the slug in the public board URL, such as gitlab in job-boards.greenhouse.io/gitlab.
- GET https://boards-api.greenhouse.io/v1/boards/{token}/jobs?content=true with no API key.
- Save id, title, absolute_url, location.name, updated_at, and, when content=true, departments, offices, and content.
- GET /v1/boards/{token}/jobs/{id} for a single public posting.
- One request returns the board. Cache it, and do not use the private Harvest API.
Find the board token
Open the company’s public Greenhouse board. The token is the first path segment after the host:
https://job-boards.greenhouse.io/gitlab→gitlabhttps://boards.greenhouse.io/gitlab→gitlab
Older links use boards.greenhouse.io. Newer links use job-boards.greenhouse.io. Both hosts point at the same token. If you only have the company name, you still need the token the company chose. Guessing it is unreliable.
A 404 from the API means that token is not a public board. It does not mean you should try another endpoint.
Fetch every posting
The list endpoint returns the whole public board in one response. It is not paginated. content=true adds the description, departments, and offices, and it makes the payload larger. Leave it off when you only want titles and links.
Python, using the standard library:
import json
from urllib.request import Request, urlopen
token = "gitlab"
url = f"https://boards-api.greenhouse.io/v1/boards/{token}/jobs?content=true"
request = Request(url, headers={"User-Agent": "job-board-example/1.0"})
with urlopen(request, timeout=60) as response:
payload = json.load(response)
jobs = payload["jobs"]
print(len(jobs))
print(jobs[0]["title"], jobs[0]["absolute_url"])
The same request in Node.js:
const token = "gitlab";
const url = `https://boards-api.greenhouse.io/v1/boards/${token}/jobs?content=true`;
const response = await fetch(url, {
headers: { "User-Agent": "job-board-example/1.0" },
});
if (!response.ok) {
throw new Error(`Greenhouse responded ${response.status}`);
}
const payload = await response.json();
console.log(payload.jobs.length);
console.log(payload.jobs[0].title, payload.jobs[0].absolute_url);
On 12 October 2026 the public GitLab board answered this list endpoint with a jobs array. Each item had id, title, absolute_url, location.name, and updated_at.
Fields worth keeping
| Field | What it is |
|---|---|
id | The public posting id. Use this to fetch one job later. |
title | The job title. |
absolute_url | The public posting page. |
location.name | The location string the board shows. It may say “Remote”. |
updated_at | When Greenhouse says the post was updated. |
departments | Present with content=true. A list of {id, name}. |
offices | Present with content=true. A list of office names. |
content | The description as HTML, only with content=true. |
internal_job_id is Greenhouse’s id for the underlying job. Prospect posts can have a null value there. You do not need it to link to the public page.
Treat content as HTML from the employer. If you only want words, strip the tags. Do not render that HTML in your own site without sanitizing it.
from html.parser import HTMLParser
class TextExtractor(HTMLParser):
def __init__(self):
super().__init__()
self.parts = []
def handle_data(self, data):
self.parts.append(data)
def text(self):
return " ".join("".join(self.parts).split())
def html_to_text(html):
parser = TextExtractor()
parser.feed(html or "")
return parser.text()
Read one posting
When you already have an id, fetch that posting instead of the whole board:
curl -fsS "https://boards-api.greenhouse.io/v1/boards/gitlab/jobs/8860302002"
That single-posting URL was checked on 12 October 2026. The body included title, content, departments, and offices. The id in a live board changes, so take the id from the list response rather than hard-coding this one.
Be a polite client
One HTTP request is enough for an entire public board. Cache the JSON. You do not need a loop, a browser, or a pool of IP addresses.
A few boards are large once descriptions are included. Download content=true when you need the text, then store it. Do not poll the endpoint in a tight loop.
This API is public and keyless. Greenhouse Harvest is a different API. Harvest uses a key from the employer’s own account and exposes recruiting data that is not on the job board. Do not point a board scraper at Harvest.
Also read the company’s terms for the board you are collecting. The API existing is not a separate permission to republish the text however you like.
When the script is the wrong tool
A script is enough for a handful of tokens you already know. It gets awkward when the list of companies changes every week, when you also need Lever, Ashby, and Workday in the same columns, or when you want a schedule without hosting the job yourself.
The Greenhouse Jobs Scraper takes the board URLs, returns the public postings in one shape, and runs on Apify. The price is on that page once it is published. The New Jobs Feed is the wider tool when the companies are not all on Greenhouse.
Written with AI assistance and checked by the Lepidata team.