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 removal —
DELETE /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. Revokesproduct_authorizationsrows andzpip_scope_grantsrows; 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.