Skip to content

Retention + deletion — ZevID

Per-category retention rules. Pairs with data-inventory.md. Mechanism column says how each rule is enforced.

Retention table

Category (from inventory) Retention period Deletion mechanism Driver
OTP codes (otps) Deleted on expiry (10-min TTL) Daily cleanup cron deletes where expires_at < now() (cleanup.service.ts:52) Operational — codes are single-use
Authorization codes (authorization_codes) 1 hour past expiry Same cron deletes where expires_at < now()-1h Operational — OAuth codes are single-use, kept briefly past expiry for race-condition safety
Sessions (sessions) Deleted on expiry Same cron deletes where expires_at < now() Operational — refresh tokens past expiry serve no purpose
Login events (login_events) 90 days Same cron deletes where created_at < now()-90d Operational — security forensics horizon
Account row (accounts) Account lifetime DPO-routed manual deletion request (see subject-rights.md) User-controlled
Phones (account_phones) Until user removes via portal User action in account portal Security → Phones User-controlled
TOTP secret + recovery codes (accounts.totp_*) Until user disables TOTP User action in account portal Security → Authentication User-controlled
Geo rules / Allowed IPs / App passwords Until user removes User action in account portal User-controlled
KYC record (kyc_verifications) 7 years after account deletion DPO-routed manual process anonymises name fields and retains the hash-only fields Regulatory — CBN AML/CFT retention
Product authorizations (product_authorizations) Until user revokes from Connected Apps User action; emits user.consent.revoked webhook Per-(account × client) row
Cross-product enrollments (product_enrollments) Account lifetime; is_active flag for soft-disable Owning product manages via PUT .../enrollments/:productClientId Each product owns its row's lifecycle
ZPIP consent requests (zpip_consent_requests) Retained for audit None — terminal-state rows retained for the audit trail Audit
ZPIP scope grants (zpip_scope_grants) Until user revokes; revoked rows retained for audit User revokes from Connected Apps → revoked_at set Audit
ZPIP token log (zpip_token_log) 7 years (financial-record-adjacent audit) Retained for the audit trail Audit
Zev Credit grants / transactions / lines / idempotency 7 years ON DELETE RESTRICT on accounts prevents deletion while grants exist Regulatory — financial records
Referrals (referral_codes, referrals) Account lifetime; isActive for soft disable User-controlled
Invites (invites) Retained for audit Audit
Admin audit log (admin_audit_logs) Indefinite None NDPC accountability
OAuth client config / service keys / product capabilities Lifetime of the relationship with the product Admin actions in superadmin panel Configuration, not personal data

Daily cleanup cron

Runs 3am UTC daily (cleanup.service.ts@Cron(CronExpression.EVERY_DAY_AT_3AM)) in the same NestJS process as the API. Deletes expired otps, authorization_codes (1h past expiry), sessions, and login_events older than 90 days. Instrumented with a Sentry Cron Monitor (slug zevid-cleanup-cron) plus a Better Stack heartbeat — failures alert via AlertingService.

User-driven deletion paths

  • Phone removalDELETE /v1/account/phones/:phoneId. Refuses to remove the primary if other verified phones exist.
  • Allowed IPs / Geo rules / App passwords — user portal Security → Access Controls.
  • TOTP / Recovery codes — user portal Security → Authentication. Requires password re-entry.
  • Connected product revocation — user portal Home → Connected Products. Revokes product_authorizations rows and zpip_scope_grants rows; emits webhooks.

Backups

Database runs on Neon-on-AWS in us-east-1. Backups follow Neon's default configuration (continuous WAL-based point-in-time recovery within the platform's retention window). The R2 bucket (file uploads) lives under Cloudflare's R2 service.

Holds + exceptions

For litigation hold or data-subject-restriction requests, the DPO (dpo@zevop.com) directs engineering on a per-case basis. Routine retention follows the table above.

Reviews

Quarterly review by the DPO + ZevID team lead. Recorded in change-log.md.