> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stella-commerce.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Multi-tenancy

> How Stella keeps merchants' data fully isolated from each other.

Stella is multi-tenant by construction. Every row in every table is
tagged with a `site_id` (or transitively, via a foreign key to a row
that is). Every query in every endpoint filters by `site_id` drawn
from the caller's token. There is no code path that elides this
filter.

## Where `site_id` comes from

| Caller           | Source                                                                 |
| ---------------- | ---------------------------------------------------------------------- |
| Storefront token | `storefront_access_tokens.merchant_id` (joined to `merchants.site_id`) |
| Customer token   | The customer's `merchant_id` at the time the token was minted          |
| Agent token      | The agent's `merchant_id` (inherited from the customer who consented)  |
| Dashboard        | The authenticated user's active merchant (per session)                 |

Calls cannot cross merchants. A customer token from `kasa` returns 401
on `/api/storefront/products` if `X-Site-ID: huba-nepal` is sent —
because the storefront-token gate runs first and rejects the mismatch.

## Data isolation invariants

1. **Every multi-tenant table has a `site_id` column or a non-nullable
   FK to one that does.** Reviewed at PR time; integration tests assert.
2. **Every query joins through `site_id` or filters on it.** SQLAlchemy
   model definitions encode the relationships; cross-tenant joins
   would require explicit ORM gymnastics.
3. **Every webhook payload carries `site_id`** so consumers can route
   without ambiguity.
4. **Audit events** include `merchant_id` for cross-merchant
   forensics.

## Per-merchant CORS

`/api/storefront/*` does dynamic CORS reflection: the response
`Access-Control-Allow-Origin` only mirrors the request's `Origin` if
it appears in any active token's `allowed_origins` for the merchant
identified by `X-Site-ID`. Tokens revoked or merchants without an
allowlisted origin → preflight 403.

This is enforced on top of standard server-side Origin checks — both
layers must pass.

## Multi-merchant developer use cases

If you operate multiple Stella tenants (e.g. an agency managing
several brands), you mint one storefront token per tenant. Tokens
don't compose across merchants; each call carries exactly one
`X-Site-ID` and one `X-Stella-Token`.
