DEV Community

Detroit Web Agent
Detroit Web Agent

Posted on

How to pull open jobs from Greenhouse, Lever and Ashby without scraping HTML

Most applicant-tracking systems publish their job boards as public JSON. No login, no HTML parsing, no headless browser. Here is how to use them, and when a ready-made tool is worth it.

The three public endpoints

ATS Endpoint Slug example
Greenhouse https://boards-api.greenhouse.io/v1/boards/{slug}/jobs?content=true gitlab
Lever https://api.lever.co/v0/postings/{slug}?mode=json palantir
Ashby https://api.ashbyhq.com/posting-api/job-board/{slug} ashby

The slug is the company's board name, visible in its careers-page URL (boards.greenhouse.io/gitlab, jobs.lever.co/palantir, jobs.ashbyhq.com/ashby).

Normalize them in ~20 lines of Python

import requests

def greenhouse(slug):
    r = requests.get(f"https://boards-api.greenhouse.io/v1/boards/{slug}/jobs", timeout=30).json()
    return [{"title": j["title"], "location": (j.get("location") or {}).get("name"),
             "url": j["absolute_url"], "posted": j.get("updated_at")} for j in r["jobs"]]

def lever(slug):
    r = requests.get(f"https://api.lever.co/v0/postings/{slug}", params={"mode": "json"}, timeout=30).json()
    return [{"title": j["text"], "location": j["categories"].get("location"),
             "url": j["hostedUrl"], "posted": j["createdAt"]} for j in r]

def ashby(slug):
    r = requests.get(f"https://api.ashbyhq.com/posting-api/job-board/{slug}", timeout=30).json()
    return [{"title": j["title"], "location": j.get("location"),
             "url": j["jobUrl"], "posted": j.get("publishedAt")} for j in r["jobs"]]

for fn, slug in [(greenhouse, "gitlab"), (lever, "palantir"), (ashby, "ashby")]:
    jobs = fn(slug)
    print(slug, len(jobs), jobs[0]["title"])
Enter fullscreen mode Exit fullscreen mode

Add a remote flag by checking the location string for "remote", and store posted as ISO dates (Lever gives epoch milliseconds).

Finding the slug from a domain

Fetch the company's /careers page and look for links to boards.greenhouse.io/, jobs.lever.co/ or jobs.ashbyhq.com/. Many sites embed the board in an iframe or script tag; the slug is in that URL. When there is no match, try the domain name itself as the slug.

When a ready-made tool beats your script

If you track hundreds of companies, you also need Workable, Recruitee and SmartRecruiters, retries, and de-duplication. Disclosure: I built an Apify actor for exactly that: ats-open-roles. Give it company slugs or domains, get normalized open roles back. It is pay-per-result at $0.002 per job, so 1,000 roles cost about $2. If you only need a handful of companies, the script above is the better deal.

Top comments (0)