Multi-Tenancy in APIs: Data Isolation, Routing, and Tenant Context
Most SaaS APIs are multi-tenant: the same infrastructure serves many customers, each operating in isolation from the others. A user of Tenant A should never see, modify, or even know about the data of Tenant B. This isolation is the foundational guarantee of a multi-tenant system, and it must hold at every layer of the stack — not just at the query level, but at the API design level, the authentication level, and the operational level.
Multi-tenancy is not an afterthought. APIs designed as single-tenant systems and later converted to multi-tenant are where isolation failures occur. The tenant boundary must be first-class from the beginning.
Identifying the Tenant
Every API request in a multi-tenant system must carry tenant context. The server needs to know which tenant the request belongs to before it can enforce data isolation. There are several mechanisms for conveying tenant identity.
Authentication credential: the most common approach. The API key or JWT used to authenticate the request belongs to a specific tenant. The server resolves the tenant from the credential and applies it as a filter to every database query. No separate tenant identifier is needed — the credential implies it.
Subdomain: acme.api.example.com and globex.api.example.com route to the same API server, which extracts the tenant from the host header. This is clean from a URL structure standpoint and works naturally with DNS. It requires a wildcard DNS entry and TLS certificate handling for subdomains.
URL path prefix: /tenants/acme/resources or /acme/resources. Explicit and visible, but adds noise to every URL and complicates OpenAPI specs that must document the tenant prefix.
Request header: X-Tenant-ID: acme. Simple to implement, easy to miss in client integrations. Not appropriate when the tenant ID should be derived from authentication rather than trusted from an arbitrary header.
The authentication-derived approach is generally preferable for security: the tenant identity is bound to the credential, which means it cannot be manipulated independently of authentication. Header and URL-based approaches are appropriate when the authentication credential is inherently tenant-agnostic (a platform-level admin token) and the tenant must be specified separately.
Data Isolation Patterns
Three primary patterns handle the physical separation of tenant data.
Separate databases per tenant provides the strongest isolation. Each tenant’s data lives in a distinct database, often on distinct infrastructure. A bug in one tenant’s data cannot affect another. Database-level encryption keys can be per-tenant. Compliance requirements that demand data residency in specific regions can be satisfied per tenant. The cost is operational complexity: schema migrations must run across every tenant database, and connection pool management grows with tenant count.
Shared database, separate schemas places each tenant’s tables in a distinct schema (or namespace) within a shared database. This provides logical isolation without the operational overhead of separate databases, though a misconfigured query that escapes the schema boundary can access other tenants’ data. Migrations run against a shared database but apply schema changes to every tenant schema.
Shared database, shared schema with tenant ID columns is the most common approach for APIs serving large numbers of small tenants. Every table includes a tenant_id column. Every query includes WHERE tenant_id = ?. The risk is that a missing tenant_id filter in any query is a data leak — the wrong tenant’s records are returned silently.
The shared-schema approach requires discipline. Every query must filter by tenant ID. Every index must include tenant ID as a prefix column. Every new table added to the schema must include a tenant ID column, and every query against that table must use it. The most effective enforcement mechanism is a query-level middleware or ORM plugin that automatically adds tenant ID filtering to every query based on the current request context, making it impossible to accidentally omit.
The Tenant Context Object
Establish a tenant context that is populated at the start of every request and used throughout the request lifecycle. This context carries the resolved tenant ID (and potentially other tenant-specific configuration: feature flags, plan limits, locale, timezone) and is accessible throughout the request’s call stack without being passed explicitly as a parameter through every layer.
In practice this is usually a request-scoped context object or thread-local storage populated by authentication middleware before any handler runs. The rest of the application reads from it rather than accepting a tenantId parameter on every function.
The pattern also enables audit logging: every action recorded in an audit log automatically includes the tenant context, without requiring explicit tenant ID threading through every operation.
Rate Limiting Across Tenants
Rate limits in a multi-tenant API must aggregate at the tenant level, not the API key level. A tenant with ten API keys should not receive ten times the rate limit of a tenant with one key. Aggregate all requests from all credentials belonging to a tenant against a single quota.
Different tenants may be on different plans with different rate limits. The rate limiting system must resolve the tenant’s plan limits, not apply a global limit. A tenant on an enterprise plan with a higher request quota should not be blocked at the same threshold as a tenant on the free plan.
Rate limiting state (current usage, window resets) should also be per-tenant and visible to the tenant: they need to know their own quota and current consumption, not the aggregate consumption across all tenants.
Tenant Isolation Testing
Tenant isolation must be explicitly tested. The test suite should include cross-tenant access tests: does authenticating as Tenant A and requesting a resource belonging to Tenant B return 403 or 404? Does the error response for a missing resource look the same whether the resource does not exist or belongs to a different tenant (to avoid enumeration of other tenants’ resource IDs)?
The correct behavior is 404 for both cases — from the requesting tenant’s perspective, a resource belonging to another tenant is as non-existent as a resource that does not exist at all. Returning 403 confirms that the resource exists but is inaccessible, which is information a legitimate tenant should not receive about another tenant’s data.
Run cross-tenant access tests on every endpoint that returns, modifies, or deletes resources. A test matrix that covers every endpoint against both the owning tenant and a non-owning tenant catches isolation gaps before they become incidents.
Tenant Onboarding and Offboarding
Tenant creation must initialize all necessary data structures — the tenant record, default configuration, initial permissions — atomically. A partially created tenant is a support burden. Use transactions or idempotent setup flows that can be safely retried.
Tenant deletion (or deactivation) must be complete. A deleted tenant’s data should not be accessible through the API, regardless of whether it is physically purged immediately or soft-deleted. Credentials belonging to a deleted tenant must be invalidated. Depending on data retention requirements, physical deletion may happen asynchronously and on a delay — but the API-level access must be revoked immediately.
Multi-tenancy is the invisible architecture that makes SaaS possible. When it works correctly, tenants experience the API as if it were built for them alone. When it fails, the consequences are severe: data exposure between customers is a trust-destroying, potentially regulatory-action-triggering event. Design the isolation in from the start, test it explicitly, and treat any cross-tenant data access as a critical severity incident regardless of how it occurred.