PASETO tokens, the four-tier capability stack, and how to read a denial.
Authorization
CortexDB enforces a four-tier policy stack on every request. Every endpoint requires one or more named capabilities; every denial cites the tier that decided and the specific capability that was missing. No opaque 403s.
Identity — PASETO v4 public tokens
Every request must carry:
Authorization: Bearer <PASETO v4 public token>
X-Cortex-Actor: user:alice
Tokens are signed by an issuer registered in the deployment's IssuerRegistry. CortexDB does not mint tokens — your IdP (or a CortexDB-trusted issuer) does. There is no /v1/auth/login endpoint.
Claims
{
"iss": "https://idp.acme.com/realms/main",
"sub": "user:alice",
"aud": "cortexdb:tenant:acme",
"exp": 1763212929,
"iat": 1763209329,
"jti": "j_01HX...",
"deployment": "cloud_managed",
"caps": ["scope.read.*", "scope.write"],
"scopes": ["org:acme/dept:eng/user:alice", "org:acme/dept:eng"]
}
X-Cortex-Actor must match sub. The token can only narrow what policy already allows — it cannot grant a capability the deployment denies.
The four-tier capability stack
Decisions cascade from outer to inner. An allow at an inner tier can override an outer allow (be more specific), but an outer deny is final.
1. Deployment policy ← preset-defined floor (cannot be overridden by tenant/scope)
2. Tenant policy ← per-tenant defaults
3. Scope policy ← per-scope ACLs and members
4. Actor policy ← per-actor overrides
Built-in deployment presets:
| Preset | Notes |
|---|---|
| on_prem_enterprise | Most permissive. Experimentals enabled. Cross-workspace GDPR erasure allowed by default. |
| cloud_managed | Default. Experimentals gated. Cross-workspace GDPR denied by default. |
| cloud_strict | Strictest. Most diagnostics denied. |
| dev_local | Unsigned tokens allowed (actor.require_signed=false). For local development only. |
Reading a denial
A 403 response always cites tier + capability + reason:
{
"error_code": "policy_denied",
"message": "forget.gdpr.cross_workspace denied",
"details": {
"capability": "forget.gdpr.cross_workspace",
"decided_by_tier": "deployment",
"reason": "preset cloud_managed denies"
},
"retriable": false
}
Use GET /v1/policy/effective?actor=<>&scope=<> to introspect the full allow/deny matrix for an actor at a scope.
Capability catalog (excerpt)
| Capability | What it gates |
|---|---|
| scope.read.local | Read records at exactly the named scope |
| scope.read.holistic | Traverse up through ancestor scopes |
| scope.read.descend | Traverse down into descendant scopes |
| scope.write | Write an experience to the scope |
| scope.write.elevated | Write above the actor's natural scope |
| scope.write.on_behalf_of | Set observed_actor ≠ caller |
| scope.write.about_other | Set subject ≠ observed_actor |
| forget.cascade.derived_only | Selective forget across derived layers |
| forget.cascade.redact_events | Blank event payloads, keep IDs |
| forget.gdpr | True event deletion via /v1/erasures |
| forget.gdpr.cross_workspace | Propagate erasure into co-owned workspaces |
| llm.invoke | Call /v1/answer (counts against tenant LLM quota) |
| diagnostics.read | Request diagnostics ≠ "none" on recall/answer |
| understanding.synthesize | Trigger /v1/understanding/synthesize |
The full catalog (stable + beta + experimental) is in the backend reference.