Hire for Craft CMS

JSON endpoint

GET /hire/jobs.json?department=engineering&jobType=full-time&q=php&page=2

Anonymous and CORS-open (Access-Control-Allow-Origin: *), so a bespoke front end, a careers widget on another domain or a static site can read it. It is the same Listing call the built-in page and craft.hire.results() make — same filters, same semantics — so they can never disagree.

Parameters

Parameter
<groupHandle>A term slug, or several (?department[]=engineering&department[]=design). OR within a group, AND across
qKeywords
pagePage number. Past the end returns an empty list, not a 404
limitJobs per page, capped so one request can't build every job on the site
orderBypostDate, expiryDate, applications, title, dateCreated, dateUpdated or a custom field handle, then asc or desc. Anything else falls back to the default sort
workplaceTypeonsite, hybrid or remote

A term that does not exist is ignored rather than erroring, so a stale bookmark shows an unfiltered page.

Response

{
  "total": 14,
  "page": 2,
  "perPage": 10,
  "totalPages": 2,
  "filters": { "department": ["engineering"] },
  "jobs": [
    {
      "id": 104,
      "code": "JOB-104",
      "title": "Senior PHP Developer",
      "url": "https://example.com/careers/senior-php-developer",
      "location": "Bristol",
      "workplaceType": "hybrid",
      "salary": "£55,000–£65,000 a year",
      "openings": 2,
      "postDate": "2026-09-01T09:00:00+00:00",
      "expiryDate": "2026-10-31T23:59:00+00:00",
      "acceptingApplications": true,
      "specs": [
        { "group": "department", "name": "Engineering", "slug": "engineering" }
      ]
    }
  ]
}

filters is always an object, even when nothing is filtered, so filters.department never throws on the first request.