Privacy and data minimization
Where personal data lives, who controls it, how long it stays, and what can be deleted today.
Prood separates authentication (Better Auth) from commerce (tenant-scoped Postgres). Commerce rows reference buyers by an internal UUID, but they also hold the contact details a store needs to fulfil an order.
The public policies are the source of truth for what Prood promises: privacy policy, data processing terms and sub-processors. This page is the engineering view of the same facts.
Roles
- Prood is the controller of platform accounts, shopper sign-in accounts, session security, visit statistics and the privacy choice.
- The merchant is the controller of its shoppers' orders, addresses and contact details. Prood is its processor.
Data zones
| Zone | Stored data | Who controls it | Access |
|---|---|---|---|
Better Auth (user, account, session, verification) | Name, email, password hash, session IP address and user agent | Prood | Auth layer only. The app database role can read only id and email of user |
customers, customer_addresses | Email, first and last name, phone, auth_user_id (null for guests), saved addresses | Merchant | Tenant RLS |
orders, carts, returns, reviews | Contact email, shipping and billing address snapshots, payment references, tracking, notes, review author name | Merchant | Tenant RLS |
| Checkout sessions (Upstash Redis) | Contact email, name, phone, addresses, amount, payment session | Merchant | Server only. 24 hours, or 7 days once a payment such as Multibanco has started |
| Rate limits (Upstash Redis) | Client IP address with the request path, per API key counters | Prood | Server only. Lasts the rate-limit window |
agent_event | Event type, agent, capability, status, duration. Never arguments or results | Prood | Organization members |
| Payment providers | Whatever the merchant's own Stripe, Easypay or Ifthenpay account needs | Merchant's provider | Third party, under the merchant's own contract |
Shopper accounts are platform-wide. One email address is one user, whichever store the shopper signed up on. Each store sees only its own customers and orders rows. After a shopper verifies their email, autoLinkGuestCustomers links guest customers rows with that email, in any store, to the account.
Commerce identifier rule
customers.id = internal UUID (commerce-owned)
customers.auth_user_id = FK → Better Auth user.id (nullable for guests)
orders.customer_id = customers.id (never auth user.id)
carts.customer_id = customers.idOrders, carts, payment metadata, webhooks and Agent Auth tools identify a buyer by customerId and orderId. Better Auth user.id must not appear on orders, carts, checkout sessions, webhooks or public API responses.
Contact details travel where they are needed: the checkout session holds the buyer's email, name and phone until the order is placed, and the payment provider receives what it needs to take the payment.
Guest checkout
- Create a
customersrow with the contact details from checkout (auth_user_idnull). - Fulfilment details live in the order address snapshot as well as on the customer.
- On register or login, link the guest row: set
auth_user_idonce, and keep usingcustomers.ideverywhere.
Merchant visibility
The dashboard lists customers from the tenant's customers rows. A merchant never sees another store's customers or the shopper's account.
Retention and erasure
What exists today:
- Redis checkout sessions and rate-limit keys expire on their own, as the table says.
- A daily cron (
/internal/retentionon apps/api, 03:30 UTC, guarded byCRON_SECRET) runspurgeExpiredAuthDatainpackages/auth/src/retention.ts. It deletes expired sessions and verification rows, team invitations 30 days after they expire, andagent_eventrows after 730 days. The constants are inRETENTION_DAYS: change them together with the privacy policy and_docs/compliance/retention-schedule.md. - A merchant can download their account data (
/api/account/export) and delete their account (Better AuthdeleteUser, refused while they are the only owner of a store). A shopper can do the same on their store account page. Deleting an account callsunlinkAuthUserFromCustomers, so stores keep their orders and customers but nothing points at the deleted account. - An owner or admin can download all of a store's data as one JSON file (
/api/store/export, a session-only route outside/v1) on every plan. An owner can delete a store:beforeDeleteOrganizationrefuses while a paid plan is live, thendeleteStoreDataremoves every row with anorganization_idin one transaction and fails if any is left. - Products, categories, API keys and agents can be deleted by the merchant.
- An owner or admin can erase one shopper (
eraseCustomerinpackages/platform/src/privacy/store-data.ts, reached through the session-onlyDELETE /api/store/customers/{id}and the customer page in the dashboard). The profile, addresses, wishlist and carts go. Orders stay and point at nobody, or, withscrubOrders, lose the shopper's email, addresses and notes. Reviews carry only an author name and aren't linked to the shopper. - What a Prood administrator does to an account is written to
admin_audit_event(recordAdminActioninpackages/auth/src/admin-audit.ts) and shown in the admin app under Audit log. It is kept 730 days (RETENTION_DAYS.adminAuditEvents).
What does not exist yet:
- Deleting image files from the R2 bucket when a store is deleted. Image upload isn't built, so no merchant file exists there today, and dormant shopper accounts aren't deleted: they are linked to merchants' orders, so they are kept until the person deletes them.
Order address snapshots may be kept where the merchant has a legal basis, such as tax or fulfilment records. Prood acts as processor for merchant storefront data and does not decide those retention windows.