Greenhouse, Lever, Ashby and Workday jobs API: the free endpoints and their gotchas
Greenhouse, Lever and Ashby each publish a public, keyless JSON feed of a company's open jobs. You need the company's board token, which is usually the last part of its job-board URL. Workday works differently, and has its own section below.
The endpoints
| ATS | Endpoint | Shape |
|---|---|---|
| Greenhouse | GET https://boards-api.greenhouse.io/v1/boards/{token}/jobs (?content=true adds descriptions, departments, offices) |
{"jobs": [...], "meta": {"total": N}} |
| Lever | GET https://api.lever.co/v0/postings/{token}?mode=json (EU accounts: api.eu.lever.co) |
bare JSON array |
| Ashby | GET https://api.ashbyhq.com/posting-api/job-board/{token} (?includeCompensation=true adds pay) |
{"apiVersion": "1", "jobs": [...]} |
Gotchas
- No server-side date filter on any of them. Lever filters
location,commitment,team,departmentandlevelon the server, but not dates. Download the board and filter locally. - Use the right date field. Greenhouse's
updated_atchanges whenever a post is edited. Usefirst_published, which the list endpoint currently returns, though the docs only show it on the single-job endpoint. Lever's equivalent iscreatedAt(epoch milliseconds); Ashby's ispublishedAt. - Greenhouse pay ranges are per job. Call
/jobs/{id}?pay_transparency=trueto getpay_input_ranges. - Lever has two hosts. An EU company returns 404 on
api.lever.co, so tryapi.eu.lever.co. - Ashby has unlisted jobs. Skip jobs where
isListedisfalse. - A bad token is a 404. All three returned 404 for a made-up token in our test on 22 Oct 2026. That means "wrong token or wrong ATS", not "not hiring".
import requests
def greenhouse(token):
r = requests.get(f"https://boards-api.greenhouse.io/v1/boards/{token}/jobs", timeout=20)
r.raise_for_status()
return [{"id": j["id"], "title": j["title"], "location": j["location"]["name"],
"posted": j.get("first_published"), "url": j["absolute_url"]} for j in r.json()["jobs"]]
def lever(token, eu=False):
host = "api.eu.lever.co" if eu else "api.lever.co"
r = requests.get(f"https://{host}/v0/postings/{token}?mode=json", timeout=20)
r.raise_for_status()
return [{"id": p["id"], "title": p["text"], "location": p["categories"].get("location"),
"posted": p.get("createdAt"), "url": p["hostedUrl"]} for p in r.json()]
def ashby(token):
r = requests.get(f"https://api.ashbyhq.com/posting-api/job-board/{token}", timeout=20)
r.raise_for_status()
return [{"id": j["id"], "title": j["title"], "location": j["location"],
"posted": j.get("publishedAt"), "url": j["jobUrl"]}
for j in r.json()["jobs"] if j.get("isListed", True)]
for name, jobs in [("greenhouse:stripe", greenhouse("stripe")),
("lever:spotify", lever("spotify")),
("ashby:ramp", ashby("ramp"))]:
print(name, len(jobs), jobs[0]["title"] if jobs else "-")
For daily "new jobs" alerts, diff job IDs against yesterday's run rather than trusting dates, because reposted jobs get fresh dates. To find tokens, see how to scrape Greenhouse jobs without a browser.
Workday jobs API
Workday has no public job-board feed like the three above. Each career site ({tenant}.wd{N}.myworkdayjobs.com/{site}) loads its jobs from its own search endpoint, which you can call too: POST /wday/cxs/{tenant}/{site}/jobs with a JSON body. It isn't documented as a public API, so expect changes, keep requests slow and respect the site's robots.txt.
What trips people up:
- 2,000 results per search, 20 per page. After that, later pages repeat the first page, and
totalis capped at 2,000 (and is 0 on pages after the first). For bigger boards, split the search by the facets the site offers (job family, location) and merge the results by job ID. - Posted dates are approximate. The listing only gives relative text such as "Posted 3 Days Ago", and older jobs show "Posted 30+ Days Ago". Converting that gets you to within about a day, at best.
- Some rows are empty stubs. Skip postings with no title or link.
import time, requests
def workday(host, tenant, site, search=""):
url = f"https://{host}/wday/cxs/{tenant}/{site}/jobs"
jobs, offset, total = [], 0, None
while True:
r = requests.post(url, json={"appliedFacets": {}, "limit": 20, "offset": offset,
"searchText": search}, timeout=20)
r.raise_for_status()
data = r.json()
if total is None:
total = data["total"] # only reliable on the first page
page = data["jobPostings"]
if not page:
break
jobs += [{"title": j["title"], "location": j.get("locationsText"),
"posted": j.get("postedOn"), "path": j["externalPath"]}
for j in page if j.get("title") and j.get("externalPath")] # skip non-public stubs
offset += 20
time.sleep(1)
if offset >= min(total, 2000):
break
return total, jobs
total, jobs = workday("TENANT.wd5.myworkdayjobs.com", "TENANT", "SITE")
print(total, "on the board,", len(jobs), "read")
print(jobs[:3])
We built this
Disclosure: Your Evenings Ltd built and sells this. Greenhouse, Lever, Ashby & Workday Jobs API is a pay-per-use Actor on the Apify Store that does all of this for a list of company domains:
- It finds the board on Greenhouse, Lever (including EU), Ashby, Workable, SmartRecruiters, Recruitee or Workday, from a domain, careers URL, board URL or
ats:token. - Workday: give it a company name, domain or
myworkdayjobs.comlink. It splits large boards to read past the 2,000-result cap. Each row'spostedAtBasissaysapproximate-from-listing-textwhen the date came from Workday's relative text. Try the Workday jobs export example. - Title, location, remote, department and posted-date filters run before you're charged.
companyTotalJobson every row and aCOMPANY-STATUSfile account for every company.monitorModereturns only opened or closed jobs. Companies it can't find are free.
It doesn't cover other systems (for example iCIMS or SuccessFactors) yet.
Longer walkthrough: Greenhouse jobs API: get jobs posted in the last 24 hours (and why your scraper returns 0 jobs) on dev.to.
FAQ
Is there a free Greenhouse jobs API?
Yes. boards-api.greenhouse.io/v1/boards/{token}/jobs is public and keyless for reading published jobs.
Do Lever and Ashby need an API key for job postings? No. Both posting APIs are public for reading.
Does Workday have a public jobs API? Not a documented one. Career sites use their own search endpoint, which returns at most 2,000 results per search and shows only approximate posted dates.
Product names and logos belong to their owners. Your Evenings Ltd is not affiliated with or endorsed by them.