govposts Circular 39 of 2026 · 394 new this week · 1159 open Circular 39 · 1159 open

Developers

API reference

JSON over HTTPS. Base URL https://govposts.co.za/api/v1. Machine-readable: openapi.json.

Authentication

Send your secret API key (gpk_…) in a header: Authorization: Bearer gpk_…. A key in the URL is refused, because URLs end up in logs. Use the key only from your server. The widget uses a different, public key (gpw_…) and needs none of this. Get a free key.

curl -H "Authorization: Bearer gpk_YOUR_KEY" \
  "https://govposts.co.za/api/v1/jobs?province=limpopo&category=health-medical&limit=10"
// On your server, never in a browser: the API key is secret.
const res = await fetch('https://govposts.co.za/api/v1/jobs?province=gauteng', {
  headers: { authorization: `Bearer ${process.env.GOVPOSTS_API_KEY}` },
});
const { data, next_cursor, attribution } = await res.json();
// Show attribution.text, linked to attribution.url, with the jobs.

Limits and caching

  • 1 000 requests a day (resets at midnight SAST) and 60 a minute, per key. Over either: 429 with a Retry-After header in seconds.
  • A key that sends ten times its daily limit in a day is switched off automatically, and we email you.
  • Responses are cached for 15 minutes and carry an ETag; send If-None-Match to get a 304.
  • Only open posts are returned. A post leaves the API as soon as it closes, is withdrawn, or turns out to be internal-only.

Endpoints

GET /jobs

List open jobs, newest first.

ParameterInDescription
provincequeryProvince slug. One of: eastern-cape, free-state, gauteng, kwazulu-natal, limpopo, mpumalanga, northern-cape, north-west, western-cape, national.
categoryqueryCategory slug (see /meta/categories). One of: health-medical, education-training, engineering-technical, it-data, finance-audit-procurement, admin-clerical, human-resources, legal-justice, policing-defence-correctional, social-development-community, agriculture-environment, transport-logistics, public-works-infrastructure, communications-marketing, management-executive, artisans-trades, general-support.
departmentqueryDepartment slug (see /meta/departments).
qqueryWords in the title, centre or reference.
salary_level_minqueryLowest salary level, 1–16.
salary_level_maxqueryHighest salary level, 1–16.
closing_afterqueryClosing on or after this date.
closing_beforequeryClosing on or before this date.
circularqueryCircular as YEAR-NUMBER, for example 2026-41.
limitqueryJobs per page, 1–50 (default 20).
cursorqueryThe next_cursor from the previous page.

GET /jobs/{id}

One open job.

ParameterInDescription
idpath

GET /circulars/latest

The newest published circular.

GET /circulars/{year}/{number}

One published circular.

ParameterInDescription
yearpath
numberpath

GET /meta/provinces

Provinces with open-post counts.

GET /meta/categories

Categories with open-post counts.

GET /meta/departments

Departments with open posts.

GET /meta/salary-levels

Per salary level: the lowest and highest annual figure published adverts printed (level, lowest_annual, highest_annual, posts).

Every response

Each response includes attribution: { "text": "Data: govposts.co.za, from DPSA Public Service Vacancy Circulars", "url": "https://govposts.co.za/" }. The terms ask you to show it with the data. Errors look like { "error": { "code": "…", "message": "…" } }.

Error codes

StatusCodeMeaning
400key_in_queryThe key was sent in the URL. Send it only in the Authorization header.
400bad_parameterA filter or paging parameter is not valid.
401missing_keyNo Authorization: Bearer header.
401invalid_keyThe key is unknown, revoked, or its account is suspended.
404not_foundNo open job, or no published circular, with that id.
429rate_limitedOver the daily or per-minute limit. Wait for the Retry-After seconds.

RSS feeds

No key needed. The latest 50 open posts, newest first; each item links to the job's page.