Skip to content

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.

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.

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"

Data is deleted in foreign-key-safe order:

  1. Config entries
  2. Permission policies
  3. API keys
  4. Webhook deliveries (via webhook FK join)
  5. Webhook events
  6. Webhooks
  7. Audit logs
  8. Consent records (via ON DELETE CASCADE)
  9. The tenant record itself

A tenant with active children cannot be purged. You must purge children first, working leaf-to-root:

// Purge the subtree bottom-up
await stratum.purgeTenant("child-1");
await stratum.purgeTenant("child-2");
await stratum.purgeTenant("parent");

Attempting to purge a tenant with children returns 409 HAS_CHILDREN.

Track per-tenant, per-subject consent with purpose, expiration, and metadata.

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.

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.

await stratum.revokeConsent(tenantId, "user-456", "marketing");
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.

Automatically purge expired transactional data (audit logs, webhook events, webhook deliveries) older than a configurable retention period.

const result = await stratum.purgeExpiredData(90); // 90 days
console.log(`Deleted ${result.deleted_count} expired records`);

Via the API:

# Default: 90 days
curl -X POST http://localhost:3001/api/v1/maintenance/purge-expired \
-H "X-API-Key: YOUR_ADMIN_KEY"
# Custom: 180 days
curl -X POST "http://localhost:3001/api/v1/maintenance/purge-expired?retention_days=180" \
-H "X-API-Key: YOUR_ADMIN_KEY"
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.

  • Default: 90 days
  • Maximum: 3,650 days (10 years)
  • Invalid values fall back to the default
  1. Export the tenant’s data and deliver it to the data subject
  2. Archive the tenant (DELETE /api/v1/tenants/:id) for a grace period
  3. Purge after the grace period (POST /api/v1/tenants/:id/purge)
  4. Schedule regular purge-expired calls for log retention

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

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.