cd /news/developer-tools/3141-jobs-from-30-career-sites-which… · home › topics › developer-tools › article
[ARTICLE · art-142931] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=· neutral

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

A developer built an Apify Actor that aggregates job postings directly from company career sites across multiple applicant tracking systems, returning a unified schema. A run against 30 tech companies on 30 September 2026 surfaced 14,807 open jobs across 28 companies, and a capped jobs-mode run collected 3,141 postings of which 1,691 (54%) included a salary range. Ashby was the only ATS in the sample publishing pay as structured data, with 75% of its jobs showing salary, while SmartRecruiters lagged at 3%.

by read5 min views3 publishedOct 1, 2026

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).

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"
}

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

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:

linear.app through its careers link (Ashby), huggingface.co through its careers page (Workable) and modular.com by a verified guess (Gem).lever:plaid returned HTTP 404, so Plaid no longer uses that Lever board. Visa's SmartRecruiters board was found but had 0 jobs. 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.

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:

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.

$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.

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"])

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}'

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.

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.

── more in #developer-tools 4 stories · sorted by recency
── more on @apify 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/3141-jobs-from-30-ca…] indexed:0 read:5min 2026-10-01 · —