# Paylocity Jobs API

> Query jobs from Paylocity boards through one API with ?source=paylocity — plus how Paylocity's recruiting board really behaves.

Source: https://hyperjobs.io/ats/paylocity

Paylocity is a payroll and HR platform with recruiting bolted on, sold to US small
and mid-sized businesses. Its boards live at
`recruiting.paylocity.com/recruiting/jobs/All/<guid>`, and they are the ones
churches, clinics, school districts and regional operators actually post to. There
are 4,782 Paylocity postings in this dataset — a number that moves hourly, so read
it from [`/v1/meta`](/docs/api-reference/meta) rather than trusting this sentence.

This is the platform in our small-business set with the most honest caveat
attached, and the caveat is the interesting part.

## Try it

```bash
curl -G https://api.hyperjobs.io/v1/jobs \
  -H "Authorization: Bearer $HYPERJOBS_KEY" \
  --data-urlencode "source=paylocity" \
  --data-urlencode "time_frame=7d" \
  --data-urlencode "limit=2"
```

```json title="Illustrative — the shape, not a live response"
{
  "data": [
    {
      "id": "paylocity:1849327",
      "title": "Parish Office Coordinator",
      "company": {
        "slug": "001ed382-3060-4f7b-a324-d44d173bb6c8",
        "name": "St Catherine of Siena Church",
        "domain": "stcatherinekc.org",
        "website": "https://stcatherinekc.org",
        "logo": "https://media.licdn.com/dms/image/…",
        "industry": "Religious Institutions",
        "employee_count": 31
      },
      "location": {
        "remote": false,
        "countries": ["United States"],
        "cities": ["Kansas City"],
        "regions": ["MO"],
        "raw": ["Kansas City, MO, United States"]
      },
      "employment_type": ["FULL_TIME"],
      "work_arrangement": null,
      "seniority": null,
      "salary": null,
      "skills": ["administration", "scheduling"],
      "taxonomies": ["Administrative"],
      "apply_url": "https://recruiting.paylocity.com/Recruiting/Jobs/Details/1849327",
      "url": "https://recruiting.paylocity.com/Recruiting/Jobs/Details/1849327",
      "source": "paylocity",
      "posted_at": "2026-07-08T15:41:00.000Z",
      "expires_at": null,
      "updated_at": "2026-07-14T10:22:18.000Z"
    }
  ],
  "total": 2,
  "limit": 2,
  "offset": 0
}
```

## How Paylocity boards actually work

The board server-renders its whole list into a global, and the body needs a second
call:

```bash
# 1. List — the board HTML carries `window.pageData` with every posting in it
curl -L "https://recruiting.paylocity.com/recruiting/jobs/All/<guid>"

# 2. Detail — schema.org JobPosting JSON-LD
curl -L "https://recruiting.paylocity.com/Recruiting/Jobs/Details/<JobId>"
```

`window.pageData` is genuinely good: a `Jobs` array with `JobTitle`, a full
structured `JobLocation`, `IsRemote`, `HiringDepartment` and a `PublishedDate`
that carries a real offset. What it does not carry is the description —
`Description` is always `""` on the list — so the body is one detail call per
posting, and that is where this platform gets difficult.

### Ignore the documented feed API

<Warning title="`/recruiting/api/feed/jobs/{guid}` returns HTTP 500">
  Paylocity advertises a feed endpoint as its integration route. Called with a
  board GUID it returns **500** — verified on three tenants.

  It isn't broken. The feed key is a **separate per-tenant opt-in credential**,
  not the GUID sitting in the board URL. So the official-looking endpoint is a
  dead end for public boards, and anyone who "fixes" a working scraper by
  switching to the documented API gets 500s for their trouble.

  `window.pageData` is the real route.
</Warning>

### The burst-429 is the whole story

The detail endpoint throttles on a **burst budget, not a rate**. Roughly 12 or 13
requests get through and the rest 429 — measured identically at concurrency 4, 3
**and** 2. Turning the pool down does not fix it, because it isn't a concurrency
problem. The budget refills within seconds.

Here's why that matters more than a normal rate limit: a 429 on the detail page
means no JSON-LD, which means **a description-less row that still counts as a
successful scrape**. Nothing errors. The board reports success. The jobs are
hollow.

| Approach | Descriptions |
| --- | --- |
| Fetch details with no 429 handling | **14%** |
| Backoff and retry, 1s → 16s | **93%** |

The residual 7% on that measured board is real and not fixable: those postings
have no JSON-LD and no description div, because the employer left the body blank.

<Note title="What that means for the rows we serve">
  Across all 4,782 Paylocity rows in the dataset today, **76.5% carry a
  description** — not the 93% a clean single-board run achieves.

  The gap is the same throttle, met at rotation scale rather than on one board
  with room to breathe. We'd rather print the number that describes the rows you
  will actually get than the one from the best run. It's among the lowest
  description fills of anything we serve, and it is the honest state of this
  platform today. Check [`/v1/meta`](/docs/api-reference/meta) and
  [coverage](/docs/reference/coverage) for where it currently sits.
</Note>

## What bites you

**`LocationName` is a venue, not a place.** It reads
`"St Gabriel Catholic Church Kansas City"` — a building's name, which no geocoder
will do anything sensible with. The structured `City` / `State` / `Country` on
`JobLocation` is the ground truth. Take the label instead and you geocode a
parish hall.

**The board token is a bare GUID, and so is the company name.** Every Paylocity
board is identified by a UUID, which means the fallback company name minted from
the token is a UUID too. The detail page's `hiringOrganization.name` is the only
human-readable employer this platform ever emits — so the description fetch that
the 429 eats is also the identity fetch. Lose one, lose both.

**There's no tier-1 company-domain route.** Paylocity publishes nothing that
resolves a board to a company domain, so identity has to be inferred from outside
the source and does not always land. A related trap: the JS-shell notice on some
board pages points at `enable-javascript.com`, which an unguarded domain-grabber
will cheerfully record as the employer's website.

**A bare UUID looks like something else's id.** A guard that treats bare UUIDs as
junk silently dropped every ADP and Paylocity board at once. Here the UUID *is*
the real board id.

## Or use ours

Two calls per posting against a burst throttle that no concurrency setting
avoids, across 2,653 GUID-named boards, where the fetch you need for the body is
also the fetch you need for the employer's name.

You get the backoff already paid for, the structured address preferred over the
venue label, `PublishedDate` carried through with its offset, the employer's name
lifted out of the detail JSON-LD instead of a UUID, Paylocity rows deduplicated
against the same job on [LinkedIn](/ats/linkedin) or elsewhere, expiry by board
diffing, and whatever [company identity](/docs/objects/company) can be resolved
for a platform that publishes none. And you get told, in the note above, exactly
how complete the descriptions are rather than finding out later.

Query it with `?source=paylocity`.
[Quickstart](/docs/quickstart), then [pricing](/pricing).
