# ATS With a Public Job Postings API: 33 Compared

> Which applicant tracking systems let you fetch a company's jobs as JSON with no API key: endpoints, pagination and salary fields for 33 ATS platforms.

Source: https://cleanjobdata.com/articles/ats-public-job-postings-api-list
Published: 2026-11-01

TL;DR: Most popular applicant tracking systems serve their hosted careers pages from a public JSON endpoint you can call without a key: Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee, Teamtailor, BambooHR, Workday and others. Personio and JazzHR publish XML feeds instead. A smaller group, including iCIMS, Taleo, Jobvite and Avature, has no public jobs API, so you read the career-site HTML and its schema.org JobPosting JSON-LD. The table below lists the endpoint, pagination, description and salary behaviour for all 33.

Most popular applicant tracking systems expose a public JSON endpoint behind their hosted careers pages that returns a company's open jobs with no API key: Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee, Teamtailor, BambooHR and Workday all work this way. A few, such as iCIMS, Taleo, Jobvite and Avature, have no public jobs API at all, so you collect job links from the career-site HTML and read each job page's schema.org `JobPosting` JSON-LD.

Everything below comes from the collectors we run against these 33 platforms, not from vendor marketing pages. If you'd rather not build and maintain 33 of them, CleanJobData normalizes all of these sources into one REST API: see the [Jobs API docs](https://cleanjobdata.com/docs/api/jobs).

## Quick reference table

`{company}` and similar placeholders stand for the company's identifier on that platform (see [how to find it](#how-to-find-a-companys-ats-and-identifier)). "Description in list?" says whether the list call already carries the full job description or you need one more request per job. In the salary column, "—" means we don't read a structured salary field from that source.

| ATS | Public endpoint (list) | Auth | Pagination | Description in list? | Salary field |
|---|---|---|---|---|---|
| [ADP Workforce Now](https://cleanjobdata.com/articles/how-to-scrape-adp-job-listings) | `GET https://workforcenow.adp.com/mascsr/default/careercenter/public/events/staffing/v1/job-requisitions?cid={cid}&ccId={ccId}&lang={lang}&$top=20&$skip=0` | None | Offset (`$skip`), about 20 per page | No, detail call | `payGradeRange` |
| [Ashby](https://cleanjobdata.com/articles/how-to-scrape-ashby-job-listings) | `GET https://api.ashbyhq.com/posting-api/job-board/{company}?includeCompensation=true` | None | None, one call | Yes | `compensation` |
| [Avature](https://cleanjobdata.com/articles/how-to-scrape-avature-job-listings) | `GET https://{host}/{basePath}/SearchJobs?jobOffset=0` | None (HTML) | Offset (`jobOffset`), 10 links per page | No, job page HTML | — |
| [BambooHR](https://cleanjobdata.com/articles/how-to-scrape-bamboohr-job-listings) | `GET https://{company}.bamboohr.com/careers/list` | None | None, one call | No, `/careers/{id}/detail` | `compensation` (free text) |
| [BrassRing](https://cleanjobdata.com/articles/how-to-scrape-brassring-job-listings) | No public API: career-site JSON only | Browser session | Page number, 50 per page | No, detail POST | — |
| [Breezy HR](https://cleanjobdata.com/articles/how-to-scrape-breezy-hr-job-listings) | `GET https://{company}.breezy.hr/` | None (HTML) | None | No, job page JSON-LD | JSON-LD `baseSalary` |
| [Comeet](https://cleanjobdata.com/articles/how-to-scrape-comeet-job-listings) | `GET https://www.comeet.com/jobs/{slug}/{companyId}` | None (JSON embedded in HTML) | None | Yes | — |
| [Cornerstone OnDemand](https://cleanjobdata.com/articles/how-to-scrape-cornerstone-job-listings) | No public API: career-site JSON only | Browser session | Page number, `pageSize` 50 | Partial, detail call for full text | — |
| [Dayforce](https://cleanjobdata.com/articles/how-to-scrape-dayforce-job-listings) | No public API: career-site JSON only | Browser session | Offset (`paginationStart`), 25 per page | Yes, plain text | Detail: `HiringMinRate` / `HiringMaxRate` |
| [Eightfold](https://cleanjobdata.com/articles/how-to-scrape-eightfold-job-listings) | `GET https://{host}/api/apply/v2/jobs?start=0&num=10` | None | Offset (`start`), fixed 10 per page | Unreliable, detail call | — |
| [Freshteam](https://cleanjobdata.com/articles/how-to-scrape-freshteam-job-listings) | `GET https://{company}.freshteam.com/jobs` | None (HTML) | None | No, job page JSON-LD | — |
| [Gem](https://cleanjobdata.com/articles/how-to-scrape-gem-job-listings) | `POST https://jobs.gem.com/api/public/graphql/batch` | None | None, one call | No, second GraphQL query | `compensationHtml` (text) |
| [Greenhouse](https://cleanjobdata.com/articles/how-to-scrape-greenhouse-job-listings) | `GET https://boards-api.greenhouse.io/v1/boards/{company}/jobs?content=true` | None | None, one call | Yes, with `content=true` | — |
| [iCIMS](https://cleanjobdata.com/articles/how-to-scrape-icims-job-listings) | `GET https://careers-{company}.icims.com/jobs/search?ss=1&pr=0&in_iframe=1` | None (HTML) | Page number (`pr`) | No, job page JSON-LD | JSON-LD `baseSalary` |
| [JazzHR](https://cleanjobdata.com/articles/how-to-scrape-jazzhr-job-listings) | `GET https://app.jazz.co/feeds/export/jobs/{company}` | None (XML) | None | Yes | JSON-LD `baseSalary` on job page (rare) |
| [Jobvite](https://cleanjobdata.com/articles/how-to-scrape-jobvite-job-listings) | `GET https://jobs.jobvite.com/{company}/jobs` | None (HTML) | None | No, job page JSON-LD | JSON-LD `baseSalary` |
| [Lever](https://cleanjobdata.com/articles/how-to-scrape-lever-job-listings) | `GET https://api.lever.co/v0/postings/{company}?mode=json` | None | None, one call | Yes | `salaryRange` |
| [Oracle Recruiting Cloud](https://cleanjobdata.com/articles/how-to-scrape-oracle-recruiting-job-listings) | `GET https://{host}/hcmRestApi/resources/latest/recruitingCEJobRequisitions?onlyData=true&finder=findReqs;siteNumber={site},limit=100,offset=0` | None | Offset (`offset` in `finder`), 100 per page | No, detail call | — |
| [PageUp](https://cleanjobdata.com/articles/how-to-scrape-pageup-job-listings) | `GET https://careers.pageuppeople.com/{tenantId}/{siteCode}/{locale}/jobs.json` | None | None, one call | Yes (`Overview`) | `Salary` (free text, mostly placeholders) |
| [Paycom](https://cleanjobdata.com/articles/how-to-scrape-paycom-job-listings) | No public API: career-site JSON only | Browser session | `skip` / `take`, 50 per page | Truncated, detail call | Detail: `baseSalary` |
| [Paylocity](https://cleanjobdata.com/articles/how-to-scrape-paylocity-job-listings) | `GET https://recruiting.paylocity.com/recruiting/jobs/All/{guid}` | None (JSON embedded in HTML) | None | No, job page JSON-LD | JSON-LD `baseSalary` (some jobs) |
| [Personio](https://cleanjobdata.com/articles/how-to-scrape-personio-job-listings) | `GET https://{company}.jobs.personio.de/xml?language=en` | None (XML) | None | Yes | `salaryInformation` |
| [Pinpoint](https://cleanjobdata.com/articles/how-to-scrape-pinpoint-job-listings) | `GET https://{company}.pinpointhq.com/postings.json` | None | None, one call | Yes | `compensation_minimum` / `compensation_maximum` |
| [Recruitee](https://cleanjobdata.com/articles/how-to-scrape-recruitee-job-listings) | `GET https://{company}.recruitee.com/api/offers/` | None | None, one call | Yes | `salary` |
| [Rippling](https://cleanjobdata.com/articles/how-to-scrape-rippling-job-listings) | `GET https://api.rippling.com/platform/api/ats/v2/board/{slug}/jobs?page=0&pageSize=100` | None | Page number (0-based), 100 per page | No, v1 detail call | Detail: `payRangeDetails` |
| [SmartRecruiters](https://cleanjobdata.com/articles/how-to-scrape-smartrecruiters-job-listings) | `GET https://api.smartrecruiters.com/v1/companies/{company}/postings?limit=100&offset=0` | None | Offset, 100 per page | No, detail call | — |
| [SAP SuccessFactors](https://cleanjobdata.com/articles/how-to-scrape-successfactors-job-listings) | `POST https://{company}.jobs.hr.cloud.sap/services/recruiting/v1/jobs` | None | Page number; HTML fallback uses `startrow` +25 | No, job page HTML | — |
| [Taleo](https://cleanjobdata.com/articles/how-to-scrape-taleo-job-listings) | `GET https://{sub}.tbe.taleo.net/{pod}/ats/careers/v2/searchResults?org={org}&cws={cws}` | None (HTML) | Offset (`rowFrom`), 25 per page | No, job page JSON-LD | JSON-LD `baseSalary` |
| [Teamtailor](https://cleanjobdata.com/articles/how-to-scrape-teamtailor-job-listings) | `GET https://{company}.teamtailor.com/jobs.json` | None | Cursor (`next_url`) | Yes (`content_html`) | — |
| [UKG Pro Recruiting](https://cleanjobdata.com/articles/how-to-scrape-ukg-job-listings) | `POST https://recruiting.ultipro.com/{companyCode}/JobBoard/{boardId}/JobBoardView/LoadSearchResults` | None | `Skip` / `Top`, 50 per page | No, detail page | Detail: `CompensationAnnualMinimum` etc. |
| [Workable](https://cleanjobdata.com/articles/how-to-scrape-workable-job-listings) | `GET https://apply.workable.com/api/v1/widget/accounts/{company}?details=true` | None | None, one call | Yes, with `details=true` | — |
| [Workday](https://cleanjobdata.com/articles/how-to-scrape-workday-job-listings) | `POST https://{company}.wd{N}.myworkdayjobs.com/wday/cxs/{company}/{site}/jobs` | None | Offset / limit, 20 per page | No, detail call | — |
| [Zoho Recruit](https://cleanjobdata.com/articles/how-to-scrape-zoho-recruit-job-listings) | `GET https://{company}.zohorecruit.com/recruit/v2/public/Job_Openings?pagename={PageName}` | None | None, one call | No, detail call | — |

## ATS with a public JSON API (no key)

These platforms answer a plain HTTP request with JSON. No account, no key, no browser session. They are the same endpoints the platforms' own careers pages call.

### Greenhouse

One call returns every job on the board. Add `content=true` and each job carries its full HTML description, so there's no per-job request. Use `first_published` as the post date; `updated_at` moves whenever a recruiter edits the job. There's no salary field. Full walkthrough: [Greenhouse job board API](https://cleanjobdata.com/articles/how-to-scrape-greenhouse-job-listings).

```bash
curl "https://boards-api.greenhouse.io/v1/boards/acme/jobs?content=true"
```

### Lever

`mode=json` returns the whole board as a JSON array. The description is split across `description`, `lists` (the bulleted sections) and `additional`, so join all three. `salaryRange` has `min`, `max`, `currency` and an `interval` you need to annualize, and `createdAt` is a millisecond timestamp. See the [Lever postings API guide](https://cleanjobdata.com/articles/how-to-scrape-lever-job-listings).

```bash
curl "https://api.lever.co/v0/postings/acme?mode=json"
```

### Ashby

Ashby's posting API returns all jobs with `descriptionHtml` in one response. Pass `includeCompensation=true` to get the `compensation` object, which has both a summary string and structured `summaryComponents`. The [Ashby guide](https://cleanjobdata.com/articles/how-to-scrape-ashby-job-listings) covers the pay and address objects.

```bash
curl "https://api.ashbyhq.com/posting-api/job-board/acme?includeCompensation=true"
```

### SmartRecruiters

The postings list pages with `limit` and `offset` (100 per page) and returns summaries only. The job ad (`jobAd.sections`) needs one detail request per posting at `/postings/{id}`. There's no salary field. Details in the [SmartRecruiters API guide](https://cleanjobdata.com/articles/how-to-scrape-smartrecruiters-job-listings).

```bash
curl "https://api.smartrecruiters.com/v1/companies/acme/postings?limit=100&offset=0"
```

### Workable

The widget endpoint returns every published job, and `details=true` adds the full description to each one. Dates are in `published_on`; there's no salary field. See the [Workable API guide](https://cleanjobdata.com/articles/how-to-scrape-workable-job-listings).

```bash
curl "https://apply.workable.com/api/v1/widget/accounts/acme?details=true"
```

### Recruitee

One call to `/api/offers/` returns all open offers. The body is split between `description` and `requirements`, so concatenate them. The `salary` object has `min`, `max`, `currency` and `period`. More in the [Recruitee guide](https://cleanjobdata.com/articles/how-to-scrape-recruitee-job-listings).

```bash
curl -H "accept: application/json" "https://acme.recruitee.com/api/offers/"
```

### Teamtailor

Every Teamtailor career site serves a JSON Feed at `/jobs.json`. Each item has the full `content_html` plus a schema.org `_jobposting` object; follow `next_url` until it's empty. The host is the career-site host, which can be a custom domain. See the [Teamtailor jobs.json guide](https://cleanjobdata.com/articles/how-to-scrape-teamtailor-job-listings).

```bash
curl -H "accept: application/json" "https://acme.teamtailor.com/jobs.json"
```

### BambooHR

`/careers/list` returns every opening in one call but without descriptions. Each job's description, `datePosted` and free-text `compensation` come from `/careers/{id}/detail`. See the [BambooHR guide](https://cleanjobdata.com/articles/how-to-scrape-bamboohr-job-listings).

```bash
curl -H "accept: application/json" "https://acme.bamboohr.com/careers/list"
```

### Workday

Workday's CXS API is a `POST` with a JSON body of `limit`, `offset`, `appliedFacets` and `searchText`, 20 jobs per page. The list gives a relative `postedOn` ("Posted 30+ Days Ago"); the detail call at `/job/{externalPath}` returns the full description and an exact `startDate`. See the [Workday guide](https://cleanjobdata.com/articles/how-to-scrape-workday-job-listings).

```bash
curl -X POST "https://acme.wd5.myworkdayjobs.com/wday/cxs/acme/External/jobs" \
  -H "content-type: application/json" \
  -d '{"appliedFacets":{},"limit":20,"offset":0,"searchText":""}'
```

### Other platforms with public JSON

- **Pinpoint.** `/postings.json` returns every job with its description in one call. Salary is in `compensation_minimum` / `compensation_maximum`, but only use it when `compensation_visible` is true. The JSON has no posting date; the same site's `/jobs.rss` feed does.
- **Rippling.** The v2 list (`page`, `pageSize=100`) has structured locations but no description, date or pay. The v1 detail call (`/platform/api/ats/v1/board/{slug}/jobs/{id}`) has the description, `createdOn` and `payRangeDetails`.
- **Zoho Recruit.** `/recruit/v2/public/Job_Openings` needs a `pagename` parameter, which differs per company and is embedded in the career page as `page_name`. It returns the whole list in one call and rejects any extra query parameter such as `page` or `limit`. The description (`Job_Description`) comes from `/Job_Openings/{id}` with the same `pagename`.
- **Eightfold.** `/api/apply/v2/jobs` pages with `start` at a fixed 10 jobs per page, whatever you pass in `num`. The list's `job_description` is often missing, so use the detail call at `/api/apply/v2/jobs/{id}`. Some tenants answer the v2 list with a 403 and use a different search API (`/api/pcsx/search`) instead.
- **PageUp.** `jobs.json` returns every open job with the full `Overview` description in one array. Hosted tenants use `/{tenantId}/{siteCode}/{locale}/jobs.json`; white-labeled custom domains use `/{locale}/listing/jobs.json`. The two shapes aren't interchangeable.
- **Oracle Recruiting Cloud.** A REST endpoint under `/hcmRestApi/resources/latest/` with paging inside the `finder` parameter (`limit`, `offset`). The description (`ExternalDescriptionStr`) comes from `recruitingCEJobRequisitionDetails` with a `ById` finder.
- **ADP Workforce Now.** OData-style `$top` / `$skip` paging on `job-requisitions`, but ADP returns about 20 rows per page whatever `$top` says, so advance by the number of rows you actually received. Descriptions come from `/job-requisitions/{itemID}`, and pay is in `payGradeRange`.
- **UKG Pro Recruiting.** `LoadSearchResults` is a `POST` with `Top` / `Skip` in the body and needs no cookie or token. The detail is an HTML page (`OpportunityDetail?opportunityId={id}`) with the job object embedded in an inline script, including structured locations and compensation fields.
- **SAP SuccessFactors.** Some Career Site Builder tenants answer a `POST` to `/services/recruiting/v1/jobs` with paged JSON. When they don't, `sitemap.xml` or the HTML `/search?startrow=N` page gives you the job URLs, and each job page carries schema.org microdata.
- **Gem.** The career page is a client-rendered shell that calls a public GraphQL endpoint. One `JobBoardList` query returns every posting; the description, publish time and `compensationHtml` come from a second `ExternalJobPosting` query, and both can be batched in one `POST`. Gem's documented Job Board API needs the employer's own key, so it can't be used for other companies' boards.

### Public XML feeds: Personio and JazzHR

- **Personio.** `https://{company}.jobs.personio.de/xml` (or `.jobs.personio.com`) is a full XML export with descriptions in `jobDescriptions` and pay in `salaryInformation`. Request `?language=en` first and fall back to the plain feed for positions whose English description is empty. Some accounts disable the feed; then you read the career page HTML and each job page. See the [Personio XML feed guide](https://cleanjobdata.com/articles/how-to-scrape-personio-job-listings).
- **JazzHR.** `https://app.jazz.co/feeds/export/jobs/{company}` has title, location, description and type for every open job. It doesn't carry the posting date; that comes from the JSON-LD on each job page at `{company}.applytojob.com`. Accounts can disable the export too.

```bash
curl -H "accept: application/xml" "https://acme.jobs.personio.de/xml?language=en"
```

## ATS without a public jobs API

For these platforms there's no documented, keyless JSON endpoint. The data is still public, because it's on the career page, but you get it in one of three ways.

### JSON tied to a career-page session

Cornerstone OnDemand, Paycom, Dayforce and IBM Kenexa BrassRing do serve jobs as JSON, but only to their own career pages: the search API expects the session the page sets up when it loads. These aren't public APIs, they change without notice, and each tenant can be configured differently, so treat them as internal endpoints rather than something to build a product on.

What you get once you're there differs: Dayforce and Paycom include pay rates on the job detail, Cornerstone's list returns a shortened description with the full ad on the detail, and BrassRing lets each employer name its own fields, so the title and location columns vary by tenant. If you need these platforms covered, this is where a maintained source like the [CleanJobData Jobs API](https://cleanjobdata.com/docs/api/jobs) saves the most work.

### Job data embedded in the career page

- **Paylocity.** The list page at `recruiting.paylocity.com/recruiting/jobs/All/{guid}` assigns every job to `window.pageData` in an inline script. Parse that JSON, then read the JSON-LD on each `/Recruiting/Jobs/Details/{id}` page for the description and salary.
- **Comeet.** Every Comeet page embeds `COMPANY_POSITIONS_DATA`, a JavaScript array of all open positions with full descriptions, so one HTML request gets everything.

### HTML list plus JSON-LD on each job page

Here you collect job links from server-rendered HTML, then fetch each job page and parse its `<script type="application/ld+json">` `JobPosting` block. That block uses the same schema.org fields Google for Jobs reads: `title`, `description`, `datePosted`, `jobLocation`, `baseSalary`. See our [JobPosting schema guide](https://cleanjobdata.com/articles/jobposting-schema-guide) for the field reference.

- **[iCIMS](https://cleanjobdata.com/articles/how-to-scrape-icims-job-listings).** Page through `/jobs/search?ss=1&in_iframe=1` with a zero-based `pr` parameter, then read each `/jobs/{id}/job?in_iframe=1`.
- **[Taleo](https://cleanjobdata.com/articles/how-to-scrape-taleo-job-listings).** `searchResults` pages by `rowFrom` in steps of 25; job pages are `viewRequisition?org={org}&cws={cws}&rid={rid}`. Some tenants have no usable JSON-LD and need HTML parsing.
- **Jobvite.** `jobs.jobvite.com/{company}/jobs` lists every job link; each `/job/{id}` page has the JSON-LD.
- **Breezy HR.** The career-site root lists `/p/{id}-{slug}` links; each job page has the JSON-LD, including `baseSalary` when the employer sets one.
- **Freshteam.** `/jobs` renders every job in one page. Job pages have JSON-LD, but the description is HTML-entity-encoded inside the JSON string and `remote` is the string `"true"` or `"false"`.
- **Avature.** `SearchJobs` pages by `jobOffset`, 10 links per page. Job pages are plain HTML label/value pairs that differ by tenant, so the description has to be picked out of them. Many tenants publish `sitemap.xml` with a per-job `lastmod`.

## How to find a company's ATS and identifier

Start from the company's careers page and follow the "Apply" or "View jobs" link. The host usually names the platform, and the path or subdomain is the identifier:

| ATS | Career URL pattern | Identifier |
|---|---|---|
| ADP | `workforcenow.adp.com/mascsr/default/mdf/recruitment/recruitment.html?cid=...&ccId=...&lang=...` | `cid`, `ccId` and `lang` (the list can come back empty with the wrong `lang`) |
| Ashby | `jobs.ashbyhq.com/acme` | `acme` |
| Avature | `acme.avature.net/careers/SearchJobs` | host plus path (`careers`) |
| BambooHR | `acme.bamboohr.com/careers` | `acme` |
| BrassRing | `sjobs.brassring.com/TGnewUI/Search/Home/Home?partnerid=...&siteid=...` | `partnerid` and `siteid` |
| Breezy HR | `acme.breezy.hr` | the host |
| Comeet | `comeet.com/jobs/acme/12.345` | slug and company ID (both required) |
| Cornerstone | `acme.csod.com/ux/ats/careersite/4/home` | tenant and career site ID |
| Dayforce | `jobs.dayforcehcm.com/acme/CANDIDATEPORTAL` | client namespace and job board code |
| Eightfold | `acme.eightfold.ai/careers` (or a vanity host) | the host |
| Freshteam | `acme.freshteam.com/jobs` | `acme` |
| Gem | `jobs.gem.com/acme` | `acme` |
| Greenhouse | `boards.greenhouse.io/acme` or `job-boards.greenhouse.io/acme` | `acme` (the board token) |
| iCIMS | `careers-acme.icims.com/jobs/search` | the subdomain |
| JazzHR | `acme.applytojob.com/apply` | `acme` |
| Jobvite | `jobs.jobvite.com/acme/jobs` | `acme` |
| Lever | `jobs.lever.co/acme` | `acme` |
| Oracle Recruiting | `xxxx.fa.us2.oraclecloud.com/hcmUI/CandidateExperience/en/sites/CX_1/` | host and site number (`CX_1`) |
| PageUp | `careers.pageuppeople.com/822/cw/en-us/listing` | the listing path |
| Paycom | `paycomonline.net/v4/ats/web.php/jobs?clientkey=...` | `clientkey` |
| Paylocity | `recruiting.paylocity.com/recruiting/jobs/All/{guid}` | the GUID |
| Personio | `acme.jobs.personio.de` or `acme.jobs.personio.com` | slug and domain (the same slug can exist on both) |
| Pinpoint | `acme.pinpointhq.com` | `acme` |
| Recruitee | `acme.recruitee.com` | `acme` |
| Rippling | `ats.rippling.com/acme-careers/jobs` | `acme-careers` (often not the company name) |
| SmartRecruiters | `jobs.smartrecruiters.com/Acme` | `Acme` (case as shown) |
| SuccessFactors | `acme.jobs.hr.cloud.sap` | `acme` |
| Taleo | `acme.tbe.taleo.net/acme04/ats/careers/v2/searchResults?org=...&cws=...` | subdomain, pod path, `org` and `cws` |
| Teamtailor | `acme.teamtailor.com` or a custom domain | the host |
| UKG | `recruiting.ultipro.com/ACM1000ACME/JobBoard/{boardId}` | host, company code and board ID |
| Workable | `apply.workable.com/acme` | `acme` |
| Workday | `acme.wd5.myworkdayjobs.com/External` | tenant, `wd` number and site name |
| Zoho Recruit | `acme.zohorecruit.com/jobs/Careers` | tenant and `pagename` (from the page source) |

Many companies embed the board on their own domain instead, so the URL bar shows `careers.acme.com`. In that case open your browser's developer tools, switch to the Network tab, filter by Fetch/XHR, and reload the careers page. The request that returns the job list shows the platform's API host and the identifier in its URL or body. This is also how you find the values that never appear in the address bar, such as Zoho's `pagename` or Cornerstone's regional API host.

There's no public directory that maps every company to its ATS identifier. Finding one board you already know about is easy; doing it for thousands of employers is a separate problem.

## Things that differ between platforms

**Salary availability.**

- Structured pay fields exist on Lever (`salaryRange`), Ashby (`compensation`), Recruitee (`salary`), Pinpoint, Rippling, Personio, ADP, UKG and Dayforce, and wherever JSON-LD carries `baseSalary` (iCIMS, Jobvite, Taleo, Breezy, Paylocity, Paycom).
- Greenhouse, Workable, SmartRecruiters, Teamtailor, Workday and Oracle give you no salary field we read, so pay only shows up in description text.
- Units vary: hourly, monthly and yearly figures sit in the same field, marked by Lever's `interval`, Recruitee's `period`, Personio's `type` or JSON-LD's `unitText`. Annualize before you compare. More in the [salary extraction guide](https://cleanjobdata.com/guides/salary-data-extraction).
- A salary field isn't always a number. PageUp's `Salary` is usually placeholder prose like "Commensurate", BambooHR's `compensation` is free text, and Pinpoint only means it when `compensation_visible` is true.

**Location formats.**

- Some sources are structured: Comeet and Cornerstone give ISO alpha-2 countries, Rippling v2 and Zoho give separate city, state and country fields, and ADP's `shortName` reads " City, ST, CC".
- Others are free text: Greenhouse's `location.name`, PageUp's `LocationList` (sometimes a campus name on both sides of the pipe), and Personio office names.
- Structured doesn't mean correct. Freshteam's JSON-LD swaps `addressRegion` and `addressLocality`, and Comeet's `state` field was wrong on real postings we checked.
- JazzHR and Zoho give full country names ("United States"), so you need a name-to-code lookup. See the [location normalization guide](https://cleanjobdata.com/guides/job-location-normalization).

**Pagination.**

- Don't trust the page size you asked for. ADP ignores `$top`, Eightfold ignores `num`, Dayforce is fixed at 25 and BrassRing at 50. Advance by the rows you actually received and stop on an empty page or the reported total.
- Offset paging over a sort with many ties can skip or repeat jobs. On BrassRing, sorting by the default date or title lost jobs across pages; sort by a unique key when the API allows it, and dedupe by job ID either way.
- Workday can report a `total` larger than it will actually page through, so stop on an empty page as well.

**Published-date reliability.**

- Prefer the field that records first publication: Greenhouse `first_published` over `updated_at`, Gem `firstPublishedTsSec`, Eightfold `t_create`.
- Some lists have no date at all: Pinpoint's JSON (use the RSS feed), Rippling's v2 list and Gem's list (use the detail call), and JazzHR's XML (use the job page).
- Some dates are coarse or oddly encoded: Workday's list gives relative text, Dayforce's list date has no real time, Zoho's `Date_Opened` is date-only, Cornerstone uses `M/D/YYYY`, and PageUp uses .NET `/Date(1791295200000)/` strings.
- Some platforms only expose a last-updated date: BrassRing's `lastupdated` and Comeet's `time_updated` are the only dates available.

**Detecting closed jobs.** None of these platforms send webhooks to outsiders. Poll the list, diff job IDs, and treat a job as closed when it drops out of a complete list. A partial list (a failed page, a truncated response) should never expire jobs. See [syncing job data in depth](https://cleanjobdata.com/guides/syncing-job-data-in-depth).

## FAQ

### Which ATS have a public API for job postings?

Greenhouse, Lever, Ashby, SmartRecruiters, Workable, Recruitee, Teamtailor, BambooHR, Pinpoint, Rippling, Zoho Recruit, Eightfold, PageUp, Workday, Oracle Recruiting Cloud, ADP Workforce Now, UKG Pro Recruiting and Gem all serve job listings as JSON with no API key. Personio and JazzHR publish keyless XML feeds. iCIMS, Taleo, Jobvite, Avature, Breezy HR and Freshteam have no public jobs API, and Cornerstone, Paycom, Dayforce and BrassRing need a token or session from the career page first.

### Do I need an API key to get Greenhouse jobs?

No. `https://boards-api.greenhouse.io/v1/boards/{board_token}/jobs?content=true` is public and returns every published job with full descriptions. Greenhouse's Harvest API does need a key, but that's for the employer's own internal hiring data, not their public job board.

### Is scraping ATS job boards allowed?

These endpoints serve job listings that employers publish so the public can find and apply to them, and none of the ones above require you to log in. That doesn't settle every question. Each platform has its own terms of service, employers may have their own terms on their career sites, and some data uses carry extra obligations. Check the relevant terms for your use case, respect `robots.txt`, keep your request rate modest, cache what you've fetched, and identify your client honestly. This isn't legal advice; if it matters for your business, ask a lawyer.

### How do I get jobs from every ATS at once?

You either build and maintain a collector per platform, using the table above, or use an API that already does it. CleanJobData collects from these ATS platforms and serves the jobs in one normalized schema: see the [Jobs API docs](https://cleanjobdata.com/docs/api/jobs).

### Does Workday have a public jobs API?

Yes, though it's undocumented. Every Workday career site calls `POST https://{tenant}.wd{N}.myworkdayjobs.com/wday/cxs/{tenant}/{site}/jobs` with no key, 20 jobs per page, and you need one detail call per job for the description and exact date. The [Workday guide](https://cleanjobdata.com/articles/how-to-scrape-workday-job-listings) has the request body and response shape.
