{"openapi":"3.1.0","info":{"title":"RemoteRegister Cloud Evidence & Learnership API","version":"5.0.0","description":"South African enterprise REST API for real-time biometric attendance capture, statutory stipend disbursement, LMS synchronization, and automated SETA/QCTO compliance verification. Compliant with POPIA Act 4 of 2013, ECTA Act 25 of 2002 Section 15, Sectoral Determination 5, and NMW 2026 GN R7083.","contact":{"name":"RemoteRegister Developer Platform","url":"https://rr-docs.medq.co.zw","email":"api-support@medq.co.zw"},"license":{"name":"Proprietary Commercial Enterprise SDK","url":"https://remoteregister.medq.co.zw/legal/terms"}},"servers":[{"url":"/api/v1","description":"RemoteRegister multi-tenant API. Governed by South African law; primary compute is hosted in the EU (Helsinki) under POPIA s72 safeguards."}],"paths":{"/public/platform":{"get":{"tags":["Public Surfaces"],"operationId":"getPublicPlatformFacts","summary":"Trust-centre facts","description":"Returns jurisdiction, governing law, hosting location, cross-border transfer basis, compliance frameworks and public surface URLs. No personal information.","responses":{"200":{"description":"Platform facts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/public/leads":{"post":{"tags":["Public Surfaces"],"operationId":"submitPublicLead","summary":"Submit a marketing enquiry","description":"Creates a marketing lead with POPIA s11/s18 consent metadata. Honeypot field `companyWebsite` is used for bot detection. Rate limited to 5 requests per 10 minutes per IP.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicLeadRequest"}}}},"responses":{"201":{"description":"Lead accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/public/newsletter/subscribe":{"post":{"tags":["Public Surfaces"],"operationId":"subscribeNewsletter","summary":"Start newsletter double opt-in","description":"Creates a PENDING subscriber and dispatches a confirmation email. POPIA s69 requires opt-in consent. Rate limited to 3 requests per 10 minutes per IP.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NewsletterSubscribeRequest"}}}},"responses":{"202":{"description":"Subscription pending confirmation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/public/newsletter/confirm":{"get":{"tags":["Public Surfaces"],"operationId":"confirmNewsletter","summary":"Confirm a newsletter subscription","description":"Consumes a single-use, hashed confirmation token.","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Subscription confirmed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/public/newsletter/unsubscribe":{"get":{"tags":["Public Surfaces"],"operationId":"unsubscribeNewsletter","summary":"Unsubscribe from the newsletter","description":"Consumes a single-use, hashed unsubscribe token.","parameters":[{"name":"token","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Subscription removed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/public/status":{"get":{"tags":["Public Surfaces"],"operationId":"getPublicStatus","summary":"Operational status feed","description":"Aggregated persistence, integration and platform status for the status page.","responses":{"200":{"description":"Status feed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/health":{"get":{"tags":["Mobile Device API"],"operationId":"mobileHealth","summary":"Device API liveness","description":"Unauthenticated liveness endpoint. Reports the API version and Play Integrity mode.","responses":{"200":{"description":"Service healthy","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/activation/start":{"post":{"tags":["Mobile Device API"],"operationId":"startMobileActivation","summary":"Redeem a pairing code and register a device key","description":"Consumes a single-use 8-character Crockford pairing code, validates the Ed25519 public key and Play Integrity verdict, creates a PENDING device and returns a 5-minute signature challenge. Rate limited to 10 requests per minute per IP. In live Play Integrity mode the token must be issued within the last 10 minutes and must carry `requestHash` set to the lowercase hex SHA-256 of the exact `publicKeyPem` string sent in this request; otherwise activation is refused.","security":[{"DeviceBearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XRRDeviceId"},{"$ref":"#/components/parameters/XRRTimestamp"},{"$ref":"#/components/parameters/XRRNonce"},{"$ref":"#/components/parameters/XRRContentSha256"},{"$ref":"#/components/parameters/XRRDeviceSignature"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivationStartRequest"}}}},"responses":{"201":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/activation/complete":{"post":{"tags":["Mobile Device API"],"operationId":"completeMobileActivation","summary":"Prove key possession and receive device tokens","description":"Verifies the Ed25519 signature over `deviceId.challenge.tenantId.userId` and issues an opaque access token, refresh token and 14-day lease.","security":[{"DeviceBearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XRRDeviceId"},{"$ref":"#/components/parameters/XRRTimestamp"},{"$ref":"#/components/parameters/XRRNonce"},{"$ref":"#/components/parameters/XRRContentSha256"},{"$ref":"#/components/parameters/XRRDeviceSignature"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivationCompleteRequest"}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/auth/refresh":{"post":{"tags":["Mobile Device API"],"operationId":"refreshMobileToken","summary":"Rotate the device refresh token","description":"Signature-only authentication. The previous refresh token is invalidated and the device lease is renewed. Allowed after the access token and lease have expired.\n\nIdempotent: send a stable clientRequestId. A replay returns HTTP 200 with data.idempotentReplay = true.","security":[{"DeviceBearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XRRDeviceId"},{"$ref":"#/components/parameters/XRRTimestamp"},{"$ref":"#/components/parameters/XRRNonce"},{"$ref":"#/components/parameters/XRRContentSha256"},{"$ref":"#/components/parameters/XRRDeviceSignature"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["refreshToken"],"properties":{"refreshToken":{"type":"string"}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/auth/revoke":{"post":{"tags":["Mobile Device API"],"operationId":"revokeMobileDevice","summary":"Self-revoke the device","description":"Marks the device REVOKED and invalidates all access tokens.\n\nIdempotent: send a stable clientRequestId. A replay returns HTTP 200 with data.idempotentReplay = true.","security":[{"DeviceBearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XRRDeviceId"},{"$ref":"#/components/parameters/XRRTimestamp"},{"$ref":"#/components/parameters/XRRNonce"},{"$ref":"#/components/parameters/XRRContentSha256"},{"$ref":"#/components/parameters/XRRDeviceSignature"}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/config":{"get":{"tags":["Mobile Device API"],"operationId":"getMobileConfig","summary":"Device configuration","description":"Tenant thresholds, scanner allowlist, offline policy and retention settings.","security":[{"DeviceBearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XRRDeviceId"},{"$ref":"#/components/parameters/XRRTimestamp"},{"$ref":"#/components/parameters/XRRNonce"},{"$ref":"#/components/parameters/XRRContentSha256"},{"$ref":"#/components/parameters/XRRDeviceSignature"}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/roster":{"get":{"tags":["Mobile Device API"],"operationId":"getMobileRoster","summary":"Assigned learner roster","description":"Returns only learners in the cohorts assigned to the device. The response carries an ETag for conditional requests.","security":[{"DeviceBearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XRRDeviceId"},{"$ref":"#/components/parameters/XRRTimestamp"},{"$ref":"#/components/parameters/XRRNonce"},{"$ref":"#/components/parameters/XRRContentSha256"},{"$ref":"#/components/parameters/XRRDeviceSignature"}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/sessions":{"get":{"tags":["Mobile Device API"],"operationId":"getMobileSessions","summary":"Scheduled sessions and venues","description":"Sessions for the next 14 days derived from assigned cohort schedules and venues.","security":[{"DeviceBearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XRRDeviceId"},{"$ref":"#/components/parameters/XRRTimestamp"},{"$ref":"#/components/parameters/XRRNonce"},{"$ref":"#/components/parameters/XRRContentSha256"},{"$ref":"#/components/parameters/XRRDeviceSignature"}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/enrolments":{"post":{"tags":["Mobile Device API"],"operationId":"enrolMobileBiometrics","summary":"Enrol ISO/IEC 19794-2 fingerprint templates","description":"Accepts 1–4 minutiae templates. The server validates format, size, NFIQ2 and minutiae count, rejects duplicate templates enrolled to another learner, and stores only a SHA-256 template hash. No image or raw template is retained. Requires recorded POPIA consent and the learner M1 biometric preference.\n\nIdempotent: send a stable clientRequestId. A replay returns HTTP 200 with data.idempotentReplay = true.","security":[{"DeviceBearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XRRDeviceId"},{"$ref":"#/components/parameters/XRRTimestamp"},{"$ref":"#/components/parameters/XRRNonce"},{"$ref":"#/components/parameters/XRRContentSha256"},{"$ref":"#/components/parameters/XRRDeviceSignature"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnrolmentRequest"}}}},"responses":{"201":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/match":{"post":{"tags":["Mobile Device API"],"operationId":"matchMobileBiometrics","summary":"Bounded 1:1 or scoped 1:N fingerprint match","description":"ONE_TO_MANY searches only the device’s assigned cohorts and venues. A no-match response contains no candidate list. Probe templates are never stored.","security":[{"DeviceBearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XRRDeviceId"},{"$ref":"#/components/parameters/XRRTimestamp"},{"$ref":"#/components/parameters/XRRNonce"},{"$ref":"#/components/parameters/XRRContentSha256"},{"$ref":"#/components/parameters/XRRDeviceSignature"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MatchRequest"}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/attendance/events":{"post":{"tags":["Mobile Device API"],"operationId":"submitMobileAttendanceEvent","summary":"Submit one online attendance event","description":"Validates the geofence, applies the existing attendance ledger rules and records the event. Retries with the same clientRequestId return the original result with idempotentReplay = true.\n\nIdempotent: send a stable clientRequestId. A replay returns HTTP 200 with data.idempotentReplay = true.","security":[{"DeviceBearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XRRDeviceId"},{"$ref":"#/components/parameters/XRRTimestamp"},{"$ref":"#/components/parameters/XRRNonce"},{"$ref":"#/components/parameters/XRRContentSha256"},{"$ref":"#/components/parameters/XRRDeviceSignature"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttendanceEventRequest"}}}},"responses":{"201":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/sync/batches":{"post":{"tags":["Mobile Device API"],"operationId":"syncOfflineBatch","summary":"Upload an offline attendance batch","description":"Verifies the device hash chain and Ed25519 batch signature, enforces a strict deviceSeq, and accepts or rejects each event independently. Gaps and chain mismatches fail the batch.\n\nIdempotent: send a stable clientRequestId. A replay returns HTTP 200 with data.idempotentReplay = true.","security":[{"DeviceBearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XRRDeviceId"},{"$ref":"#/components/parameters/XRRTimestamp"},{"$ref":"#/components/parameters/XRRNonce"},{"$ref":"#/components/parameters/XRRContentSha256"},{"$ref":"#/components/parameters/XRRDeviceSignature"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SyncBatchRequest"}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/sync/state":{"get":{"tags":["Mobile Device API"],"operationId":"getMobileSyncState","summary":"Upload cursor state","description":"Returns the last accepted device sequence, batch hash and lease state.","security":[{"DeviceBearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XRRDeviceId"},{"$ref":"#/components/parameters/XRRTimestamp"},{"$ref":"#/components/parameters/XRRNonce"},{"$ref":"#/components/parameters/XRRContentSha256"},{"$ref":"#/components/parameters/XRRDeviceSignature"}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/heartbeat":{"post":{"tags":["Mobile Device API"],"operationId":"recordMobileHeartbeat","summary":"Record device health","description":"Battery, storage, scanner and app version. Allowed while a wipe is pending so the device can acknowledge it.\n\nIdempotent: send a stable clientRequestId. A replay returns HTTP 200 with data.idempotentReplay = true.","security":[{"DeviceBearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XRRDeviceId"},{"$ref":"#/components/parameters/XRRTimestamp"},{"$ref":"#/components/parameters/XRRNonce"},{"$ref":"#/components/parameters/XRRContentSha256"},{"$ref":"#/components/parameters/XRRDeviceSignature"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/HeartbeatRequest"}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/telemetry":{"post":{"tags":["Mobile Device API"],"operationId":"submitMobileTelemetry","summary":"Submit non-PII diagnostics","description":"Operational diagnostics with no learner identifiers or biometric data.\n\nIdempotent: send a stable clientRequestId. A replay returns HTTP 200 with data.idempotentReplay = true.","security":[{"DeviceBearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XRRDeviceId"},{"$ref":"#/components/parameters/XRRTimestamp"},{"$ref":"#/components/parameters/XRRNonce"},{"$ref":"#/components/parameters/XRRContentSha256"},{"$ref":"#/components/parameters/XRRDeviceSignature"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":true}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/evidence/upload-url":{"post":{"tags":["Mobile Device API"],"operationId":"createEvidenceUploadUrl","summary":"Create a signed evidence upload URL","description":"Returns an HMAC-signed, short-lived upload URL for non-biometric evidence. Returns 501 when no evidence store is configured.\n\nIdempotent: send a stable clientRequestId. A replay returns HTTP 200 with data.idempotentReplay = true.","security":[{"DeviceBearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XRRDeviceId"},{"$ref":"#/components/parameters/XRRTimestamp"},{"$ref":"#/components/parameters/XRRNonce"},{"$ref":"#/components/parameters/XRRContentSha256"},{"$ref":"#/components/parameters/XRRDeviceSignature"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["contentType","byteLength"],"properties":{"contentType":{"type":"string","enum":["image/jpeg","image/png","application/pdf"]},"byteLength":{"type":"integer","minimum":1,"maximum":10485760},"learnerId":{"type":"string"},"sessionId":{"type":"string"}}}}}},"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/wipe/ack":{"post":{"tags":["Mobile Device API"],"operationId":"acknowledgeMobileWipe","summary":"Acknowledge a remote wipe","description":"Confirms that local roster, templates and cached evidence have been removed.\n\nIdempotent: send a stable clientRequestId. A replay returns HTTP 200 with data.idempotentReplay = true.","security":[{"DeviceBearerAuth":[]}],"parameters":[{"$ref":"#/components/parameters/XRRDeviceId"},{"$ref":"#/components/parameters/XRRTimestamp"},{"$ref":"#/components/parameters/XRRNonce"},{"$ref":"#/components/parameters/XRRContentSha256"},{"$ref":"#/components/parameters/XRRDeviceSignature"}],"responses":{"200":{"description":"Successful response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/admin/pairing-codes":{"get":{"tags":["Mobile Device Administration"],"operationId":"listPairingCodes","summary":"List pairing codes","description":"Tenant-scoped pairing codes. Only the HMAC hash is stored; the plaintext code is shown once at creation.","security":[{"SessionAuth":[]}],"responses":{"200":{"description":"Pairing codes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}},"post":{"tags":["Mobile Device Administration"],"operationId":"createPairingCode","summary":"Create a pairing code","description":"Creates a single-use 15-minute pairing code bound to a tenant and optionally to cohorts and venues. Roles: R1, R2, R3, R6.","security":[{"SessionAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"label":{"type":"string","maxLength":120},"cohortIds":{"type":"array","items":{"type":"string"}},"venueIds":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"201":{"description":"Pairing code created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/admin/pairing-codes/{id}":{"delete":{"tags":["Mobile Device Administration"],"operationId":"revokePairingCode","summary":"Revoke a pairing code","description":"Revokes an unused pairing code. Roles: R1, R2, R3, R6.","security":[{"SessionAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Pairing code revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/admin/devices":{"get":{"tags":["Mobile Device Administration"],"operationId":"listMobileDevices","summary":"List registered devices","description":"Tenant-scoped device inventory with status, lease and last-seen metadata. Roles: R1, R2, R3, R6.","security":[{"SessionAuth":[]}],"responses":{"200":{"description":"Device inventory","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/admin/devices/{id}/revoke":{"post":{"tags":["Mobile Device Administration"],"operationId":"revokeMobileDeviceAdmin","summary":"Revoke a device","description":"Immediately invalidates tokens and blocks all device calls. Roles: R1, R2.","security":[{"SessionAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Device revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}},"/mobile/admin/devices/{id}/wipe":{"post":{"tags":["Mobile Device Administration"],"operationId":"requestMobileDeviceWipe","summary":"Request a remote wipe","description":"Sets a wipe instruction; the device confirms through /mobile/wipe/ack. Roles: R1, R2.","security":[{"SessionAuth":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Wipe requested","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileEnvelope"}}}},"default":{"description":"Error response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MobileError"}}}}}}}},"components":{"securitySchemes":{"DeviceBearerAuth":{"type":"http","scheme":"bearer","description":"Opaque device access token (rr_dt_...). Every device request must also be signed with the registered Ed25519 key over the canonical string METHOD, path with sorted query, X-RR-Timestamp, X-RR-Nonce, X-RR-Content-SHA256 and X-RR-Device-Id."},"SessionAuth":{"type":"http","scheme":"bearer","description":"Tenant administrator session token issued by /api/v1/auth/login."},"OAuth2ClientCredentials":{"type":"oauth2","description":"OAuth 2.0 Client Credentials Grant for machine-to-machine integrations (LMS, ERP, Payroll).","flows":{"clientCredentials":{"tokenUrl":"/api/v1/oauth/token","scopes":{"attendance:read":"Query attendance clocking records & session certificates","attendance:write":"Submit offline attendance registers from verified edge hardware","learners:read":"Read candidate demographics, SA ID verification & POPIA consent","learners:write":"Register learners & update compliance attributes","stipends:read":"Access SD5 calculations & locked payroll runs","payouts:initiate":"Trigger instant PayShap / EFT disbursements","webhooks:manage":"Configure event delivery subscriptions","lms:sync":"Synchronize qualification modules & attendance with LMS","audit:read":"Inspect tamper-evident cryptographic audit logs"}}}},"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}},"parameters":{"XRRDeviceId":{"name":"X-RR-Device-Id","in":"header","required":true,"schema":{"type":"string","example":"dev_mob_9f2c..."}},"XRRTimestamp":{"name":"X-RR-Timestamp","in":"header","required":true,"description":"ISO 8601 timestamp or epoch seconds/ms. Must be within ±120 seconds of server time.","schema":{"type":"string","example":"2026-10-24T10:15:30.000Z"}},"XRRNonce":{"name":"X-RR-Nonce","in":"header","required":true,"description":"16–128 URL-safe characters, single use within a 5-minute window.","schema":{"type":"string"}},"XRRContentSha256":{"name":"X-RR-Content-SHA256","in":"header","required":true,"description":"Lowercase hex SHA-256 of the raw body. Use the SHA-256 of the empty string for GET/HEAD.","schema":{"type":"string","pattern":"^[a-f0-9]{64}$"}},"XRRDeviceSignature":{"name":"X-RR-Device-Signature","in":"header","required":true,"description":"Base64url Ed25519 signature over the canonical signing string.","schema":{"type":"string"}}},"schemas":{"MobileEnvelope":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","additionalProperties":true},"serverTime":{"type":"string","format":"date-time"}}},"MobileError":{"type":"object","required":["success","error","message"],"properties":{"success":{"type":"boolean","const":false},"error":{"type":"string","enum":["AUTHENTICATION_REQUIRED","DEVICE_NOT_FOUND","DEVICE_REVOKED","DEVICE_RESTRICTED","SIGNATURE_INVALID","TIMESTAMP_SKEW","NONCE_REPLAYED","LEASE_EXPIRED","TENANT_MISMATCH","ASSIGNMENT_REQUIRED","CONSENT_REQUIRED","QUALITY_TOO_LOW","DUPLICATE_BIOMETRIC","TEMPLATE_FORMAT_UNSUPPORTED","TEMPLATE_TOO_LARGE","MATCH_NOT_FOUND","MATCHER_UNAVAILABLE","DUPLICATE_EVENT","BATCH_OUT_OF_ORDER","BATCH_HASH_MISMATCH","PAYLOAD_TOO_LARGE","RATE_LIMITED","IDEMPOTENCY_CONFLICT","PAIRING_CODE_INVALID","PAIRING_CODE_EXPIRED","PAIRING_CODE_REVOKED","PAIRING_CODE_USED","ACTIVATION_ALREADY_COMPLETED","ACTIVATION_CHALLENGE_EXPIRED","PLAY_INTEGRITY_FAILED","PAIRING_NOT_CONFIGURED","VALIDATION_ERROR"]},"message":{"type":"string"},"requestId":{"type":"string"},"details":{"type":"object","additionalProperties":true}}},"ActivationStartRequest":{"type":"object","required":["pairingCode","publicKeyPem","playIntegrityToken"],"properties":{"pairingCode":{"type":"string","example":"7KQ4-9M2X"},"clientRequestId":{"type":"string","format":"uuid"},"publicKeyPem":{"type":"string","description":"Ed25519 public key in SPKI PEM form."},"playIntegrityToken":{"type":"string"},"device":{"type":"object","properties":{"label":{"type":"string","maxLength":120},"manufacturer":{"type":"string"},"model":{"type":"string"},"androidVersion":{"type":"string"},"appVersion":{"type":"string"},"scanner":{"type":"object","properties":{"vendor":{"type":"string"},"model":{"type":"string"},"serialHash":{"type":"string"},"firmware":{"type":"string"},"sdkVersion":{"type":"string"}}}}}}},"ActivationCompleteRequest":{"type":"object","required":["activationId","signature"],"properties":{"activationId":{"type":"string"},"signature":{"type":"string","description":"Base64url Ed25519 signature over deviceId.challenge.tenantId.userId."}}},"FingerTemplate":{"type":"object","required":["position","templateFormat","templateBase64","nfiq2Score","minutiaeCount"],"properties":{"position":{"type":"string","enum":["RIGHT_THUMB","RIGHT_INDEX","LEFT_THUMB","LEFT_INDEX","RIGHT_MIDDLE","LEFT_MIDDLE"]},"templateFormat":{"type":"string","const":"ISO_19794_2_2005"},"templateBase64":{"type":"string","description":"ISO/IEC 19794-2 template, 64–4096 bytes when decoded."},"nfiq2Score":{"type":"integer","minimum":50,"maximum":100},"minutiaeCount":{"type":"integer","minimum":12,"maximum":120},"capturedAt":{"type":"string","format":"date-time"}}},"EnrolmentRequest":{"type":"object","required":["learnerId","consentRef","clientRequestId","fingers"],"properties":{"learnerId":{"type":"string"},"consentRef":{"type":"string","minLength":6},"clientRequestId":{"type":"string","format":"uuid"},"scannerSerialHash":{"type":"string"},"fingers":{"type":"array","minItems":1,"maxItems":4,"items":{"$ref":"#/components/schemas/FingerTemplate"}}}},"MatchRequest":{"type":"object","required":["mode","probes"],"properties":{"mode":{"type":"string","enum":["ONE_TO_ONE","ONE_TO_MANY"]},"candidateLearnerId":{"type":"string","description":"Required for ONE_TO_ONE."},"scope":{"type":"object","properties":{"cohortId":{"type":"string"},"venueId":{"type":"string"}}},"fusion":{"type":"boolean"},"probes":{"type":"array","minItems":1,"maxItems":2,"items":{"$ref":"#/components/schemas/FingerTemplate"}}}},"AttendanceEventRequest":{"type":"object","required":["eventId","clientRequestId","learnerId","method","occurredAt","cohortId","venueId"],"properties":{"eventId":{"type":"string","format":"uuid"},"clientRequestId":{"type":"string","format":"uuid"},"learnerId":{"type":"string"},"method":{"type":"string","enum":["M1_BIOMETRIC_1N","M2_BIOMETRIC_11","M4_PIN_PHOTO","M6_SUPERVISED_OVERRIDE"]},"occurredAt":{"type":"string","format":"date-time"},"cohortId":{"type":"string"},"venueId":{"type":"string"},"sessionId":{"type":"string"},"matchId":{"type":"string"},"scannerSerialHash":{"type":"string"},"geofence":{"type":"object","properties":{"lat":{"type":"number"},"lng":{"type":"number"},"accuracyM":{"type":"number"},"inside":{"type":"boolean"},"distanceM":{"type":"number"}}},"quality":{"type":"object","properties":{"nfiq2Score":{"type":"integer"}}},"flags":{"type":"object","properties":{"mockLocation":{"type":"boolean"},"rooted":{"type":"boolean"},"usbReplug":{"type":"boolean"},"clockDrift":{"type":"boolean"}}}}},"SyncBatchRequest":{"type":"object","required":["batchId","deviceSeq","batchHash","signature","events"],"properties":{"batchId":{"type":"string","format":"uuid"},"deviceSeq":{"type":"integer","minimum":1},"previousBatchHash":{"type":"string"},"batchHash":{"type":"string","pattern":"^[a-f0-9]{64}$"},"signature":{"type":"string","description":"Base64url Ed25519 signature over batchHash."},"capturedFrom":{"type":"string","format":"date-time"},"capturedTo":{"type":"string","format":"date-time"},"events":{"type":"array","minItems":1,"items":{"$ref":"#/components/schemas/AttendanceEventRequest"}}}},"HeartbeatRequest":{"type":"object","properties":{"batteryPct":{"type":"integer","minimum":0,"maximum":100},"storageFreeMb":{"type":"integer","minimum":0},"appVersion":{"type":"string"},"scannerStatus":{"type":"string"},"pendingBatches":{"type":"integer","minimum":0}}},"PublicLeadRequest":{"type":"object","required":["name","email","message","consent","companyWebsite"],"properties":{"name":{"type":"string","maxLength":120},"email":{"type":"string","format":"email"},"organisation":{"type":"string","maxLength":160},"role":{"type":"string","maxLength":120},"message":{"type":"string","maxLength":4000},"consent":{"type":"boolean","const":true},"companyWebsite":{"type":"string","description":"Honeypot; must be empty."},"sourcePage":{"type":"string"}}},"NewsletterSubscribeRequest":{"type":"object","required":["email","consent","companyWebsite"],"properties":{"email":{"type":"string","format":"email"},"consent":{"type":"boolean","const":true},"companyWebsite":{"type":"string","description":"Honeypot; must be empty."},"sourcePage":{"type":"string"}}}}},"tags":[{"name":"Public Surfaces","description":"Unauthenticated marketing, trust-centre, newsletter and status endpoints"},{"name":"Mobile Device API","description":"Signed Ed25519 device API for Android USB fingerprint scanners at /api/v1/mobile"},{"name":"Mobile Device Administration","description":"Tenant-scoped pairing code, device revocation and remote wipe management"},{"name":"OAuth & Access","description":"OAuth 2.0 client credentials token issuance & API key management"},{"name":"Learner Registry & POPIA","description":"SA ID validation, DHA Population Register verification, and guardian consent"},{"name":"Biometrics & Liveness","description":"NIST MINEX III minutiae matching and ISO 30107-3 PAD selfie liveness"},{"name":"Attendance & Edge Sync","description":"Real-time clock-in capture, polygon geofencing, and offline sync cursors"},{"name":"Verification & Overrides","description":"Maker-Checker approval queue for M6/M7 supervisor exceptions"},{"name":"Deterministic Stipend Engine","description":"Sectoral Determination 5 calculations, tranche budgets, and dual sign-off"},{"name":"Banking & Direct PayShap","description":"Instant SARB PayShap payouts, FNB/Capitec/Standard Bank ACB exports"},{"name":"Webhooks","description":"HMAC-SHA256 signed event delivery engine & retry log"},{"name":"LMS Connectors","description":"Bidirectional sync bridge for Yiba Verified, Wemasoft, and Moodle Workplace"},{"name":"Tax & Regulatory Exports","description":"SARS s12H schedules, ETI ledgers, B-BBEE spend packs, and SETA MIS transformers"}]}