API reference

Version 1.0. JSON over HTTPS. Reading live listings needs no key; managing your own listings needs one from your dashboard.

Authentication

curl https://jobs.co.nz/api/v1/me \
  -H "Authorization: Bearer jcnz_your_key_here"

Keys carry read or read write. A key inherits the account's standing: if posting is blocked in the dashboard — unverified, over quota, revoked — the API returns 403 with the same reason.

Endpoints

Method & pathKeyWhat it does
GET /api/v1/jobsSearch live listings
GET /api/v1/jobs/{id}One live listing
GET /api/v1/metaValid values for every enumerated field
GET /api/v1/mereadAccount, quota and credits
GET /api/v1/employer/jobsreadYour listings, any status
POST /api/v1/employer/jobswriteCreate a listing
PATCH /api/v1/employer/jobs/{id}writeUpdate a listing
POST /api/v1/employer/jobs/{id}/closewriteTake a listing down
GET /api/v1/employer/jobs/{id}/applicationsreadApplications to your listing

Creating a listing

curl -X POST https://jobs.co.nz/api/v1/employer/jobs \
  -H "Authorization: Bearer jcnz_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Registered Nurse — Emergency Department",
    "description": "At least eighty characters describing the role…",
    "category": "Healthcare & Medical",
    "region": "Wellington",
    "salary_min": 78000,
    "salary_max": 92000
  }'

A new employer's first listing returns 201 with status: "pending_review" — every employer's first listing is read by a moderator, through the API as much as through the site.

Job fields

Generated from the schema the server validates against, so this table can't describe a rule the API doesn't enforce.

FieldTypeRules
title required str min 3 chars. max 140 chars. Role title as candidates will see it.
description required str min 80 chars. max 20000 chars. Plain text. The first two lines show in search results.
category required str One of: Accounting & Finance, Administration, Agriculture & Fishing, Construction & Trades, Education & Training, Engineering … (18 values, see /api/v1/meta).
region required str One of: Northland, Auckland, Waikato, Bay of Plenty, Gisborne, Hawke's Bay … (16 values, see /api/v1/meta).
town str max 80 chars. Defaults to .
employment_type str One of: full-time, part-time, fixed-term, contract, casual, internship. Defaults to full-time.
work_mode str One of: onsite, hybrid, remote. Defaults to onsite.
salary_min int min 0. max 10000000.
salary_max int min 0. max 10000000.
salary_period str One of: year, hour. Defaults to year.
salary_visible bool Defaults to True. Show the salary on the listing. Roles that do rank higher.
apply_mode str One of: internal, external, email. Defaults to internal.
apply_target str max 300 chars. Defaults to . Required for apply_mode external (URL) or email (address).

Unknown fields are rejected rather than ignored, and no value is coerced: "salary_min": "80000" is a string and will be refused, because silently accepting it is how a serialiser bug reaches the database.

Errors

{
  "error": {
    "code": "validation_failed",
    "message": "Some fields are invalid.",
    "errors": [
      {"field": "region", "message": "must be one of: Northland, Auckland, …"},
      {"field": "salary_max", "message": "must be at least salary_min"}
    ]
  }
}
StatusCodeMeaning
400invalid_query / invalid_jsonMalformed request
401unauthorizedNo key, or an unknown/revoked one
403forbidden / not_permittedWrong scope, or the account can't post
404not_foundNo such listing, or not yours
409identity_lockedTitle/category/region locked after publication
413payload_too_largeBody over 64 KB (measured on the body, not the Content-Length header)
415unsupported_media_typeMissing Content-Type: application/json
422validation_failedFields failed validation — see errors
429rate_limitedOver the limit — see Retry-After

Every error names the field where there is one, and all failures are reported together rather than one per round trip.

Rate limits

CallerLimitMetered by
No key120 / 60sIP
Key, reading600 / 60skey
Key, writing60 / 60saccount

Writes get the smallest budget because they change what everyone else reads, and they are metered per account — minting extra keys doesn't buy more write throughput. Reads are metered per key, so two integrations on one account don't starve each other. Every response carries X-RateLimit-Limit, X-RateLimit-Window and X-RateLimit-Scope.