# Scrape any company's job postings — Greenhouse, Lever & Ashby, with one API call

> Source: <https://dev.to/quantoracle/scrape-any-companys-job-postings-greenhouse-lever-ashby-with-one-api-call-4db>
> Published: 2026-07-22 02:11:12+00:00

Almost every tech company's job board runs on one of a handful of ATS platforms — Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee. And nearly all of them expose a **public, documented JSON API** for their postings.

Which means "scrape job postings" isn't really a scraping problem. It's a *normalization* problem: six different response shapes, board-slug discovery, HTML-encoded descriptions, and compensation data that's structured differently everywhere it exists at all.

Here's the DIY version, the one-call version, and one genuinely underrated thing hiding in this data.

No key, no auth. These are live right now:

```
# Greenhouse
curl "https://boards-api.greenhouse.io/v1/boards/stripe/jobs"
# Lever
curl "https://api.lever.co/v0/postings/palantir?mode=json"
# Ashby
curl "https://api.ashbyhq.com/posting-api/job-board/openai"
```

At the time of writing that's **518 open roles at Stripe, 280 at Palantir, and 727 at OpenAI** — three calls, three completely different JSON shapes.

Greenhouse nests `offices`

and `departments`

as arrays of objects. Lever flattens everything into `categories`

. Ashby puts the city in `location.name`

and hides compensation behind a separate `includeCompensation=true`

flag. Descriptions come back as escaped HTML on some, Markdown-ish on others. Multiply by six platforms and you've got a weekend project plus ongoing maintenance every time one of them changes.

[Job Postings API](https://apify.com/fetchbase/job-postings-scraper) auto-detects which ATS a company uses — from a bare slug or a full careers URL — and returns one normalized row per job. Here's a **real, unedited** record from a live run:

```
curl -X POST "https://api.apify.com/v2/acts/fetchbase~job-postings-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "companies": ["stripe", "gitlab", "https://jobs.ashbyhq.com/openai"] }'
{
  "company": "stripe",
  "ats": "greenhouse",
  "id": "7954688",
  "title": "Account Executive, AI Sales (Grower)",
  "department": "1654 Account Executives (AI)",
  "location": "San Francisco, CA",
  "remote": null,
  "url": "https://stripe.com/jobs/search?gh_jid=7954688",
  "publishedAt": "2026-07-21T18:51:19-04:00",
  "salary": null,
  "description": "## Who we are\n\n### About Stripe\n\nStripe is a financial infrastructure..."
}
```

Note `remote`

and `salary`

are `null`

there — and that's the honest part. **Those fields are only as good as what the ATS publishes.** Greenhouse boards frequently omit both. The actor normalizes the shape; it can't invent data the source doesn't expose.

Which brings us to the interesting bit.

Ashby boards expose structured compensation, and plenty of companies leave it on. Of those **727 OpenAI roles, all 727 carry a published pay range** — things like `$257K – $335K • Offers Equity`

. **460 of them are flagged remote.**

That is a real, public, structured compensation dataset that most people assume you have to buy from Levels.fyi or scrape out of rendered HTML. It's sitting behind a GET request.

Useful inputs while you're exploring:

| Input | Does |
|---|---|
`companies` |
Slugs or careers URLs — mix ATSes freely in one run |
`remoteOnly` |
Keep only roles the board flags remote |
`titleFilter` |
Substring match on title (`"engineer"` ) |
`maxJobsPerCompany` |
Cap big boards (default 1000) |
`includeDescriptions` |
Set `false` for a fast, light index |

You're billed per job returned, so filters cut cost as well as noise.

**A "who's hiring in AI" snapshot.** One run across several labs, compare volume and department mix:

**A remote-jobs alert.** `remoteOnly`

+ `titleFilter: "engineer"`

across your target companies, on a schedule, diffed against yesterday's dataset → new postings to Slack:

**Comp research.** Pull a whole Ashby board and you get ranges attached to titles and locations:

[Scrape OpenAI's openings]— 727 live roles, all with pay ranges

**Hiring as a market signal.** Posting counts over time are a leading indicator — teams that are shipping are hiring, and teams in trouble quietly stop. Snapshot weekly and you've built a trends dataset nobody sells you.

These are *intended-use, documented* endpoints. No bot walls, no proxy budget, no 3am breakage when a careers page gets redesigned. The tedious part — ATS detection, six-way shape merge, HTML→Markdown, comp parsing — is the part worth not rewriting yourself.

Pay per job returned, failed runs cost nothing, and Apify's free credits cover plenty of testing. The rest of the utility suite lives at [apify.com/fetchbase](https://apify.com/fetchbase).

*Built this because job data shouldn't require scraping infrastructure. If you want another ATS supported or normalized salary output, say so in the comments or the actor's Issues tab.*
