# Privacy and data minimization (https://prood.com/docs/architecture/privacy-data)

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](https://prood.com/privacy), [data processing terms](https://prood.com/dpa) and [sub-processors](https://prood.com/subprocessors). This page is the engineering view of the same facts.

## Roles [#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 [#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 [#commerce-identifier-rule]

```text
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.id
```

Orders, 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 [#guest-checkout]

1. Create a `customers` row with the contact details from checkout (`auth_user_id` null).
2. Fulfilment details live in the **order address snapshot** as well as on the customer.
3. On register or login, link the guest row: set `auth_user_id` once, and keep using `customers.id` everywhere.

## Merchant visibility [#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 [#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/retention` on apps/api, 03:30 UTC, guarded by `CRON_SECRET`) runs `purgeExpiredAuthData` in `packages/auth/src/retention.ts`. It deletes expired sessions and verification rows, team invitations 30 days after they expire, and `agent_event` rows after 730 days. The constants are in `RETENTION_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 Auth `deleteUser`, refused while they are the only owner of a store). A shopper can do the same on their store account page. Deleting an account calls `unlinkAuthUserFromCustomers`, 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: `beforeDeleteOrganization` refuses while a paid plan is live, then `deleteStoreData` removes every row with an `organization_id` in 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 (`eraseCustomer` in `packages/platform/src/privacy/store-data.ts`, reached through the session-only `DELETE /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, with `scrubOrders`, 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` (`recordAdminAction` in `packages/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.

## Related [#related]

<Cards>
  <Card title="Auth & tenant" href="/docs/apps/storefront/auth-tenant" description="Storefront auth and tenant resolution." />
  <Card title="Checkout flow" href="/docs/architecture/checkout-flow" description="Payment session metadata." />
  <Card title="Multi-tenant platform" href="/docs/architecture/multi-tenant" description="RLS and organization isolation." />
</Cards>
