Skip to content

Multi-tenancy

Every API request carries a tenant identity extracted from the trust header.
The platform enforces isolation at three layers:

LayerMechanism
APIX-Tenant-Id validated by trust middleware
Metadata (D1)All queries are scoped with WHERE tenant_id = ?
Storage (DO)Each shard is a separate Durable Object instance

No Durable Object is ever shared between tenants. Physical isolation is structural, not policy-based.

External caller
│
▼
edge-gateway ──(validates credential, adds X-Internal-Sig + X-Tenant-Id)──▶ core-api
│
trustMiddleware verifies sig
│
▼
handlers use c.var.tenantId

The trust.middleware.ts on core-api uses @core-labs/trust-kit to:

  1. Verify the X-Internal-Sig HMAC signature (shared secret with the upstream gateway)
  2. Extract and validate X-Tenant-Id
  3. Set c.var.tenantId for all downstream handlers
  • A tenant owns their graph projects, node/edge types, and all graph data.
  • Platform operators (admin) can view tenant metadata via the control-plane API using an admin token.
  • Tenants cannot see each other’s data, even if they use the same graph type names.

Graph metadata queries always include tenantId:

SELECT * FROM graphs WHERE tenant_id = ? AND id = ?

Drizzle ORM enforces this at the repository layer — raw SQL with missing tenant filters will fail TypeScript compilation if the schema types are used correctly.

In ENVIRONMENT=development, the trust middleware accepts any X-Tenant-Id without signature verification. This lets you test multi-tenant scenarios locally:

Terminal window
# Tenant A
curl http://localhost:8787/graphs -H "X-Tenant-Id: tenant-a"
# Tenant B
curl http://localhost:8787/graphs -H "X-Tenant-Id: tenant-b"

Each tenant sees only their own graphs.