# Pulling open jobs from Greenhouse, Lever and Ashby without scraping HTML

> Source: <https://dev.to/tmeteordata/pulling-open-jobs-from-greenhouse-lever-and-ashby-without-scraping-html-4a1o>
> Published: 2026-10-24 01:32:36+00:00

I built an Apify actor that reads job boards from several applicant tracking systems (ATS), so keep that bias in mind. An AI assistant helped me draft this article, and I reviewed and edited it. The free route comes first, because for a handful of companies a short script is enough.

Recruiters, job-board builders and sales teams that watch hiring signals all want the same thing: a clean list of open roles at specific companies. Career pages are often JavaScript-heavy, but most companies run their hiring on an ATS that exposes a public JSON feed so the career page can render. You do not need to scrape HTML. You need the company's board slug, which is the last part of the careers URL (for example `boards.greenhouse.io/airbnb`, `jobs.lever.co/spotify`, `jobs.ashbyhq.com/ramp`).

``` python
import requests

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

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

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

for name, fn, slug in [("greenhouse", greenhouse, "airbnb"),
                       ("lever", lever, "spotify"),
                       ("ashby", ashby, "ramp")]:
    jobs = fn(slug)
    print(name, slug, len(jobs), jobs[0])
```

I checked these against the live endpoints. Greenhouse returns `jobs` with `title`, `location.name`, `absolute_url`, `updated_at` and `first_published`. Lever returns a list with `text`, `categories` (including `location`, `department`, `commitment`), `hostedUrl`, `applyUrl` and `createdAt`. Ashby returns `jobs` with `title`, `department`, `location`, `isRemote`, `workplaceType`, `publishedAt` and `jobUrl`. Field sets differ between vendors and can change, so check each response's keys before depending on one. For descriptions, Greenhouse needs `?content=true` on the request.

The actor covers Greenhouse, Lever, Ashby, Workable, SmartRecruiters and Recruitee, plus a schema.org JobPosting fallback for single career pages. You can paste company slugs or careers URLs. The input below is the README's real test input:

```
{
  "companies": [
    "airbnb",
    "https://jobs.lever.co/spotify",
    "https://jobs.ashbyhq.com/ramp"
  ],
  "onlyNewSinceLastRun": false,
  "maxJobsPerCompany": 3,
  "maxJobsTotal": 3,
  "includeDescription": false
}
```

Other real fields include `keywords`, `excludeKeywords`, `locations`, `departments`, `employmentTypes`, `remoteOnly`, `workModels`, `postedAfter`, `minSalary`, `skillsInclude` and `monitorMode` (return only new or changed jobs, optionally also removed ones).

Excerpt of real sample output (description removed, a few fields kept):

```
{
  "company": "Spotify",
  "ats": "lever",
  "title": "Senior Product Manager, Consumer - Audiobooks",
  "department": "Product",
  "location": "London",
  "remote": false,
  "workplaceType": "hybrid",
  "employmentType": "FULL_TIME",
  "postedAt": "2026-10-12T13:42:44.230Z",
  "jobUrl": "https://jobs.lever.co/spotify/ddc10181-6ecc-4dfc-9d02-2c5ce820cfdd",
  "salaryMin": null,
  "seniority": "senior"
}
```

The Greenhouse row in the same file shows the vendor differences: Airbnb's "Senior Software Engineer, Payments Transaction Risk" has `remote`, `workplaceType` and `employmentType` all `null`, because Greenhouse's board feed does not carry them.

`remote: true` only when the location says remote), Lever has no `updatedAt`, and most boards publish no salary.` seniority` is a heuristic from the title only. Treat it as a best guess.
For a few boards, run the script. For a watchlist with filters and a daily new-jobs feed, the actor is here: [https://apify.com/transparent_meteorite/ats-open-roles](https://apify.com/transparent_meteorite/ats-open-roles)

*Disclosure again: I built the actor, and an AI assistant helped draft this article. I reviewed it and checked the three endpoints before writing the snippet.*
