Many destinations. One platform.
Each destination, operator or region is a tenant with isolated data, configuration and integrations. The platform runs as a single shared application, with row-level tenant scoping and per-tenant configuration.
Tenancy properties enforced at the access layer.
Row-level tenant scoping
Enforced in Payload (headless) access control on every collection.
Per-tenant branding
Theme tokens, fonts, logos, hero imagery for both seasons.
Per-tenant integration credentials
Connector configurations stored encrypted at rest.
Per-tenant content, products, fares, schedules, customers
Isolated by row-level tenant_id.
Per-tenant Stripe account or Stripe Connect routing
For split payments to providers.
Per-tenant data residency option
EU default; tenant-specific region available.
Per-tenant subdomain or custom domain
With automatic TLS via Cloudflare.
Per-tenant locales
de, fr, en, it, rm and any additional configured.
Per-tenant identity provider
SwissID, SwissPass, OAuth/OIDC, SAML.
Per-tenant audit log
Retention configurable per regulatory regime.
Tenant switcher.
Switch between any workspace you have access to. Role and region shown inline; isolation is enforced at the data layer.
Workspaces
Switch workspace
How a request finds its tenant.
Tenancy is a first-class column on every collection. All Payload (headless) access control hooks enforce it. The middleware resolves tenant from subdomain, custom domain, or auth context.
middleware (pseudocode)
function resolveTenant(req) {
// 1. Subdomain match (mob.screver.app)
const sub = req.host.split('.')[0];
if (TENANTS_BY_SUBDOMAIN[sub]) return TENANTS_BY_SUBDOMAIN[sub];
// 2. Custom domain match (book.glacierexpress.ch)
if (TENANTS_BY_DOMAIN[req.host]) return TENANTS_BY_DOMAIN[req.host];
// 3. Auth context (Bearer pk_tenant_xxx)
const claims = decodeApiKey(req.headers.authorization);
return claims?.tenant ?? throwUnauthorized();
}Keep reading
