GDPR Article 30 requires controllers to maintain a written record of processing activities. The exemption for organisations under 250 employees does not apply when processing is regular (yours is — every login is processing), so assume you need this.
This is the register for a stock deployment. It is a starting point you adapt, not a document you file as-is: the controller identity, retention periods, and transfer safeguards are yours to fill in, and any feature you add creates a row that is not here.
Controller
| Field | Value |
|---|---|
| Controller | LEGAL_COMPANY_NAME (see config/app.ts → appConfig.legal) |
| Address | LEGAL_ADDRESS |
| Registration | LEGAL_REGISTRATION_NUMBER |
| DPO | DPO_EMAIL — leave unset if you have not appointed one. Publishing a fake DPO contact is worse than publishing none. |
| Lead supervisory authority | SUPERVISORY_AUTHORITY |
Article 37 makes a DPO mandatory for large-scale regular monitoring or large-scale special-category processing. A typical SaaS is below that line, but AI usage logging pushes some deployments closer than operators expect.
Processing activities
1. Account management and authentication
| Purpose | Create and secure accounts, authenticate users, manage workspace membership |
| Legal basis | Art. 6(1)(b) performance of a contract |
| Data subjects | Registered users, invited workspace members |
| Categories | Email, name, phone, birthday, avatar, auth identifiers, membership role |
| Tables | profiles, accounts, memberships, invitations |
| Recipients | Supabase (hosting + auth), email provider (magic links) |
| Retention | Life of the account, then the 30-day deletion queue |
2. Billing and payments
| Purpose | Process subscriptions, credit packs, licenses; invoicing; tax records |
| Legal basis | Art. 6(1)(b) contract; Art. 6(1)(c) legal obligation for tax retention |
| Categories | Billing identifiers, Stripe customer/subscription/payment ids, amounts, currency |
| Tables | subscriptions, payments, licenses, credit_transactions |
| Recipients | Stripe |
| Retention | Statutory accounting period (commonly 7–10 years) — survives account deletion; state your local period here |
Card data never reaches the application; Stripe holds it.
3. AI features (chat, agents, RAG)
| Purpose | Generate responses to user prompts, index and search uploaded documents |
| Legal basis | Art. 6(1)(b) contract |
| Categories | Prompt text, generated output, uploaded document content and embeddings, token counts, model/agent ids |
| Tables | chat_sessions, chat_messages, ai_requests, documents, document_chunks |
| Recipients | The LLM providers configured in aiConfig.providers — see /ai-transparency |
| Transfers | Provider-dependent; typically EU + US |
| Retention | Life of the account; cleanup-sessions purges old sessions on the configured schedule |
This is the highest-risk activity in the register. Users paste arbitrary content into prompts, including data about third parties and occasionally special-category data, and it leaves your infrastructure. It is the main reason a DPIA is likely required — see DPIA.
4. Consent management
| Purpose | Record and demonstrate cookie/marketing consent |
| Legal basis | Art. 6(1)(c) legal obligation (Art. 7(1) demonstrability) |
| Categories | Consent category, decision, timestamp, policy version, IP, user agent |
| Tables | user_consents (current state), user_consent_events (append-only trail) |
| Retention | Kept beyond withdrawal — the proof is the point |
5. Security, audit and operational logging
| Purpose | Detect abuse, investigate incidents, maintain an admin audit trail |
| Legal basis | Art. 6(1)(f) legitimate interest (securing the service) |
| Categories | Hashed IP, user agent, event type, geolocation, admin actions, redacted error messages |
| Tables | user_access_logs, admin_logs, error_logs |
| Retention | LOGS_RETENTION_DAYS (default 90), enforced by purge-error-logs |
A legitimate-interest basis requires a documented balancing test. Write it down; "we needed it" is not a balancing test.
6. Transactional and marketing email
| Purpose | Magic links, billing notices, invitations; newsletter where opted in |
| Legal basis | Art. 6(1)(b) for transactional; Art. 6(1)(a) consent for marketing |
| Tables | pending_emails, notification_preferences |
| Recipients | Brevo or Mailjet, per EMAIL_PROVIDER |
7. Referral and affiliate programs
| Purpose | Attribute referrals, calculate rewards and commissions, prevent fraud |
| Legal basis | Art. 6(1)(b) contract; Art. 6(1)(f) for fraud prevention |
| Categories | Attribution links, hashed IP and user agent, application text, commission amounts |
| Tables | referral_codes, referrals, affiliate_* |
| Retention | Life of the account plus any commission dispute window |
Only active when REFERRAL_ENABLED / AFFILIATES_ENABLED are on. Remove these rows if you ship with the features off.
8. Push notifications
| Purpose | Deliver credit alerts and system announcements |
| Legal basis | Art. 6(1)(a) consent (browser permission) |
| Tables | push_subscriptions, notification_log |
| Retention | Until unsubscribed; cleanup-push-subscriptions removes stale tokens |
Technical and organisational measures (Art. 32)
Row-level security scoped by membership; service-role writes for audit tables; CSRF on state-changing routes; rate limiting; secret redaction and IP hashing in logs; SSRF validation on webhook URLs; constant-time secret comparison; recent-auth gate on sensitive admin actions; encryption in transit and at rest via Supabase.
Maintenance
Review when you add a table holding personal data, add or change a subprocessor, change a retention period, or change a legal basis. Bump PRIVACY_POLICY_VERSION in config/compliance.ts whenever the change affects what the privacy notice says.