Compliance API
PDPL data retention sweep and SDAIA breach notification reporting.
Per T-06-11: All compliance routes require
super_adminrole. A middleware check rejects non-super_admin requests with403 FORBIDDEN. Per T-06-12: Every sweep and report invocation is audit-logged.
POST /compliance/retention/sweep
Execute a PDPL data retention sweep. Records whose retention deadline is at or before the sweep time (retainUntil <= now) are processed; records with a later deadline (retainUntil > now) remain mandated, are preserved, and are counted.
Requires authentication + super_admin role.
What the sweep does
| Record | Eligible when | Action |
|---|---|---|
activations | retainUntil <= now and deletedAt is null | Soft-delete — deletedAt is stamped |
esim_store_orders | Deletion was requested, retainUntil <= now, and deletedAt is null | Scrub — the eSIM payload columns smdpAddress, matchingId, activationCodeRaw and iccid are nulled, deletedAt is stamped, and the row is kept |
notifications | Deletion was requested, retainUntil <= now, and deletedAt is null | Clear params to {} and stamp deletedAt. Keep the row, dedupe key, and delivery history |
notification_batches | Finished or cancelled at least 24 hours ago, and replayExpiresAt <= now or is null, irrespective of recipient deletion | Replace request with channels-only metadata. Keep the batch row, response snapshot, fingerprint, and dedupe history |
Store orders are scrubbed rather than removed because the order and its payment are a financial record. Everything with financial meaning stays: the presentment amount and currency, the FX rate, the retail USD snapshot, the product link, and the timestamps. A scrubbed order still appears in GET /store/orders with esimProfile: null, and its QR endpoint returns 404 STORE_ORDER_NOT_FOUND.
retainUntil is stamped on a user's store orders when that user requests account deletion through DELETE /profile. The retention window is 365 days per CMPL-05.
Account deletion also stamps existing notifications with the same 365-day deadline. Archive and expiry alone do not make recipient notification content eligible. Scrubbed notifications disappear from inbox reads but remain in profile exports with empty parameters. Batch request scrubbing is independent of this recipient retention window; callback receipts and opt-outs are not cleared. See notification retention.
The deadline comparison is inclusive: a record whose retainUntil timestamp exactly equals the sweep's now value is eligible in that sweep. The same conditions — retainUntil <= now and deletedAt is null — are applied when selecting and marking/scrubbing, so an eligible record is counted and processed once; records with retainUntil > now remain mandated.
Example Request
POST /compliance/retention/sweep
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Response
200 OK
{
"sweepDate": "2026-05-05T08:00:00.000Z",
"prunedCount": 5,
"mandatedPreservedCount": 42,
"storeOrdersScrubbedCount": 3,
"storeOrdersPreservedCount": 17,
"notificationsScrubbed": 4,
"notificationsPreserved": 12
}| Field | Type | Description |
|---|---|---|
sweepDate | string | ISO 8601 timestamp the sweep evaluated against |
prunedCount | integer | Activations soft-deleted by this sweep |
mandatedPreservedCount | integer | Activations still inside their retention window |
storeOrdersScrubbedCount | integer | Store orders whose eSIM payload was scrubbed by this sweep |
storeOrdersPreservedCount | integer | Store orders still inside their retention window |
notificationsScrubbed | integer | Notification records selected for content scrubbing |
notificationsPreserved | integer | Un-scrubbed notifications with a deletion request and a future retention deadline |
The audit event RETENTION_SWEEP_EXECUTED records the sweep timestamp, all six response counts, and notificationBatchRequestsScrubbed (batch requests changed by the sweep). The batch count is audit-only. The retention sweep must run to scrub eligible content; elapsed time alone does not perform the update.
Error Responses
| Code | Status | Condition |
|---|---|---|
AUTH_REQUIRED | 401 | Missing or invalid token |
FORBIDDEN | 403 | User is not super_admin |
POST /compliance/breach-report
Generate an SDAIA breach notification report per PDPL Article 39. Breach notifications must be reported to SDAIA within 72 hours of detection.
Requires authentication + super_admin role.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
incidentId | string | Yes | Unique incident identifier |
detectedAt | string | Yes | ISO 8601 datetime of breach detection |
affectedCategories | string[] | Yes | Array of affected data categories (min 1) |
estimatedAffectedUsers | integer | Yes | Estimated number of affected users (min 0) |
mitigationActions | string[] | No | Array of mitigation action descriptions |
description | string | Yes | Detailed breach description (min 1 character) |
Example Request
POST /compliance/breach-report
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json{
"incidentId": "INC-2026-0001",
"detectedAt": "2026-05-04T14:30:00.000Z",
"affectedCategories": ["personal_data", "payment_data", "identity_data"],
"estimatedAffectedUsers": 150,
"mitigationActions": [
"Affected users notified via SMS and email",
"Passwords force-reset for all affected accounts",
"Third-party forensic investigation initiated"
],
"description": "Unauthorized access to user identity verification records detected on May 4, 2026. Investigation ongoing."
}Response
200 OK
{
"reportId": "BR-2026-0001",
"incidentId": "INC-2026-0001",
"generatedAt": "2026-05-05T08:00:00.000Z",
"reportType": "SDAIA_breach_notification",
"summary": {
"detectedAt": "2026-05-04T14:30:00.000Z",
"reportedAt": "2026-05-05T08:00:00.000Z",
"timeToReport": "17h30m",
"within72Hours": true,
"affectedCategories": ["personal_data", "payment_data", "identity_data"],
"estimatedAffectedUsers": 150,
"mitigationActions": [
"Affected users notified via SMS and email",
"Passwords force-reset for all affected accounts",
"Third-party forensic investigation initiated"
]
},
"metadata": {
"pdplArticle": "39",
"reportFormat": "SDAIA_v1",
"generatedBy": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
}Error Responses
| Code | Status | Condition |
|---|---|---|
AUTH_REQUIRED | 401 | Missing or invalid token |
FORBIDDEN | 403 | User is not super_admin |
VALIDATION_ERROR | 400 | Invalid request body (missing required fields, etc.) |