Multi-tenancy
How Sellvik isolates data and credentials across shops.
Sellvik is multi-tenant: many merchant shops share the same physical infrastructure but never see each other's data. Understanding the tenancy model is the difference between a clean integration and a 3am page.
Tenancy is per-shop, not per-account
A shop is the unit of tenancy. One person can own many shops; one shop can have many users. The API resolves the calling shop from the API key, not from any URL path component.
That means:
- An admin key minted for shop A can only read/write shop A's data, period.
Calling
/v1/admin/productswith shop A's key never returns shop B's products — the route doesn't take ashopIdparameter at all. - A publishable key for shop A only works for
/v1/store/*calls bound to shop A.
One canonical host, many routes
Every v1 endpoint resolves from three hosts. They return identical responses:
| Host | Use |
|---|---|
https://api.sellvik.app | Canonical. Use this in production code. |
https://<shop-subdomain>.sellvik.com | Convenience for storefronts already on the tenant subdomain. |
https://<custom-domain> | Same as above if the merchant attached their own domain. |
The first form is what we recommend. It survives custom-domain reattachment, subdomain renames, and DNS reconfiguration — and it makes per-shop logs in your stack easier to read (one Sellvik host, not N).
How the server resolves your shop
For admin requests:
- Extract the bearer token; SHA-256 hash it.
- Look up
ApiKeyby hashed key. - The matching row carries
shopId. That's your tenant for this request. - Every Prisma query inside the handler is scoped via
shopContext— there is no way for handler code to "accidentally" cross tenants.
For store requests:
- Extract the publishable key from
X-Sellvik-Key. - Same lookup.
- Additionally verify the shop has
headlessMode = trueand the requestOriginis inShop.allowedOrigins.
This is the same model the storefront uses internally — the only difference is the request enters via the headless API surface instead of the proxy.
Customer accounts are per-shop
A customer who registers at shop A is not a customer at shop B. There is no cross-shop customer table. Email collisions are allowed across shops; within a shop, email is unique.
The same shopper buying from two Sellvik shops has two separate accounts and two separate JWTs. This is intentional — merchants own their customer lists.
What "shop subdomain" means
shopSubdomain is the URL slug for a shop, picked at signup and immutable
afterward (without manual support). Examples: acme, bibi-saree,
tea-house.
You see it in URLs (acme.sellvik.com), in webhook payloads (shopSubdomain
field on every event), and in your panel URL. You will never need to
pass it to a v1 API call — the key implies the shop.
If you build tooling that operates across many shops (e.g. an integration
that serves multiple Sellvik merchants), key the storage by shopId or
shopSubdomain. Both are stable; the shop UUID is the safer key for an
external system because subdomains can change with merchant support
intervention.