DEV Community

Radar Labs
Radar Labs

Posted on

Getting 6,826 job postings with salary as JSON in four minutes (and what it actually costs)

Most "job API" tutorials show you a toy call and stop before the part where you need 20,000 rows. This is the other version: the exact request, the exact response, the exact bill, and the four ways it will fail on you.

What you get

One call returns jobs from 259 companies across Greenhouse, Ashby, Lever, Workable, Personio and Workday. Strip the ones without pay and you are left with about 6,826 postings that state an annual salary range, normalised to USD.

Each record looks like this:

{
  "company": "openai",
  "ats": "ashby",
  "title": "Research Scientist / Research Engineer - Foundations, Search",
  "location": "San Francisco, CA",
  "remote": false,
  "salaryLow": 380000,
  "salaryHigh": 850000,
  "salaryCurrency": "USD",
  "salaryUsdLow": 380000,
  "salaryUsdHigh": 850000,
  "postedAt": "2025-06-16T22:35:00.347000+00:00",
  "url": "https://jobs.ashbyhq.com/openai/…"
}
Enter fullscreen mode Exit fullscreen mode

salaryUsdLow/High are the interesting fields: they exist because I convert hourly and monthly bands into a year and convert currencies, and because I keep the original numbers next to the normalised ones so you can second-guess me.

The call

No SDK, no pagination, no auth dance beyond an Apify token:

curl -X POST "https://api.apify.com/v2/acts/contributory_garnet_hyz~ats-job-feed/runs?token=$APIFY_TOKEN&waitForFinish=600" \
  -H "Content-Type: application/json" \
  -d '{
        "useVerifiedPool": true,
        "salaryOnly": true,
        "minSalaryUsd": 150000,
        "maxPerCompany": 3,
        "maxJobs": 100
      }'
Enter fullscreen mode Exit fullscreen mode

That run takes about four minutes and returns 100 jobs. The request-level knobs:

input what it does default
useVerifiedPool read the bundled 259-company list instead of the companies you pass false
companies your own slugs or careers URLs (Workday needs the full URL) 13 well-known slugs
salaryOnly drop postings without a published range false
minSalaryUsd drop below this annual midpoint 0
keywords substring match on title none
maxPerCompany stop one company eating the list 20
maxJobs hard cap on returned rows 200
maxWorkers parallel board requests (max 10) 6
includeDescriptions read Greenhouse job bodies (needed for their salaries) true
onlyNew only return postings you have not seen in previous runs false
remoteOnly, excludeSenior, excludeJunior, excludeNonTech, maxAgeDays obvious filters off

The bill, itemised

This is the part nobody publishes. Real numbers from my own runs:

  • $0.0005 per run start
  • $0.0015 per job returned
  • platform usage: about $0.006 with job descriptions on, about $0.0008 with them off

So: 100 jobs with descriptions ≈ $0.006 + $0.0005 + $0.15 = $0.157. 50 jobs ≈ $0.08. A daily diff of ~20 new postings ≈ $0.04/day.

That platform-usage line is 8x higher when includeDescriptions is on, because Greenhouse descriptions are a 144 MB download. If you do not need salary for Greenhouse companies — or you are happy with the ~18% coverage that structured fields give you — turn it off and the run is twice as fast and an order of magnitude cheaper.

But note the catch: salaryOnly + includeDescriptions: false will mostly return Ashby jobs, because Greenhouse publishes pay in prose, not in metadata. You are choosing between cheap and complete.

Four ways it breaks

  1. Slugs rot. 527 candidate company slugs tested → 259 answered. Next month some of the 259 will 404. Every run returns failedCompanies with the reason per board, e.g. "greenhouse: empty; lever: HTTP 404; ashby: HTTP 404". It does not silently return fewer rows.
  2. Workable is a shell. Its public endpoint answers for 4 companies and returns an empty list with a 200. Treated as a failure, not as "this company is not hiring".
  3. Workday cannot be guessed. Its board host is {tenant}.wd{N}.myworkdayjobs.com and the root path returns HTTP 406, so there is no redirect to discover the site name. I tested 24 large companies × 2 host numbers × 4 site names = 192 guesses and got exactly one hit. That is why Workday needs a URL. Its list API also has no salary field at all, so a range can only come from the job description.
  4. Greenhouse salaries are prose. "The base salary range is 196,000 USD - 310,500 USD" — currency after each number, plus postings that list Level 4 and Level 5 bands in one sentence. My first parser turned that into 196000–196000. If you build your own, test on NVIDIA's postings.

If you only want the snapshot

You do not need an account for the frozen version — 1,459 rows of it, CC BY 4.0:

The live actor is https://apify.com/contributory_garnet_hyz/ats-job-feed — $0.0015 per job, and it is a one-person project, so if the parsing looks wrong somewhere I do want to hear about it.

Two honest caveats: this is published pay, not actual pay, and roughly 44% of postings publish nothing at all. Also, some companies post evergreen roles that stay up for a year — maxAgeDays will not save you from those, because their posting date is genuinely old and they are genuinely still open.


Numbers revision (2026-10-24): the pipeline was merging distinct city-level requisitions under one title, so row counts here are lower than the current dataset. After fixing the dedupe key the same 259 companies yield **18,567 postings / 7,984 with published pay* (median $200,000). The current figures live on the dataset page; the bug is written up here.*

Top comments (0)