← Back to home · Privacy policy · ROPA · DPA
Data Retention Policy
Version 1.0 — 17 June 2026
This policy operationalises the GDPR storage limitation principle (Art. 5(1)(e)) for every category of personal data TravelCS processes — both as a controller (operator accounts, marketing leads, platform telemetry) and as a processor (operator-owned customer data). For each category we publish the retention period, the deletion trigger, the legal basis and the disposal method.
1. Scope
Applies to all personal data stored in TravelCS production systems, backups and sub-processor environments. Operator-specific overrides are permitted where the operator documents a different lawful retention period in their own privacy notice; the shorter period always wins.
2. Retention schedule
| Ref | Category | System / table | Retention period | Deletion trigger | Legal basis | Disposal |
|---|---|---|---|---|---|---|
| R1 | Operator admin & employee accounts | auth.users, operator_employees, operator_members | Active life of the account + 30 days | Account closure request OR 24 months of continuous inactivity. Operator-employee specific lifecycle triggers (stale invites, disabled seats, dormant active seats) are detailed in R1b. | Art. 6(1)(b) contract; Art. 5(1)(e) storage limitation | Hard delete from primary store; purge from daily backups within 30 days |
| R1b | Operator employee seats (churned / inactive) | public.operator_employees (status, last_active_at, updated_at) | Disabled seat: 30 days after status='disabled' (matches R1 backup tail). Stale invite: 90 days after status='invited' without acceptance. Dormant active seat: auto-disabled after 24 months without sign-in (then re-enters the 30-day Disabled clock). | status='disabled' for ≥30 days → hard delete. status='invited' for ≥90 days without conversion → hard delete. status='active' AND coalesce(last_active_at, updated_at) older than 24 months → auto-disable (does not delete immediately, preserves audit attribution). | Art. 5(1)(e) storage limitation; Art. 6(1)(b) contract (seat no longer needed for the operator's service); Art. 32(1)(b) integrity (revoke dormant credentials) | Hard delete of the operator_employees row; team_invites cascade-delete; team_audit_logs.employee_id is preserved with the foreign key set to NULL so the audit trail (Art. 5(2) accountability) survives the seat. Enforced daily by pg_cron job 'retention-purge-daily' via public.purge_inactive_operator_employees(); every run audited to public.retention_purge_log under rule_ref='R1b-disabled', 'R1b-stale-invite', 'R1b-auto-disabled' (migrations/061_operator_employees_lifecycle.sql). |
| R2 | Landing-page leads (marketing enquiries) | public.leads, public.landing_leads | 12 months from capture | 12-month rolling window OR earlier opt-out / erasure request | Art. 6(1)(f) legitimate interest (commercial follow-up); Art. 5(1)(e) | Monthly automated purge; converted leads migrate to R3 (operator-owned) |
| R3 | Booking leads & customer contact details | public.booking_leads | 24 months after last interaction (operator default; configurable per operator) | No inbound or outbound message for 24 months | Operator's Art. 6(1)(b) / (f) basis — TravelCS retains as processor on instructions | Soft-delete + 30-day grace, then hard delete; DSAR erasure honoured immediately |
| R4 | Channel message bodies (email, WhatsApp, web chat) | public.channel_messages | 36 months from message timestamp | Rolling 36-month window OR operator account closure | Operator's Art. 6(1)(b) basis; Art. 5(1)(c) minimisation enforced via redaction | Body purged; minimal metadata (timestamp, channel, direction) kept 12 more months for analytics |
| R5 | AI drafting prompts & completions | public.ai_drafts, ai_employee_runs | 90 days | Daily age-based purge | Art. 6(1)(f) legitimate interest (quality & abuse detection); Art. 5(1)(e) | Hard delete; aggregate non-PII metrics retained indefinitely |
| R6 | DSAR requests & audit trail | public.dsar_requests, public.dsar_request_events | 3 years from request closure | Closure date + 3 years | Art. 5(2) accountability; Art. 12(3) demonstrability | Hard delete after retention period; archived export to cold storage if litigation hold applies |
| R7 | Breach incident register | public.breach_incidents, public.breach_incident_events | 5 years from incident closure | Closure date + 5 years | Art. 33(5) breach documentation obligation | Hard delete after retention period |
| R8 | Authentication & security logs | auth audit log, RLS coverage probe rows | Auth logs: 12 months. RLS audit rows: 24 months. | Age-based purge | Art. 32 security of processing; Art. 5(2) accountability | Hard delete; aggregate counters retained |
| R9 | Request / application logs (platform telemetry) | edge logs, request logs | 90 days | Rolling 90-day window | Art. 6(1)(f) legitimate interest (security, debugging) | Automatic rotation |
| R10 | Backups | Managed daily snapshots + point-in-time recovery window | 30 days | Rolling 30-day window | Art. 32(1)(c) availability and resilience | Encrypted snapshots automatically expired |
| R11 | Deleted-operator JSON backups (admin restore window) | public.deleted_operator_backups | 30 days from operator deletion | deleted_at + 30 days (matches R1/R10 backup tail) | Art. 6(1)(c) accountability for admin-initiated erasures; Art. 32(1)(c) availability (short reversibility window for accidental deletion); Art. 5(1)(e) storage limitation | Hard delete of the JSON snapshot row; deletion audited to public.retention_purge_log. DSAR Art. 17 erasure of a customer inside a snapshot is honoured immediately on request, ahead of the 30-day window. |
| R12 | Authenticated user accounts (auth.users) — unassigned or unconfirmed | auth.users | Unconfirmed (email_confirmed_at IS NULL): 24 hours from created_at. Unassigned (no row in user_roles, operator_members or operator_employees): 24 hours from created_at. | Daily pg_cron job 'retention-purge-daily' invokes public.purge_unconfirmed_auth_users(24) and public.purge_unassigned_auth_users(24) inside public.run_retention_purge_daily(); both hard-delete the auth.users row. | Art. 5(1)(c) data minimisation (no lawful processing purpose for an account that never confirmed or was never assigned a role/operator link); Art. 5(1)(f) integrity & confidentiality (minimise dormant attack surface); Art. 32(1)(b) | Hard delete from auth.users; every run audited to public.retention_purge_log under rule_ref='R12-unconfirmed' / 'R12-unassigned' (migrations/069_auth_user_lifecycle.sql). |
3. Erasure on request (Art. 17)
A valid erasure request received via /dsar overrides the standard retention period for the categories where erasure is lawful. Records kept under a legal obligation (DSAR audit trail R6, breach register R7, fiscal records) are restricted rather than deleted, with access limited to the DPO until the legal retention period expires.
4. Disposal methods
- Hard delete: row removal from the primary database, cascading to dependent records via foreign-key
ON DELETE CASCADEwhere defined. - Backup expiry: daily encrypted snapshots roll off automatically after the R10 window; no manual restore from expired snapshots is permitted.
- Redaction: where aggregate analytics value remains after the PII window closes, the row is kept but identifying fields are nulled (channel message body, IP address, free-text content).
5. Review
Reviewed at least every 12 months by the DPO and whenever a new processing activity is added to the ROPA. Material changes are notified to operator admins by email and reflected in the privacy notice.
6. Contact
Questions or requests: dpo@travelcs.ai.