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
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
- Every multi-tenant table has a
site_idcolumn or a non-nullable FK to one that does. Reviewed at PR time; integration tests assert. - Every query joins through
site_idor filters on it. SQLAlchemy model definitions encode the relationships; cross-tenant joins would require explicit ORM gymnastics. - Every webhook payload carries
site_idso consumers can route without ambiguity. - Audit events include
merchant_idfor 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 oneX-Site-ID and one X-Stella-Token.