# CareerPlug Jobs API

> Query jobs from CareerPlug boards through one API with ?source=careerplug — plus how CareerPlug's paginated board really behaves.

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

CareerPlug is hiring software for US franchises and small businesses — Meineke,
Nothing Bundt Cakes, Mathnasium and several thousand independent operators like
them. Every posting page carries schema.org JobPosting JSON-LD, and it's the
richest structured markup in the small-business pool. There are 15,133 CareerPlug
postings in this dataset, **100% of them with a real description** — counts move
hourly, so read them from [`/v1/meta`](/docs/api-reference/meta) rather than
trusting this sentence.

## Try it

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

```json title="Illustrative — the shape, not a live response"
{
  "data": [
    {
      "id": "careerplug:360clean-columbia_419257",
      "title": "Cleaning Technician, Day Shifts",
      "company": {
        "slug": "360clean-columbia",
        "name": "360clean of Columbia - 2765",
        "domain": "360clean.com",
        "website": "https://360clean.com",
        "logo": "https://media.licdn.com/dms/image/…",
        "industry": "Facilities Services",
        "employee_count": 24
      },
      "location": {
        "remote": false,
        "countries": ["United States"],
        "cities": ["Lexington"],
        "regions": ["SC"],
        "raw": ["Lexington, SC, US"]
      },
      "employment_type": ["PART_TIME"],
      "work_arrangement": null,
      "seniority": null,
      "salary": null,
      "skills": ["cleaning", "sanitation"],
      "taxonomies": ["Facilities"],
      "apply_url": "https://360clean-columbia.careerplug.com/jobs/419257/apps/new",
      "url": "https://360clean-columbia.careerplug.com/jobs/419257/apps/new",
      "source": "careerplug",
      "posted_at": "2026-06-19T16:27:17.000Z",
      "expires_at": null,
      "updated_at": "2026-07-14T09:03:44.000Z"
    }
  ],
  "total": 2,
  "limit": 2,
  "offset": 0
}
```

## How CareerPlug boards actually work

An HTML list, then a page per posting:

```bash
# 1. List — 30 postings per page, ids in the hrefs
curl -L "https://<tenant>.careerplug.com/jobs?page=1"

# 2. Detail — JobPosting JSON-LD on the application page
curl -L "https://<tenant>.careerplug.com/jobs/<id>/apps/new"
```

`<tenant>` is the subdomain. The list is markup, so the ids come out of the
`/jobs/<id>` hrefs; everything worth having comes from the detail page's JSON-LD.

### The pagination is real, which is rarer than it sounds

Half this pool ships a page parameter that the server ignores, so you learn to
distrust them. CareerPlug's is genuine, and it was verified the only way that
settles it: by crawling a board to exhaustion. On `meineke-careers`, 30 ids per
page with **zero overlap through page 12**, 4 on page 13, 0 on page 14 — **364
distinct postings**.

So you page. But you page until a page contributes nothing new, rather than
trusting a total, because the board exposes no count and a fixed page ceiling
would quietly truncate exactly the big franchise tenants you most wanted.

### The JSON-LD is the best in this pool

| JSON-LD field | Gives you |
| --- | --- |
| `description` | The full body |
| `datePosted` | A real publish date, ISO with a zone |
| `validThrough` | A declared expiry when the employer set one |
| `baseSalary` | Structured pay, min/max with `unitText` |
| `jobLocation.address` | A full PostalAddress, not a string |
| `employmentType` | Standard schema.org values |
| `hiringOrganization.sameAs` | **The franchisee's own website** |

That last row is the one to notice. `sameAs` is the franchisee's real site, which
makes CareerPlug one of the few platforms in this segment where a posting arrives
already pointing at the company that posted it. Identity, for free, from the
source.

## What bites you

**The brand tenant is empty on purpose.** `applebees-careers` and
`burger-king-careers` return **200 with zero jobs**, and that is not a block, a
throttle or a broken parse. Hiring on this platform routes to per-franchisee
tenants, so the brand-level board is genuinely empty. An empty CareerPlug board
means empty. Retrying it, escalating it to a proxy tier or flagging it as failed
all burn budget on a board that is working exactly as designed.

**The tenant name is a brand slug, not the employer.** Derive a company name from
the board token and `meineke-careers` becomes "Meineke-Careers" — a brand, when
the entity actually hiring is one franchisee in one town. The JSON-LD's
`hiringOrganization` is the real employing entity, and on this platform they are
routinely different things.

**Proxying this platform is a waste of money.** The list pages are HTML and each
detail page is a full ~190KB document from which a small JSON-LD block is kept. A
single board runs **7–17MB**. It works direct, so residential GB spent here buys
nothing at all. Ours is pinned to never proxy.

**Salary is in the markup, not on every posting.** `baseSalary` is structured and
parsed when the employer published it. Plenty of franchise postings simply don't.

## Or use ours

The shape is friendly. The volume is the tax: 30 postings per page, a ~190KB
detail fetch per posting, paged to exhaustion, across the 505 CareerPlug boards in
our queue — and the tenants worth having are the long tail of individual
franchisees, not the brands you'd think to look up.

You get every board crawled to its real end rather than to a guessed page count,
the JSON-LD parsed into the same [`Job`](/docs/objects/job) shape as every other
source, the franchisee's real name and site preferred over the brand slug, empty
brand tenants understood as empty instead of retried, structured addresses and
dates carried through, CareerPlug rows deduplicated against the same job on
[LinkedIn](/ats/linkedin) or elsewhere, expiry by board diffing, and the
[company join](/docs/objects/company) — which CareerPlug, unusually, helps with.

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