Multi-Tenancy
cPod is a multi-tenant platform. Every entity in every domain carries a tenantId. The platform enforces tenancy at the database query layer — SDK consumers never pass tenant identifiers.
Core Principle
You never pass tenantId to any SDK call. The platform extracts it from your JWT and stamps it server-side on every read, write, and query.
Developer writes: Backend executes:
sdk.people.list() → db.people.find({
tenantId: 'tnt-abc123', ← from JWT
deletedAt: null,
})
sdk.storage.db.set({ → mongo.set({
tier: 'private', key: 'tnt-abc123/apps/app-myservice/config',
path: 'config', value: { ... },
value: { ... }, })
})The tenantId in the JWT claim was set by the CoreSDK Control Plane at token issuance, from the app’s registered tenant at registration time. A client cannot modify it.
Isolation Boundaries
┌─────────────────────────────────────────────────────────────────┐
│ Tenant A (tnt-abc123) │
│ │
│ ┌────────────────────┐ ┌────────────────────┐ │
│ │ App 1 │ │ App 2 │ │
│ │ app-hr-portal │ │ app-sec-scanner │ │
│ │ │ │ │ │
│ │ private storage │ │ private storage │ │
│ │ (only this app) │ │ (only this app) │ │
│ └────────┬───────────┘ └────────┬───────────┘ │
│ │ │ │
│ └─────────┬───────────────┘ │
│ │ │
│ shared storage │
│ (all apps in tenant) │
│ │
│ EDM entities: people, assets, risk, ... │
│ (accessible by any app with edm.read scope) │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Tenant B (tnt-xyz789) │
│ ... completely isolated from Tenant A at query level ... │
└─────────────────────────────────────────────────────────────────┘Organization vs Tenant
The platform uses two terms for the same concept depending on context:
| Term | Context | Description |
|---|---|---|
Tenant | Platform internals, JWT claims, database keys | The isolation boundary at platform level. Prefixed tnt-. Never shown to end users. |
Organization | SDK API, developer-facing docs | The same concept — what enterprise developers think of as “their org”. |
The SDK exposes sdk.organizations.getCurrent(), not sdk.tenants.getCurrent(). The response includes your organization’s name, slug, plan, and settings — but internally it maps 1:1 to a Tenant record.
const org = await sdk.organizations.getCurrent()
// {
// id: 'tnt-abc123', ← internal tenantId format
// name: 'Acme Corp',
// slug: 'acme-corp',
// plan: 'enterprise',
// region: 'us-east-1',
// createdAt: '2025-03-01T00:00:00Z',
// }You will see tnt- prefixed IDs in audit logs and debug output. This is the same entity as your Organization — the prefix indicates the internal Tenant record.
App Isolation Within a Tenant
Multiple apps can be registered under a single tenant. They share EDM data (subject to scope) but are isolated at the storage layer.
| What’s shared | What’s isolated |
|---|---|
| EDM entities (people, assets, risk, …) | private storage tier |
shared storage tier | user storage is per-user-per-app |
| Audit log (tenant-scoped) | OAuth credentials (client_id, client_secret) |
The appId is derived from the OAuth client_id at registration and is stamped into the JWT at issuance. An app cannot self-report a different appId — it is always set from the registered credential.
Rego Policy for Tenant Isolation
The CoreSDK sidecar evaluates a Rego policy on every request. The default policy enforces:
# Simplified example of tenant isolation rules
package cpod.authz
default allow := false
# Allow read if tenant_id in token matches entity tenant_id
allow if {
input.action == "read"
input.claims.tenant_id == input.resource.tenant_id
"edm.read" in input.claims.scopes
}
# Allow write if tenant_id matches and write scope present
allow if {
input.action in {"create", "update", "delete"}
input.claims.tenant_id == input.resource.tenant_id
"edm.write" in input.claims.scopes
}Tenants can upload custom Rego bundles to extend these rules — for example, to enforce that only users in a specific group can write to certain entity types.
Cross-Tenant Access
Cross-tenant access is not supported in the standard SDK. The platform does not issue tokens that span multiple tenants. If your use case requires data aggregation across tenants (e.g., a multi-org dashboard), use the admin API with appropriate CPOD_ADMIN_TOKEN credentials — this flow is documented in the Management section.
Tenant Provisioning
Tenants are created through the platform admin API, not through the SDK. Once provisioned:
- The tenant receives a
tnt-prefixed ID - An initial admin user is seeded
- The admin registers apps via
POST /v1/oauth/apps - Each app registration returns a
client_idand one-timeclient_secret - Apps configure
CPOD_CLIENT_IDandCPOD_CLIENT_SECRETand start making SDK calls
From that point on, all tenancy is automatic — no tenantId is ever passed in SDK calls.