How to scrape Greenhouse jobs without a headless browser
If you're about to point Playwright or Selenium at a Greenhouse careers page, stop. Every Greenhouse job board is backed by a public JSON API that returns the same jobs the page shows. You don't need a browser, a login or an API key. The only hard part is finding the company's board token.
Step 1: find the board token
The token is the slug Greenhouse uses for the company's board. Look for it in these places, in this order:
- The board URL.
boards.greenhouse.io/figmaorjob-boards.greenhouse.io/figmameans the token isfigma. - Links on the company's own careers page. Many companies host jobs on their own domain but link each job to
boards.greenhouse.io/{token}/jobs/{id}, or add?gh_jid={id}to their own URLs. Either one tells you it's Greenhouse. - The embed script. Embedded boards load
boards.greenhouse.io/embed/job_board/js?for={token}. Thefor=value is the token. - A guess from the domain.
figma.comโfigmaoften works, but a match on a generic name could be a different company. Check it (see below).
Confirm a token by calling GET https://boards-api.greenhouse.io/v1/boards/{token}. It returns the board's name, so you can check that it's the company you meant. A wrong token returns 404.
Step 2: read the jobs
GET https://boards-api.greenhouse.io/v1/boards/{token}/jobs returns every published job, with meta.total as the count. Add content=true for the description, departments and offices. For pay ranges, call /jobs/{id}?pay_transparency=true per job.
import re, requests
LINK = re.compile(r"(?:boards|job-boards)(?:\.eu)?\.greenhouse\.io/(?:embed/job_board(?:/js)?\?for=)?([A-Za-z0-9_-]+)")
def find_token(careers_url):
html = requests.get(careers_url, timeout=20, headers={"User-Agent": "Mozilla/5.0"}).text
for token in dict.fromkeys(LINK.findall(html)):
if token in ("embed", "v1"):
continue
r = requests.get(f"https://boards-api.greenhouse.io/v1/boards/{token}", timeout=20)
if r.status_code == 200:
return token, r.json().get("name")
return None, None
token, name = find_token("https://www.figma.com/careers/")
print(token, name)
if token:
jobs = requests.get(f"https://boards-api.greenhouse.io/v1/boards/{token}/jobs",
timeout=20).json()
print(jobs["meta"]["total"], "jobs on the board")
Gotchas
- Some careers pages render jobs with JavaScript, so the raw HTML has no Greenhouse links. Try
/careers,/jobsand the homepage. If none of them link to a board, the company may use a different ATS. - Descriptions are HTML-escaped. With
content=true,contentcomes back with entities like<p>. Unescape it (html.unescape) before you strip tags. updated_atis not the posting date. Usefirst_publishedfor "new in the last N days".- The job post
idis not the job'sinternal_job_id. One job can have several posts (for example in different locations). Decide whether you count posts or jobs. - Be polite. One request per board gets every job, so you don't need to page through anything or hammer the API.
The same pattern works for Lever and Ashby. Workday is different. All of them are covered on our Greenhouse, Lever, Ashby and Workday jobs API page.
We built this
Disclosure: Your Evenings Ltd built and sells this. If you have a list of domains rather than tokens, Greenhouse, Lever & Ashby Jobs API on the Apify Store does the token hunt for you:
- It takes the token or board URL when you have one. Otherwise it guesses tokens from the domain and checks each board's company name and job-link domains. If that fails, it reads up to 4 pages of the company's site looking for board links.
- Each row reports
detectionMethodanddetectionConfidence, so you can tell a verified match from a guess. - It covers Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee and Workday. Companies it can't find are returned as
not_foundand aren't charged. - Filters run before you're charged, and a
COMPANY-STATUSfile accounts for every input line.
Worked example with filters and completeness checks: Get only the jobs you want from Greenhouse, Lever and Ashby boards (and prove you didn't miss any) on dev.to.
FAQ
Do I need a browser to scrape Greenhouse jobs? No. The public Job Board API returns the published jobs as JSON with a plain GET request.
How do I find a company's Greenhouse board token?
From its board URL, from Greenhouse links or gh_jid parameters on its careers page, or from the for= value in the embed script. Confirm it with /v1/boards/{token}.
Why does the API return 404? The token is wrong, or the company doesn't use Greenhouse.
Product names and logos belong to their owners. Your Evenings Ltd is not affiliated with or endorsed by them.