Sellvik / developers
Concepts

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/products with shop A's key never returns shop B's products — the route doesn't take a shopId parameter 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:

HostUse
https://api.sellvik.appCanonical. Use this in production code.
https://<shop-subdomain>.sellvik.comConvenience 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:

  1. Extract the bearer token; SHA-256 hash it.
  2. Look up ApiKey by hashed key.
  3. The matching row carries shopId. That's your tenant for this request.
  4. Every Prisma query inside the handler is scoped via shopContext — there is no way for handler code to "accidentally" cross tenants.

For store requests:

  1. Extract the publishable key from X-Sellvik-Key.
  2. Same lookup.
  3. Additionally verify the shop has headlessMode = true and the request Origin is in Shop.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.

On this page