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%. 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 : python from apify client import ApifyClient client = ApifyClient "