Skip to main content
The POST /api/v1/encryption/rotate route re-encrypts all ciphertext held for the calling tenant under the current key version. It covers every store that uses the platform’s AES-256-GCM versioned encryption, and runs as a dry-run or a live pass depending on the dryRun flag.

POST /api/v1/encryption/rotate

Auth: admin role + tenant scope. MFA posture depends on the caller:
  • Session admins — the middleware step-up gate requires a valid, fresh kaireon_stepup cookie (the same 15-minute HMAC proof that protects all admin writes) before this write is allowed. See MFA enforcement.
  • krn_ API-key callers — these carry no session JWT, so the middleware MFA gate is skipped. This is the platform-wide posture for all admin writes via API key, not specific to this route — key possession alone gates them.

Request body

Covered stores

Not covered (notRotated)

The response always includes a notRotated[] array for stores that cannot be rotated:

Response

Each store returns { total, rotated, errors[] }. total is the number of rows fetched; rotated is the count successfully re-encrypted (or that would be, in dry-run); errors[] holds per-row error strings.
A secret that cannot be decrypted is never overwritten. If the ciphertext fails to authenticate — tampering, or the key that encrypted it is no longer configured — rotation refuses that row, leaves the stored value untouched, and records it in errors[]. This matters: an undecryptable ciphertext is still recoverable if the original key is restored, whereas one that had been re-encrypted from an empty payload would be gone permanently. Such rows are counted in total but not in rotated, so rotated < total with entries in errors[] is the signal to investigate before assuming rotation is complete.

Row cap

Each store is capped at 5,000 rows per pass. When a store exceeds the cap, the pass is truncated and the store’s errors[] array contains a message indicating the cap was hit. Re-running the route does not auto-advance past the already-rotated rows — the cap applies to the first 5,000 rows fetched by the WHERE clause on every run. Tenants with more than 5,000 rows in a store need staged rotation (e.g. run the route repeatedly until all errors disappear).

Keyless development

When CONNECTOR_ENCRYPTION_KEY is not set (local development), the platform-settings rotation performs a clean no-op. All other stores behave normally because they derive their key from the same env var.

Environment variables

Rotation workflow

  1. Generate a new key and set it as CONNECTOR_ENCRYPTION_KEY with CONNECTOR_ENCRYPTION_KEY_VERSION=2 (or your next version string).
  2. Set the old key as CONNECTOR_ENCRYPTION_KEY_PREVIOUS with CONNECTOR_ENCRYPTION_KEY_PREVIOUS_VERSION=1.
  3. Redeploy so the new env vars are live.
  4. Call POST /api/v1/encryption/rotate with { "dryRun": true } to verify what will be rotated.
  5. Call POST /api/v1/encryption/rotate (live pass). Check all stores report errors: [].
  6. For tenants with > 5,000 rows in any store, repeat step 5 until errors disappear.
  7. Once rotation is confirmed complete, you can safely clear CONNECTOR_ENCRYPTION_KEY_PREVIOUS.

Status codes

  • MFA enforcement — the step-up gate protecting session-admin writes to this route
  • DSAR — export payloads that age out independently of this rotation
  • Security hardening — operator checklist