Guide

Errors & rate limits

The tables below are generated from the handlers and the Nginx configuration, so they list what the running system does.

Error format#

{
  "success": false,
  "error": "INSUFFICIENT_SCOPE",
  "message": "This endpoint requires the 'webhooks:manage' OAuth scope."
}

Rate limits#

Limits are applied per client IP address in two layers. Many users behind one office, school or mobile-carrier network share a single address and a single budget; plan retries accordingly.

1. Edge (Nginx), before the request reaches the application

Applies toRateBurst
/api/v1/mobile/(activation/(start|complete)|auth/refresh) 10 requests / minute 10
/api/v1/public/(leads|newsletter/subscribe) 30 requests / minute 10
/api/v1/verify/ 120 requests / minute 60

Burst is the number of extra requests accepted instantly before the steady rate applies. Everything not listed is unaffected at the edge.

2. Application

EndpointLimit
POST /api/v1/mobile/activation/complete 10 per 1 minute
POST /api/v1/mobile/activation/start 10 per 1 minute
POST /api/v1/public/leads 5 per 10 minutes
POST /api/v1/public/newsletter/subscribe 3 per 10 minutes
GET /api/v1/public/platform 60 per 1 minute
GET /api/v1/public/status 60 per 1 minute
GET /api/v1/verify/:hash 60 per 1 minute

Reading a 429

Do not assume a JSON body

A 429 from the application is JSON (RATE_LIMITED) and carries Retry-After, plus X-RateLimit-Limit, -Remaining and -Reset on the endpoints that report them. A 429 from the edge is Nginx's default HTML page with no Retry-After. Treat the status code as authoritative, and back off with jitter (start at a few seconds, double up to a minute) when no Retry-After is present.

API error codes#

StatusCodeMeaning
401AUTHENTICATION_REQUIREDA valid RemoteRegister session or bearer token is required.
403AUDITOR_READ_ONLYExternal auditor (R8) access is strictly read-only.
403CROSS_TENANT_ACCESS_DENIEDThis session is not authorized for the requested tenant.
403CSRF_PROTECTIONCookie-authenticated mutations must be sent as application/json.
403FORBIDDEN_ROLEThis action requires one of: ….
403INSUFFICIENT_SCOPEThis endpoint requires the '…' OAuth scope.
403LEARNER_SELF_SCOPE_VIOLATIONLearner accounts may only access their own self-service records and notifications.
403MACHINE_SCOPE_VIOLATIONMachine tokens may only access integration, webhook and edge-sync endpoints.
429RATE_LIMITEDToo many requests. Please try again shortly.

Device API error codes#

StatusCodeMeaning
400CONSENT_REQUIREDconsentRef is required and must reference the recorded consent.
400PAYLOAD_TOO_LARGEA batch must contain 1-500 events.
400SIGNATURE_INVALIDThe exact request body was not preserved; the signature cannot be verified.
400TEMPLATE_FORMAT_UNSUPPORTEDposition is not a supported finger.
400TEMPLATE_TOO_LARGETemplate must be valid base64 between 64 and 4096 bytes.
400VALIDATION_ERRORclientRequestId is required for this operation.
401AUTHENTICATION_REQUIREDThe refresh token is invalid or expired.
401NONCE_REPLAYEDX-RR-Nonce has already been used.
401SIGNATURE_INVALIDThe activation challenge signature did not verify.
401TIMESTAMP_SKEWX-RR-Timestamp must be within 120 seconds of server time (ISO 8601 or epoch seconds/milliseconds).
403ASSIGNMENT_REQUIREDThe requested cohort is outside this device assignment.
403CONSENT_REQUIREDThe learner has not given POPIA consent for biometric processing.
403LEARNER_NOT_ASSIGNEDThe learner is not assigned to this device.
403PLAY_INTEGRITY_FAILEDDevice attestation did not pass.
404DEVICE_NOT_FOUNDActivation was not found.
404MATCH_NOT_FOUNDNo enrolled templates are available in this scope.
404PAIRING_CODE_INVALIDThe pairing code is not recognised.
409ACTIVATION_ALREADY_COMPLETEDThis activation is no longer pending.
409BATCH_HASH_MISMATCHpreviousBatchHash does not match the last accepted batch.
409BATCH_OUT_OF_ORDERdeviceSeq must be greater than the last accepted sequence (…).
409DUPLICATE_BIOMETRICThis template is already enrolled to another learner.
409PAIRING_CODE_USEDThe pairing code has already been used.
410ACTIVATION_CHALLENGE_EXPIREDThe activation challenge has expired. Start again with a new pairing code.
410PAIRING_CODE_EXPIREDThe pairing code has expired.
410PAIRING_CODE_REVOKEDThe pairing code has been revoked.
422QUALITY_TOO_LOWNFIQ2 score must be at least ….
501EVIDENCE_UPLOAD_NOT_CONFIGUREDEvidence upload is not configured on this deployment. Contact the tenant administrator.
503PAIRING_NOT_CONFIGUREDPairing is not configured on this deployment.

Some codes appear with more than one status or message depending on which check failed (for example VALIDATION_ERROR); the message identifies the field.