All platforms

ApplicantPro Jobs API

Query jobs from ApplicantPro boards through one API with ?source=applicantpro — plus how ApplicantPro's core jobs endpoint really behaves.

?source=applicantprosource reference

ApplicantPro is applicant tracking for small US employers, and its customer list skews municipal: city governments, aquatic centers, libraries, airports, non-profits. Boards live at <tenant>.applicantpro.com, backed by a JSON API that's unusually rich once you can get it to answer you. There are 251 ApplicantPro postings in this dataset, 100% of them with a real description — counts move hourly, so read them from /v1/meta rather than trusting this sentence.

Why that count is small, and what it isn't

251 is not this platform's size and it isn't a quality verdict. The adapter is verified at 100% descriptions across every tenant tested, and our queue holds 423 ApplicantPro boards — most of which have not yet been through a full rotation. The number is a coverage state, not a ceiling, and it should grow. /v1/meta is the live answer; coverage is where the rotation is explained.

Try it#

BASH
curl -G https://api.hyperjobs.io/v1/jobs \
  -H "Authorization: Bearer $HYPERJOBS_KEY" \
  --data-urlencode "source=applicantpro" \
  --data-urlencode "time_frame=7d" \
  --data-urlencode "limit=2"
Illustrative — the shape, not a live response
{
  "data": [
    {
      "id": "applicantpro:cityofprescott_1029",
      "title": "Lifeguard",
      "company": {
        "slug": "cityofprescott",
        "name": "City of Prescott",
        "domain": "prescott-az.gov",
        "website": "https://prescott-az.gov",
        "logo": "https://media.licdn.com/dms/image/…",
        "industry": "Government Administration",
        "employee_count": 480
      },
      "location": {
        "remote": false,
        "countries": ["United States"],
        "cities": ["Prescott"],
        "regions": ["AZ"],
        "raw": ["Prescott, AZ"]
      },
      "employment_type": ["PART_TIME"],
      "work_arrangement": "On-site",
      "seniority": null,
      "salary": {
        "min": 16.5,
        "max": 19.25,
        "currency": null,
        "unit": "HOUR",
        "annual_min": 34320,
        "annual_max": 40040,
        "summary": "DOE, plus certification reimbursement"
      },
      "skills": ["lifeguarding", "cpr"],
      "taxonomies": ["Recreation"],
      "apply_url": "https://cityofprescott.applicantpro.com/jobs/1029",
      "url": "https://cityofprescott.applicantpro.com/jobs/1029",
      "source": "applicantpro",
      "posted_at": "2026-06-19T00:00:00.000Z",
      "expires_at": null,
      "updated_at": "2026-07-14T07:45:11.000Z"
    }
  ],
  "total": 2,
  "limit": 2,
  "offset": 0
}

How ApplicantPro boards actually work#

Three steps, and the first one is not optional:

BASH
# 1. Bootstrap — the board HTML is the only place `domainId` exists
curl -L "https://<tenant>.applicantpro.com/jobs/"        # → componentData: { domainId : 4689 }
 
# 2. List — every live posting, no descriptions
curl -L "https://<tenant>.applicantpro.com/core/jobs/<domainId>?getParams=%7B%7D"
 
# 3. Detail — the body, plus a benefits block
curl -L "https://<tenant>.applicantpro.com/core/jobs/<domainId>/<jobId>/job-details?getParams=%7B%7D"

The list is genuinely rich: salary as minSalary/maxSalary with payTypeFrame carrying the unit, employmentType, workplaceType, structured city/state/ISO3 location, and startDateRef/endDateRef dates. What it does not carry is the description, so the body is one detail call per new posting.

We read the JSON detail rather than the job page's JSON-LD deliberately: the description is identical on both (8,759 characters on each, measured) and the JSON additionally carries a benefits block the JSON-LD has no room for.

getParams is required, and omitting it returns 200#

A PHP error, served with a success status

Leave getParams off and the API answers HTTP 200 with a PHP type error in the body:

Argument 1 passed to Awesome\Utils\… must be of the type array, null given

A status check sails straight past this. So does anything that trusts a 200 and moves on. You get a successful request, an empty job list, and a board that looks like it simply isn't hiring.

getParams={} — url-encoded, %7B%7D — satisfies it. The value doesn't have to say anything; the parameter just has to exist.

domainId cannot be derived#

It's a per-tenant integer that lives in exactly one place: the board HTML, as componentData: { domainId : 4689 }. There's no pattern to it and no endpoint that maps a slug to it. That's why the bootstrap fetch above is a step rather than an optimisation you can skip — every board costs one extra request before you can ask it anything.

A useful side effect: churned tenants redirect to /notset.php and yield no domainId at all, which is a clean signal that the board is gone rather than broken.

What bites you#

orgTitle is not the employer. It reads like one on a few tenants (sportadvisory → "Shafter Aquatic Center"), which is exactly what makes it dangerous. On most it's the internal org unit: "Airport", "Data", "Library -Dept #4100". Map it to the company name and you write "Data" into your dataset as a company that employs people. It's an org unit in every case, so it belongs in team, and the board's own name stays the employer.

untilFilled makes endDateRef a lie. When a posting is open until filled, endDateRef still carries a date — a rolling placeholder, not a deadline. Expiring on it drops live jobs. When untilFilled is set, there is no expiry.

The published URL form is the one your regex won't match. The canonical link employers actually publish is the path form, www.applicantpro.com/openings/<tenant>/jobs, which 302s to the subdomain. A subdomain regex reads that URL's host as www and rejects it. This is not academic: the Wayback index holds ApplicantPro tenants almost exclusively in the path shape — 1,821 in a 20,000-URL sample, against zero in subdomain form. Check the path first or discovery finds nothing.

Dates arrive as "Jun 19, 2026". Parse that with a naive local-time parser and the date lands a day early for anyone west of UTC.

Or use ours#

A bootstrap fetch, a list call with a parameter whose absence returns 200, a detail call per posting, and a discovery path that only works if you match the URL form nobody's regex expects — across the 423 boards in our queue.

You get the domainId bootstrap handled per tenant, getParams always present, descriptions and the benefits block the JSON-LD omits, salary with the unit read off payTypeFrame, orgTitle filed as a team rather than libelling "Data" as an employer, untilFilled postings left unexpired, dates parsed at UTC so they don't drift a day, ids scoped per tenant, dedup against the same job on LinkedIn or elsewhere, and the company join.

The same adapter also backs iSolved Hire, which is this exact product white-labelled onto another domain.

Query it with ?source=applicantpro. Quickstart, then pricing.

Every source above normalises onto the same schema, with the hiring company already joined onto each posting.

Get an API key