API guide
Building Multi-Tenant APIs: Architecture Patterns 2026
Multi-tenant API architecture in 2026 — database isolation strategies, tenant-aware routing, authorization, rate limiting, and data isolation patterns.

Building Multi-Tenant APIs: Architecture Patterns
A multi-tenant API must identify the tenant, authorize the requested action, and carry that context through every data and infrastructure call. The design is successful when a request cannot silently cross tenant boundaries and operators can prove the controls with tests and logs.
TL;DR verdict
For teams evaluating shared infrastructure, one option is a shared schema with PostgreSQL row security when its role, ownership, policy, and pooling constraints fit the workload. Schema-per-tenant and database-per-tenant make different isolation and operational tradeoffs. The reviewed mechanism documentation does not establish which architecture is common, so choose from threat model, failure radius, data-residency needs, restore requirements, and measured workload.
API fit matrix
| Data model | Isolation seam | Operational cost | Evaluate when |
|---|---|---|---|
| Shared database, shared schema | Tenant key plus policy on each protected table | One migration and shared capacity | Tenants can share infrastructure and policy behavior is testable |
| Shared database, separate schemas | Schema selection and schema-specific privileges | Many schema objects and migration targets | Namespace separation is useful without separate database operations |
| Separate databases | Connection routing and database credentials | Per-database migrations, backups, pools, and monitoring | Failure radius, residency, or restore boundaries justify the overhead |
| Hybrid placement | Tenant registry chooses shared or dedicated placement | Two operating paths | A defined tenant class needs a separate boundary |
Keep the placement decision in a tenant registry rather than scattering it through handlers.
Resolve tenant context once
Resolve the tenant from an authenticated signal such as a scoped API key or signed token. A host or path can help choose the tenant, but it should not grant access by itself. Pass a typed tenant context to business logic instead of letting every query reconstruct it.
interface TenantContext {
tenantId: string;
actorId: string;
permissions: string[];
requestId: string;
}
async function listOrders(ctx: TenantContext) {
requirePermission(ctx, 'orders:read');
return withTenantTransaction(ctx.tenantId, (db) =>
db.query('SELECT * FROM orders ORDER BY created_at DESC')
);
}
Auth matrix
| Signal | Use | Check |
|---|---|---|
| API key | Server-to-server tenant binding | Hash lookup, status, scopes, rotation, tenant ownership |
| JWT claim | Signed tenant and actor context | Issuer, audience, signature, expiry, tenant membership |
| Subdomain | Request routing hint | Resolve to tenant, then require authenticated membership |
| Explicit tenant path | Administrative resource selection | Authorize actor for the selected tenant |
| Internal service identity | Background and service calls | Preserve originating tenant and actor in the job payload |
Run authorization after authentication and tenant resolution. Administrative roles still need an explicit tenant scope unless the operation is deliberately cross-tenant and separately audited.
PostgreSQL row security
The PostgreSQL 18 documentation explains that enabled row security policies control which rows normal queries and data-modification commands can access. If row security is enabled and no applicable policy exists, default-deny applies.
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.current_tenant_id')::uuid)
WITH CHECK (tenant_id = current_setting('app.current_tenant_id')::uuid);
RLS is a defense layer, not an unconditional guarantee. Superusers and roles with BYPASSRLS bypass policies, and table owners normally bypass row security unless the table is configured to force it. Separate the migration/ownership role from the restricted application role, review grants, and test with the exact role used in production.
Data-access quality table
| Concern | Required behavior | Test |
|---|---|---|
| Tenant propagation | Every repository method requires tenant context | Type or runtime check rejects absent context |
| Transaction scope | Context is set inside the same transaction as protected queries | Pool reuse cannot leak context |
| Write policy | WITH CHECK rejects cross-tenant inserts and updates | Attempt to change a row's tenant key |
| Role behavior | Application role cannot bypass policy | Run direct SQL as the production role |
| Cache scope | Tenant ID is part of every relevant key | Same resource ID in two tenants returns distinct values |
| Job scope | Queued work carries immutable tenant identity | Worker refuses payloads without tenant context |
Use transaction-scoped context with pools
PostgreSQL exposes configuration-setting functions, including transaction-local settings. Set the tenant inside a transaction and run protected statements on that same connection.
BEGIN;
SELECT set_config('app.current_tenant_id', '00000000-0000-4000-8000-000000000123', true);
SELECT * FROM orders;
COMMIT;
The example uses a valid UUID because the policy casts the setting to uuid; in production, set the UUID for the authenticated tenant whose rows the transaction may access. The final true makes this transaction-scoped tenant context. It reduces cross-request leakage when a pooled connection is reused.
PgBouncer transaction pooling assigns a server connection for one transaction. Its feature matrix also says transaction pooling breaks session-based features and marks SET/RESET unsupported in that mode. Use transaction-local context within the transaction and do not rely on session state across transactions. Validate the exact driver and pool behavior before shipping.
Per-tenant rate-limit box
Apply limits to a stable tenant identifier after authentication. A safe policy records the unit, window, burst behavior, response, retry signal, and administrative override.
A plan table in architecture documentation is only an illustrative example; configure limits from your own product policy, service capacity, and customer contract. Keep tenant limits distinct from global dependency protection so one tenant cannot consume the reserve needed for everyone else.
Capacity and noisy-neighbor controls
Measure concurrency, query duration, lock time, queue depth, storage growth, and cache pressure by tenant. Then:
- size pools from measured concurrency;
- add statement or transaction budgets where the workload supports them;
- move expensive work to tenant-scoped queues;
- route analytical reads deliberately;
- isolate a tenant only when its measured behavior or contract requires it; and
- benchmark with representative policies and workload before attributing cost to RLS.
Neither PostgreSQL nor PgBouncer documentation supplies a portable connection ceiling, pool fan-in ratio, policy-overhead percentage, or sharding threshold.
Integration risk box
| Failure mode | Consequence | Control |
|---|---|---|
| Handler accepts a tenant ID from an untrusted header | Cross-tenant access | Bind the tenant to authenticated credentials |
| Application role owns protected tables | Policy bypass | Separate owner and runtime roles; test grants |
| Pooler changes connection semantics | Missing or leaked context | Keep context transaction-local and integration-test the pool |
| Cache key omits tenant identity | Cross-tenant response reuse | Centralize cache-key construction |
| Background job loses tenant context | Work runs in the wrong scope | Require tenant identity in the queue contract |
| Restore process is shared but untested | Recovery cannot satisfy tenant needs | Test restore granularity before promising it |
Isolation tests are release gates
Create two tenants with colliding resource identifiers and exercise read, list, update, delete, cache, background-job, and direct-database paths. A useful assertion is not merely a denied HTTP response; it confirms that Tenant A cannot observe or modify Tenant B's record and that logs retain the request and tenant identities.
Test the RLS layer with the production application role. Also test bypass roles intentionally so operators know which maintenance paths sit outside the policy.
Source-backed evidence
PostgreSQL row security
The current PostgreSQL documentation supports policy behavior, default-deny, configuration settings, and the documented bypass conditions.
PgBouncer transaction pooling
The current PgBouncer feature matrix supports the transaction-assignment model and its incompatibility with session-based features. It does not supply workload sizing numbers.
Editorial limits
Placement criteria, test matrices, rate-limit design, and tenant observability are architecture guidance. Validate them against the service's threat model and measurements.
Methodology
APIScout reviewed PostgreSQL 18 documentation for row security and configuration functions plus the current PgBouncer features page on 2026-08-22. Unsupported performance thresholds, prevalence claims, compliance shortcuts, plan quotas, and third-party availability statements were removed.
Source-backed FAQ
Does RLS replace application authorization?
No. Application authorization decides whether the actor may perform the action. RLS adds a data-layer policy and reduces the impact of some query mistakes, subject to role and ownership conditions.
Can tenant context be stored in a database session?
That is unsafe to assume with transaction pooling. Keep context transaction-local, use the same transaction for protected work, and test the driver/pool combination.
When should a tenant move to a dedicated database?
When a concrete restore, residency, failure-radius, performance, or contractual requirement justifies the additional migrations, connections, backups, and monitoring.
What does the "enterprise tier" label prove?
Nothing about architecture by itself. Tie dedicated placement to an explicit requirement and an operating plan, not to a marketing label.
Sources
- PostgreSQL 18: Row Security Policies — accessed 2026-08-22
- PostgreSQL 18: System Administration Functions — accessed 2026-08-22
- PgBouncer features — accessed 2026-08-22
Related guides
{/* Sources: mt-postgres-rls, mt-postgres-config, mt-pgbouncer-features. Claims: apiscout:building-multi-tenant-apis-2026:pricing_or_plan, apiscout:building-multi-tenant-apis-2026:compatibility_integrations, apiscout:building-multi-tenant-apis-2026:product_capabilities, apiscout:building-multi-tenant-apis-2026:performance_benchmarks, apiscout:building-multi-tenant-apis-2026:ranking_popularity_superlative, apiscout:building-multi-tenant-apis-2026:availability_or_provider_status. */}
The API Integration Checklist (Free PDF)
Step-by-step checklist: auth setup, rate limit handling, error codes, SDK evaluation, and pricing comparison for 50+ APIs. Used by 200+ developers.
Join 200+ developers. Unsubscribe in one click.