Greenhouse, Lever, Ashby, SmartRecruiters, Recruitee, Personio and Breezy all publish an employer's open jobs as JSON or XML at a public URL, with no key. Workday and Oracle career sites load their jobs from JSON endpoints that also answer without a key, but those endpoints are not documented as public APIs. Every one of them works per employer: you need to know the employer's board name first, and there is no list of all employers. That last step is most of the work.
Why ATS feeds are worth reading
An applicant tracking system (ATS) is the software an employer uses to post jobs and collect applications. When a job is posted there, the employer's careers page shows it at once, and it disappears when the role closes. A job board copy of the same job can lag by days or stay up after the job is filled. So the ATS feed is the closest thing to the source of truth for whether a job is open.
The feeds also carry fields that job boards often drop: the requisition ID, the department, the exact office, and in many US states the pay range the employer published.
The main platforms at a glance
The "jobs in our data" column is the count of open jobs we held from each platform on October 19, 2026. It shows how much of the market each one carries. It is not a count of every job on that platform.
| ATS | Jobs in our data | Where the jobs are | Key needed | Documented as public | Pay in the feed |
|---|---|---|---|---|---|
| Workday | 1,129,586 | POST https://{tenant}.wd{n}.myworkdayjobs.com/wday/cxs/{tenant}/{site}/jobs | No | No. It is the endpoint the career site calls | Only inside the description text |
| Oracle Recruiting Cloud | 523,667 | GET https://{host}.fa.oraclecloud.com/hcmRestApi/resources/latest/recruitingCEJobRequisitions?finder=findReqs;siteNumber={site} | No | No. It is the endpoint the career site calls | Sometimes, as structured fields |
| SAP SuccessFactors | 520,845 | The career site's sitemap.xml and search pages; some sites also answer at /services/recruiting/v1/jobs | No | No | Inside the description text |
| SmartRecruiters | 373,432 | GET https://api.smartrecruiters.com/v1/companies/{company}/postings | No | Yes, the Posting API | Not in the list response we read |
| iCIMS | 321,329 | The career portal pages, https://{portal}/jobs/search?ss=1&in_iframe=1 | No | No | Inside the description text |
| Greenhouse | 195,956 | GET https://boards-api.greenhouse.io/v1/boards/{board}/jobs | No | Yes, the Job Board API | Yes, with pay_transparency=true |
| Lever | 77,291 | GET https://api.lever.co/v0/postings/{company}?mode=json | No | Yes, the Postings API | Yes, salaryRange when the employer fills it |
| Ashby | 66,853 | GET https://api.ashbyhq.com/posting-api/job-board/{board}?includeCompensation=true | No | Yes, the public job posting API | Yes, with includeCompensation=true |
| Recruitee | Smaller | GET https://{company}.recruitee.com/api/offers/ | No | Yes, the Careers Site API | A salary object (empty for all 23 bunq jobs we read) |
| Personio | Smaller | GET https://{company}.jobs.personio.de/xml | No | Yes, the XML feed | No pay element in the feed we read |
| Breezy HR | Smaller | GET https://{company}.breezy.hr/json | No | Not as an API | A salary text field |
Counts from our GET /v1/jobs/count?source=… on October 19, 2026. Our coverage page lists all 131 sources we read. Endpoints checked by calling each one on October 19, 2026 between 1:59 and 2:00 PM ET.
Greenhouse: the easiest feed
Greenhouse documents its Job Board API and says plainly that "authentication is not required for any GET endpoints". One call returns all of a board's open jobs. Add content=true for descriptions and pay_transparency=true for the pay ranges the employer entered.
curl -s "https://boards-api.greenhouse.io/v1/boards/gitlab/jobs"
Real response from October 19, 2026 at 1:59 PM ET, trimmed to one job:
{"jobs":[{
"absolute_url":"https://job-boards.greenhouse.io/gitlab/jobs/8860302002",
"internal_job_id":6547054002,
"location":{"name":"Remote, France"},
"id":8860302002,
"updated_at":"2026-10-15T15:24:46-04:00",
"requisition_id":"7109",
"title":"Account Executive - France",
"company_name":"GitLab",
"first_published":"2026-10-02T11:31:50-04:00",
"departments":[{"id":4096748002,"name":"EMEA - Commercial"}],
"offices":[{"id":4042500002,"name":"France","location":"France"}]}],
"meta":{"total":227}}
Note the two dates. first_published is when the job first went live. updated_at changes on any edit, so do not use it as the posting date.
A Greenhouse reader in 15 lines
For one board, you need nothing but the Python standard library. This reads GitLab's board with pay ranges and prints the first three jobs that have one:
import json, urllib.request
def greenhouse_jobs(board):
url = f"https://boards-api.greenhouse.io/v1/boards/{board}/jobs?pay_transparency=true"
with urllib.request.urlopen(url, timeout=30) as r:
return json.load(r)["jobs"]
jobs = greenhouse_jobs("gitlab")
with_pay = [j for j in jobs if j.get("pay_input_ranges")]
print(len(jobs), "open jobs,", len(with_pay), "with a pay range")
for j in with_pay[:3]:
p = j["pay_input_ranges"][0]
print(j["title"], "|", j["location"]["name"], "|",
p["min_cents"] // 100, "-", p["max_cents"] // 100, p["currency_type"])
Real output, October 19, 2026 at 2:29 PM ET:
228 open jobs, 105 with a pay range
AI Transformation Owner, CRO | Remote, United States | 139200 - 235200 USD
AI Transformation Owner, Product & Design | Remote, Canada; Remote, United Kingdom; Remote, United States | 203200 - 345600 USD
Associate Renewals Manager | Remote, Canada; Remote, United States | 53760 - 105000 USD
Thirty minutes earlier the same board had 227 jobs. Boards change all day, so store when you read each one. Pay arrives in cents, and a job can carry several ranges, for example one per country. This script prints only the first, which for a job open in three countries is a simplification you would not keep in production.
To run this for many employers, loop over board names, wait between calls, and save each response with the time you read it. Then compare today's job IDs with yesterday's: IDs that disappeared are jobs that closed.
Lever and Ashby
Lever's Postings API returns a list of postings for one company. Its docs list salaryRange and salaryDescription fields. They only appear when the employer fills them in. In our call for Palantir, none of the 306 postings had a salary range.
curl -s "https://api.lever.co/v0/postings/palantir?mode=json"
[{"id":"6ed76ce8-4156-4b60-b120-403538bd66cd",
"text":"Administrative Business Partner",
"categories":{"commitment":"Full-time","location":"Singapore, Singapore",
"team":"Administrative","allLocations":["Singapore, Singapore"]},
"country":"SG","workplaceType":"hybrid",
"createdAt":1786469891368,
"hostedUrl":"https://jobs.lever.co/palantir/6ed76ce8-4156-4b60-b120-403538bd66cd",
"applyUrl":"https://jobs.lever.co/palantir/6ed76ce8-4156-4b60-b120-403538bd66cd/apply"}]
createdAt is milliseconds since 1970, not an ISO date. Lever's docs also warn that it may rate limit, and that limits "may also be changed without warning".
Ashby's public job posting API works the same way and gives structured pay when you ask for it. For OpenAI's board it returned 805 jobs. The first one, trimmed:
curl -s "https://api.ashbyhq.com/posting-api/job-board/openai?includeCompensation=true"
{"jobs":[{
"title":"Technical Program Manager, Compute Infrastructure",
"department":"Technical Program Management",
"employmentType":"FullTime","location":"San Francisco",
"publishedAt":"2026-03-12T16:38:15.322+00:00","isListed":true,
"address":{"postalAddress":{"addressRegion":"California",
"addressCountry":"United States","addressLocality":"San Francisco"}},
"jobUrl":"https://jobs.ashbyhq.com/openai/8fb1615c-34bf-47c4-a1d1-b7b2f836bbd3",
"compensation":{"scrapeableCompensationSalarySummary":"$257K - $335K",
"compensationTiers":[{"components":[{"compensationType":"Salary",
"interval":"1 YEAR","currencyCode":"USD","minValue":257000,"maxValue":335000}]}]}}]}
Workday: open, but capped
Workday has no documented public jobs API. Each Workday career site loads its list from a JSON endpoint, and that endpoint answers a plain POST. The URL has three parts you must find for each employer: the tenant, the data center number (wd1, wd5 and so on) and the site name.
curl -s -X POST "https://nvidia.wd5.myworkdayjobs.com/wday/cxs/nvidia/NVIDIAExternalCareerSite/jobs" \
-H 'Content-Type: application/json' \
-d '{"appliedFacets":{},"limit":20,"offset":0,"searchText":""}'
{"total":2000,
"jobPostings":[{
"title":"Senior Software Engineer - Image and Data Processing Libraries",
"externalPath":"/job/Poland-Remote/Senior-Software-Engineer---Image-and-Data-Processing-Libraries_JR2022702",
"locationsText":"2 Locations",
"postedOn":"Posted Today",
"bulletFields":["JR2022702"]}],
"facets":[{"facetParameter":"jobFamilyGroup", ...}, ...]}
Three things in this answer catch people out:
- The total stops at 2,000. NVIDIA's
totalsaid 2,000, but the counts in its own job family facet added up to 2,735. The list cannot be paged past the cap. To get every job you have to split the search by facet (job family, location, worker type) until each slice is under the cap, then merge the slices and remove repeats. - Dates are words.
postedOnsays "Posted Today" or "Posted 30+ Days Ago". You need the detail call for each job to get anything exact. - Locations are summarised. "2 Locations" means the real places are only in the detail record.
So reading one large Workday employer properly takes many list calls plus one detail call per job. Workday is the biggest source in our data, and also the slowest to read well.
Oracle and SmartRecruiters
Oracle Recruiting Cloud career sites call an HCM REST endpoint that also answers without a key. You need the host and the site number. For JPMorgan Chase's site CX_1001 it reported TotalJobsCount 7,173 on October 19, 2026. One job, trimmed:
{"Id":"210616021",
"Title":"Investment Banking - EMEA Real Estate - Analyst / Associate - London",
"PostedDate":"2026-10-16","PostingEndDate":null,
"PrimaryLocationCountry":"GB",
"JobFamily":"Associates","JobFunction":"Program Analysts & Associate",
"WorkplaceTypeCode":null,"ContractType":null,"JobSchedule":null}
Many fields are null for most employers. Treat a null as "not published", not as "no".
SmartRecruiters documents a public Posting API. For Bosch Group it reported totalFound 5,082, served 100 at a time. Each record has structured location, function, employment type and experience level, plus custom fields that differ per employer.
The hard part is finding the employers
Every endpoint above needs a board name, tenant or site number. No ATS publishes a list of its customers. People build that list in a few ways:
- Collect careers page links from company websites and follow them to the ATS.
- Search web crawl archives such as Common Crawl for ATS hostnames, for example
*.myworkdayjobs.comorjobs.lever.co/*. - Read the apply links on job boards, which often point back to the ATS.
- Guess board names from company names and test them. This finds some, and wastes calls on the rest.
Then the list goes stale. Employers switch ATS, rename boards and close sites. A reader has to notice when a board returns nothing for a while and check whether it moved.
Traps that cost the most time
- One job, many postings. Large employers post the same role once per city, or on two boards at once. In our October 14 snapshot, 5,716,621 postings collapsed to 4,871,084 jobs. If you count postings you will overcount jobs.
- Agency and staffing boards. Some boards belong to staffing firms listing jobs for other employers. The
company_nameis the agency, not the employer. - Hidden and internal jobs. Some feeds include unlisted or internal-only jobs. Ashby marks these with
isListed. Others do not mark them at all. - Text pay. Where pay is only in the description, you have to parse phrases like "$27 to $32 per hour". Watch for averages and bonuses: "earn an average of $27 an hour" is not a posted pay range.
- Rate limits and blocks. Most of these endpoints are free to call but not unlimited. Spread calls out, back off on 429 and 503 answers, and log the rate you were running at when you were pushed back.
- Terms. A public URL is not the same as permission to resell. Read each platform's and each employer's terms before you republish their data.
Build it yourself or buy it
Build it yourself when you need a fixed list of a few hundred employers, mostly on Greenhouse, Lever or Ashby. Those three are well documented, return all jobs in one call, and need no key. A small script and a daily cron job will do. You will own the schedule and the field mapping.
Buy it when you need broad coverage, Workday and Oracle employers, pay parsed from text, or jobs merged across boards. That work grows with every new platform. Ours reads 131 ATS, job-board and career-site sources and gives each value a state, such as known, derived or extraction_failed, so you can see where a field came from.
A quick way to decide: count what you need first. Counts are free on our API.
curl -s "https://api.headcountlabs.com/v1/jobs/count?source=workday&country=US" \
-H "Authorization: Bearer $HEADCOUNT_API_KEY"
Real answer, October 19, 2026 at 2:24 PM ET. Broad filters return an estimate with a margin, and the note says so:
{"count":739584,"count_is_estimate":true,"count_margin":26917,
"count_note":"Estimated from 2,889 jobs in 1/256 of the catalog (picked by job ID, which is random), because an exact count of this filter did not finish in time. The true count is within count_margin of count about 95 times in 100. Narrow the filters (for example add a state or company) for an exact count.",
"snapshot_watermark":"2026-10-19T15:20:54.671Z"}
Or with no key at all, from any MCP client, through the public MCP endpoint at https://api.headcountlabs.com/mcp/public and its count_jobs tool.
Places where others are better: if you want to post jobs or manage applicants, you need the ATS's own authenticated API (Greenhouse Harvest, Lever's API, Workday's customer APIs), which these public feeds do not replace. If you only need one employer, call its feed directly. It is free and always current.
Questions
Does Greenhouse need an API key to read jobs?
No. Greenhouse's Job Board API docs say authentication is not required for any GET endpoint. You need the employer's board name.
Does Workday have a public jobs API?
Not a documented one. Each Workday career site loads jobs from a JSON endpoint that answers without a key, but its totals stop at 2,000, so large employers need many split searches.
How do I get pay ranges from Greenhouse, Lever or Ashby?
Add pay_transparency=true on Greenhouse and includeCompensation=true on Ashby. Lever returns salaryRange when the employer fills it in. Many employers leave pay out.
Is there a list of every company using an ATS?
No ATS publishes one. People build lists from careers page links, web crawl archives, and apply links found on job boards.
Can I republish jobs I read from these feeds?
Check the terms of the platform and of each employer first. A feed you can read without a key is not automatically one you may resell.
Which ATS has the most jobs?
In our data on October 19, 2026, Workday was largest with 1,129,586 open jobs, then Oracle with 523,667 and SuccessFactors with 520,845. That reflects which employers we read, not a full market share.
Sources
All read or called on October 19, 2026.
- https://developers.greenhouse.io/job-board.html
- https://github.com/lever/postings-api
- https://developers.ashbyhq.com/docs/public-job-posting-api
- https://developers.smartrecruiters.com/docs/posting-api
- Live calls to boards-api.greenhouse.io (gitlab), api.lever.co (palantir), api.ashbyhq.com (openai), api.smartrecruiters.com (BoschGroup), nvidia.wd5.myworkdayjobs.com, jpmc.fa.oraclecloud.com, bunq.recruitee.com, personio.jobs.personio.de and breezy.breezy.hr
- https://headcountlabs.com/coverage/
More guides
- Free job posting APIs in 2026: what is actually free
- Job search MCP servers compared
- Coresignal alternatives for job postings data
- A daily dataset of open jobs you can keep in sync
- Best job posting data APIs in 2026
- Does Indeed have an API?
- Does LinkedIn have a jobs API?
- Is there a Google Jobs API?
- Does Glassdoor have an API?