{
  "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": [],
        "x-authentication": "Single-use pairing code and device challenge. No bearer token or request signature exists yet at this step.",
        "parameters": [],
        "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": [],
        "x-authentication": "Single-use pairing code and device challenge. No bearer token or request signature exists yet at this step.",
        "parameters": [],
        "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": [
          {
            "DeviceSignatureOnly": []
          }
        ],
        "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."
      },
      "DeviceSignatureOnly": {
        "type": "apiKey",
        "in": "header",
        "name": "X-RR-Device-Signature",
        "description": "Signed request headers only (X-RR-Device-Id, X-RR-Timestamp, X-RR-Nonce, X-RR-Content-SHA256, X-RR-Device-Signature); no access token. Used to rotate the refresh token after the access token has expired."
      },
      "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 (RFC 6749 §4.4) for machine-to-machine integrations (LMS, ERP, Payroll). POST application/x-www-form-urlencoded to /api/v1/auth/token with client_id and client_secret in the body or as HTTP Basic credentials. A machine token is confined to the /integrations, /sync and /webhooks surfaces (plus /auth/session and /auth/logout) and to the scopes granted to the client.",
        "flows": {
          "clientCredentials": {
            "tokenUrl": "/api/v1/auth/token",
            "scopes": {
              "attendance:read": "Query verified daily clock-in/out records, sealed session manifests, and attendance percentages.",
              "attendance:write": "Submit biometric clock-in batches and offline attendance logs from approved edge hardware.",
              "learners:read": "Query enrolled learners, demographics, South African ID status, and POPIA consent flags.",
              "learners:write": "Register candidates, upload preflight CSVs, and update banking/demographic attributes.",
              "stipends:read": "Access SD5/NMW stipend calculations, deduction breakdowns, and locked disbursement runs.",
              "payouts:initiate": "Trigger instant disbursements through Netcash PayShap, Paystack, or Stitch Open Banking.",
              "webhooks:manage": "Register, edit, and test HTTPS event callback subscriptions and examine delivery logs.",
              "lms:sync": "Ingest class rosters and push verified attendance hours into Yiba, Wemasoft, or Moodle.",
              "audit:read": "Inspect cryptographic SHA-256 ledger entries and verify ECTA Section 15 evidence seals.",
              "integrations:read": "Read integration health and LMS connector configuration and qualification modules.",
              "integrations:write": "Reserved: modify integration settings. No machine-accessible route requires it yet."
            }
          }
        }
      },
      "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"
    }
  ]
}