DEV Community

Cover image for 3,141 jobs from 30 career sites: which ATS actually shows the salary?
Tidy Tools
Tidy Tools

Posted on

3,141 jobs from 30 career sites: which ATS actually shows the salary?

Company career sites are the freshest source of job data there is. A job appears there before any job board copies it, and it disappears the day it closes. The problem is that every company hides its jobs behind a different applicant tracking system (ATS): Stripe uses Greenhouse, OpenAI uses Ashby, Palantir uses Lever, NVIDIA uses Workday, Bosch uses SmartRecruiters. Each has its own feed, its own field names and its own idea of what a "location" is.

I built an Apify Actor that reads all of them and returns one table. On 30 September 2026 I pointed it at 30 well-known tech companies. Here is what came back, and how to do the same with your own list (the input has no company limit, so the same run works for a few hundred companies).

The input: just a list of companies

You can mix board URLs, ats:slug shortcuts and plain company websites. For websites, the Actor finds the board itself: from careers links on the site, from embeds on the careers page, and finally from guesses it verifies against the board's company name.

{
  "companies": [
    "greenhouse:stripe", "greenhouse:airbnb", "greenhouse:cloudflare",
    "ashby:openai", "ashby:notion", "linear.app",
    "jobs.lever.co/palantir", "lever:spotify",
    "https://nvidia.wd5.myworkdayjobs.com/NVIDIAExternalCareerSite",
    "smartrecruiters:BoschGroup",
    "huggingface.co", "modular.com", "netflix.com"
  ],
  "maxJobsPerCompany": 150,
  "descriptionFormat": "text"
}
Enter fullscreen mode Exit fullscreen mode

(That is an excerpt; the full list had 30 entries: 10 Greenhouse, 7 Ashby, 3 Lever, 5 Workday, 2 SmartRecruiters and 3 plain websites.)

Step 1: which ATS does each company use, and how many jobs are open?

Detection mode ("mode": "detect") returns one row per company. The whole list took 209 seconds.

ATS Companies Open jobs
Workday 5 (NVIDIA, Salesforce, Mastercard, Intel, Adobe) 5,643
SmartRecruiters 2 (Bosch, Visa) 4,835
Greenhouse 10 (Stripe, Datadog, Cloudflare, GitLab…) 2,505
Ashby 7 (OpenAI, Ramp, Notion, Perplexity…) 1,400
Lever 2 (Palantir, Spotify) 397
Gem 1 (Modular) 19
Workable 1 (Hugging Face) 8
Total 28 of 30 14,807

A few details from that run:

  • Three companies were given as plain websites, and all three were found: linear.app through its careers link (Ashby), huggingface.co through its careers page (Workable) and modular.com by a verified guess (Gem).
  • Two were not found, and neither was charged. Netflix runs its own careers site, not a supported ATS. lever:plaid returned HTTP 404, so Plaid no longer uses that Lever board. Visa's SmartRecruiters board was found but had 0 jobs.
  • The biggest boards are not the startups. Bosch alone had 4,835 open jobs and Salesforce 1,507. OpenAI had the largest startup board at 839.

Step 2: the jobs themselves

Then I ran jobs mode with a cap of 150 jobs per company, in batches of 10 companies. Each run took between 3 and 54 seconds; the Workday batch is the slow one because Workday needs one extra request per job for the description. That gave 3,141 jobs in one schema: title, company, department, location and locations, ISO country, workplaceType, employmentType, postedAt, jobUrl, applyUrl, the description, and four salary columns.

How many postings include a salary?

1,691 of 3,141 (54%) had a salary range. It depends a lot on the ATS:

ATS Jobs With salary Where the salary comes from
Ashby 705 531 (75%) 447 from Ashby's own compensation fields, the rest from the posting text
Greenhouse 1,281 724 (57%) the pay-transparency paragraph in the posting
Lever 228 110 (48%) the posting text
Workday 750 304 (41%) the posting text (Workday's feed has no salary field)
SmartRecruiters 150 4 (3%) the posting text; most Bosch jobs are outside the US

Ashby is the only ATS in this sample that publishes pay as structured data. For all the others the Actor reads the pay range from the description (salarySource: "description") and splits it into salaryMin, salaryMax, salaryCurrency and salaryPeriod.

Parsing free text is where the edge cases live. Intel writes its ranges as $158,200.00-264,460.00 USD (no dollar sign on the second number); after I taught the parser that format, Intel went from 0 to 91 of 150 jobs with a salary. NVIDIA writes 184,000 USD - 287,500 USD for Level 4, …, which the parser does not read yet, so all 150 NVIDIA jobs have empty salary columns. The real Workday figure is higher than 41%.

Some numbers from those columns:

  • 1,561 postings had a yearly USD range. The median midpoint was $205,360.
  • 288 of them had "software engineer" in the title. The median range was $190,800 – $277,050.
  • Currencies: USD 1,593, EUR 57, GBP 24, CAD 13, BRL 4.
  • Where the jobs are: US 1,582, UK 188, India 181, Japan 67, Canada 64.
  • workplaceType: 771 remote, 709 hybrid, 346 on-site, and 1,315 where the ATS did not say.

These are the first 150 jobs per company from 30 tech companies, not every opening, so treat them as a sample, not a salary survey.

What it costs

$1 per 1,000 jobs, no start fee. The 3,141 jobs above would cost $3.14; the detection run is $0.002 per company found, so $0.056 for this list. Companies without a readable board, jobs removed by your filters and duplicate jobs are not charged.

For a daily feed, turn on Only new jobs (onlyNewJobs): after the first run you only get, and only pay for, jobs that were not there on the previous run. Closed jobs come back as free closed rows, so you can remove them from your own board.

Run it from code

With the Python client (pip install apify-client):

from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("tidytools/ats-career-site-jobs").call(run_input={
    "companies": ["greenhouse:stripe", "ashby:openai", "linear.app"],
    "titleKeywords": ["engineer"],
    "maxJobsPerCompany": 50,
})
for job in client.dataset(run["defaultDatasetId"]).iterate_items():
    if job.get("error"):  # company without a readable board (not charged)
        print("skipped:", job["input"], job["error"])
        continue
    print(job["company"], "|", job["title"], "|", job.get("salaryMin"), job.get("salaryMax"), job.get("salaryCurrency"), "|", job["jobUrl"])
Enter fullscreen mode Exit fullscreen mode

Or with plain HTTP, which waits for the run and returns the rows:

curl -X POST "https://api.apify.com/v2/acts/tidytools~ats-career-site-jobs/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"companies": ["greenhouse:stripe", "ashby:openai"], "titleKeywords": ["engineer"], "maxJobsPerCompany": 20}'
Enter fullscreen mode Exit fullscreen mode

Filters (title keywords and exclusions, location, department, remote only, posted in the last N days) are applied before charging, so a narrow query over hundreds of companies still costs only what it returns.

Supported job boards

Greenhouse, Lever (US and EU), Ashby, Workday, SmartRecruiters, Workable, Recruitee, Personio, Breezy HR, Teamtailor, BambooHR, Rippling, Pinpoint, Gem, JazzHR, Homerun and Freshteam. For ATSs without a public feed (iCIMS, Taleo, SuccessFactors and others) detection mode still tells you which one a company uses.

It only reads the public feeds that these vendors provide so that companies can show jobs on their own websites. No logins, no candidate data.

Try it here: ATS & Career Site Jobs Scraper - Greenhouse, Lever, Workday

Disclosure: I built this Actor and earn money when people run it on Apify.

Top comments (0)