GDPR compliance
Stratum provides built-in tooling for GDPR compliance: data export (Article 20), hard-purge (Article 17), consent management, audit logging, and configurable data retention policies.
Data export (Article 20)
Section titled “Data export (Article 20)”Export all data belonging to a tenant as a structured JSON archive:
const archive = await stratum.exportTenantData("tenant-uuid");Via the API:
curl http://localhost:3001/api/v1/tenants/TENANT_UUID/export \ -H "X-API-Key: YOUR_ADMIN_KEY"The export includes:
| Table | Contents |
|---|---|
tenant |
Tenant record (name, slug, metadata, status) |
config_entries |
All config key-value pairs |
permission_policies |
All permission policies |
api_keys |
Key metadata (no plaintext secrets) |
webhooks |
Webhook registrations |
webhook_events |
Event history |
webhook_deliveries |
Delivery history |
audit_logs |
Full audit trail |
consent_records |
Consent records with purposes |
API keys are exported without plaintext secrets for security.
Tenant purge (Article 17)
Section titled “Tenant purge (Article 17)”Hard-delete all data belonging to a tenant. This operation is irreversible.
For a SCHEMA_PER_TENANT tenant it also drops the tenant’s schema (DROP SCHEMA ... CASCADE,
in the same transaction as the rows), and for a DB_PER_TENANT tenant it drops
the tenant’s database (before the rows, since DROP DATABASE cannot run in a
transaction; close any connection pools to that database first). A tenant that
is still pending has no storage of its own, so only its rows are removed.
await stratum.purgeTenant("tenant-uuid");Via the API:
curl -X POST http://localhost:3001/api/v1/tenants/TENANT_UUID/purge \ -H "X-API-Key: YOUR_ADMIN_KEY"Deletion order
Section titled “Deletion order”Data is deleted in foreign-key-safe order:
- Config entries
- Permission policies
- API keys
- Webhook deliveries (via webhook FK join)
- Webhook events
- Webhooks
- Audit logs
- Consent records (via
ON DELETE CASCADE) - The tenant record itself
Hierarchical constraint
Section titled “Hierarchical constraint”A tenant with active children cannot be purged. You must purge children first, working leaf-to-root:
// Purge the subtree bottom-upawait stratum.purgeTenant("child-1");await stratum.purgeTenant("child-2");await stratum.purgeTenant("parent");Attempting to purge a tenant with children returns 409 HAS_CHILDREN.
Consent management
Section titled “Consent management”Track per-tenant, per-subject consent with purpose, expiration, and metadata.
Grant consent
Section titled “Grant consent”await stratum.grantConsent(tenantId, { subject_id: "user-456", purpose: "marketing", expires_at: "2025-12-31T23:59:59Z", metadata: { source: "signup_form", ip: "10.0.0.1" },});The control plane API requires expires_at to be an ISO 8601 datetime with a time zone, either Z or an offset such as +02:00. It validates the request body with GrantConsentInputSchema, and returns 400 VALIDATION_ERROR for any other string. That includes a date without a time, a datetime without a time zone, and PostgreSQL special values such as infinity. The library method grantConsent applies the same schema. It throws a ValidationError for any other string and writes no row.
Check active consent
Section titled “Check active consent”const consent = await stratum.getActiveConsent(tenantId, "user-456", "marketing");if (consent) { // Consent is active (not revoked, not expired) sendMarketingEmail(userId);}getActiveConsent() returns null if consent is revoked or expired.
Revoke consent
Section titled “Revoke consent”await stratum.revokeConsent(tenantId, "user-456", "marketing");Predefined purposes
Section titled “Predefined purposes”| Constant | Value |
|---|---|
DATA_PROCESSING |
data_processing |
ANALYTICS |
analytics |
MARKETING |
marketing |
THIRD_PARTY_SHARING |
third_party_sharing |
You can also use any custom string as a purpose.
Data retention
Section titled “Data retention”Automatically purge expired transactional data (audit logs, webhook events, webhook deliveries) older than a configurable retention period.
const result = await stratum.purgeExpiredData(90); // 90 daysconsole.log(`Deleted ${result.deleted_count} expired records`);Via the API:
# Default: 90 dayscurl -X POST http://localhost:3001/api/v1/maintenance/purge-expired \ -H "X-API-Key: YOUR_ADMIN_KEY"
# Custom: 180 dayscurl -X POST "http://localhost:3001/api/v1/maintenance/purge-expired?retention_days=180" \ -H "X-API-Key: YOUR_ADMIN_KEY"What gets purged
Section titled “What gets purged”| Table | Records Deleted |
|---|---|
webhook_deliveries |
Deliveries older than cutoff |
webhook_events |
Events older than cutoff |
audit_logs |
Logs older than cutoff |
Tenant records, config entries, and permissions are not affected by retention purges.
Limits
Section titled “Limits”- Default: 90 days
- Maximum: 3,650 days (10 years)
- Invalid values fall back to the default
Recommended GDPR workflow
Section titled “Recommended GDPR workflow”- Export the tenant’s data and deliver it to the data subject
- Archive the tenant (
DELETE /api/v1/tenants/:id) for a grace period - Purge after the grace period (
POST /api/v1/tenants/:id/purge) - Schedule regular
purge-expiredcalls for log retention
Authorization
Section titled “Authorization”All GDPR operations require the admin scope:
| Route | Required Scope |
|---|---|
GET /api/v1/tenants/:id/export |
admin |
POST /api/v1/tenants/:id/purge |
admin |
POST /api/v1/maintenance/purge-expired |
admin |
Audit trail
Section titled “Audit trail”All GDPR operations are recorded in the audit log with full context (actor, resource, before/after state). This provides evidence of compliance actions for regulatory audits.