Start here

Authentication

Four kinds of caller reach the API, each with its own mechanism and its own boundary. Pick the row that matches what you are building.

Overview#

CallerMechanismReachesAvailability
Anyone None Public endpoints Live
Android device Bearer device token and an Ed25519 signature on every request Mobile device API Implemented; activation blocked in production (see availability)
Server integration OAuth 2.0 client credentials → bearer token Integrations, edge sync, webhooks Preview (see below)
Administrator (human) Browser session cookie, CSRF-protected, optional TOTP The web application and device administration Live — not an integration mechanism

Machine integrations (OAuth 2.0 client credentials)#

Preview — availability

The token endpoint, scope checks and tenant binding below are implemented and covered by automated tests. Production currently authenticates against a single operator-seeded client. Clients created by tenant administrators in the console are held in a separate registry that the token endpoint does not consult yet, so there is no self-service way to obtain client credentials. Do not plan an integration around this surface until provisioning is connected; this page will say so when it is.

Get a token#

POST https://rr-api.medq.co.zw/api/v1/auth/token implements the client-credentials grant of RFC 6749 §4.4. It is public (it is how you authenticate) and only grant_type=client_credentials is supported. Send the client credentials as HTTP Basic or in a form body:

curl -s https://rr-api.medq.co.zw/api/v1/auth/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=client_credentials
curl -s https://rr-api.medq.co.zw/api/v1/auth/token \
  -d grant_type=client_credentials \
  -d client_id="$CLIENT_ID" \
  --data-urlencode client_secret="$CLIENT_SECRET"

A successful response:

{
  "access_token": "<jwt>",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "attendance:write lms:sync webhooks:manage"
}

Use it as a bearer token:

curl -s https://rr-api.medq.co.zw/api/v1/webhooks \
  -H "Authorization: Bearer $ACCESS_TOKEN"
Not this one

POST /api/v1/oauth/token also exists but is restricted to a signed-in super administrator (it backs the in-app API console) and returns 401 AUTHENTICATION_REQUIRED to integrations. Material published before 29 October 2026 (an older OpenAPI document or Postman collection, the earlier marketing docs page) may still point there; use /api/v1/auth/token.

What a token can call#

A machine token is confined to the routes below, plus /auth/session and /auth/logout. Anything else returns 403 MACHINE_SCOPE_VIOLATION; a route outside the token's scopes returns 403 INSUFFICIENT_SCOPE naming the missing scope. This table is generated from the access policy in the running code.

MethodPathScope required
GET /api/v1/integrations/lms/connectors integrations:read
PUT /api/v1/integrations/lms/connectors/:id lms:sync
GET /api/v1/integrations/lms/modules/:provider integrations:read
POST /api/v1/integrations/lms/push-attendance lms:sync
POST /api/v1/integrations/lms/sync-roster lms:sync
POST /api/v1/integrations/lms/test-connection lms:sync
GET /api/v1/integrations/status integrations:read
POST /api/v1/sync/batches attendance:write
GET /api/v1/webhooks webhooks:manage
POST /api/v1/webhooks webhooks:manage
DELETE /api/v1/webhooks/:id webhooks:manage
POST /api/v1/webhooks/:id/test webhooks:manage
GET /api/v1/webhooks/dead-letters webhooks:manage
GET /api/v1/webhooks/deliveries webhooks:manage
POST /api/v1/webhooks/deliveries/:id/retry webhooks:manage
POST /api/v1/webhooks/simulate-event webhooks:manage

Request and response schemas for these routes are not yet part of the OpenAPI document.

Scopes#

The scope registry lists every scope a client can be offered, and creating a client with any name outside it is rejected (400). Only some scopes are enforced by a machine-accessible route today; the rest are reserved names and grant nothing yet.

ScopeMeaningStatus
attendance:read Query verified daily clock-in/out records, sealed session manifests, and attendance percentages. Reserved — no machine route requires it yet
attendance:write Submit biometric clock-in batches and offline attendance logs from approved edge hardware. Enforced on 1 route
audit:read Inspect cryptographic SHA-256 ledger entries and verify ECTA Section 15 evidence seals. Reserved — no machine route requires it yet
integrations:read Read integration health and LMS connector configuration and qualification modules. Enforced on 3 routes
integrations:write Reserved: modify integration settings. No machine-accessible route requires it yet. Reserved — no machine route requires it yet
learners:read Query enrolled learners, demographics, South African ID status, and POPIA consent flags. Reserved — no machine route requires it yet
learners:write Register candidates, upload preflight CSVs, and update banking/demographic attributes. Reserved — no machine route requires it yet
lms:sync Ingest class rosters and push verified attendance hours into Yiba, Wemasoft, or Moodle. Enforced on 4 routes
payouts:initiate Trigger instant disbursements through Netcash PayShap, Paystack, or Stitch Open Banking. Reserved — no machine route requires it yet
stipends:read Access SD5/NMW stipend calculations, deduction breakdowns, and locked disbursement runs. Reserved — no machine route requires it yet
webhooks:manage Register, edit, and test HTTPS event callback subscriptions and examine delivery logs. Enforced on 8 routes

Token errors#

StatuserrorCause
400unsupported_grant_typegrant_type is present but is not client_credentials.
401invalid_clientUnknown client, wrong secret, or missing credentials. The response never says which.

Administrator sessions#

People sign in to the web application and receive an httpOnly session cookie. Mutating requests made with that cookie must be sent as application/json (or with X-Requested-With: XMLHttpRequest); otherwise the API answers 403 CSRF_PROTECTION. Sessions are for the console and for the administrator endpoints in the mobile reference (pairing codes, device revocation); do not use them for unattended integrations.