CortexDB Docs
Docs for CortexDB 0.12.8, the version running on CortexDB Cloud (api.cortexdb.ai).
Core Concepts

Authorization

PASETO tokens, the four-tier capability stack, and how CortexDB evaluates authorization.

CortexDB authorizes every request through a four-tier capability stack and identifies callers with PASETO v4 public tokens. Authorization is enforced at the storage layer, not in application code.

The four-tier 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

Deployment presets

Set with CORTEX_DEPLOYMENT_PRESET. These four values are the complete set.

PresetNotes
dev_localDefault. Experimentals enabled. Local development only. A keyed server rejects actor-only requests with 401.
on_prem_enterpriseMost permissive production preset; cross-workspace GDPR erasure allowed by default.
cloud_shared_saasShared-cloud preset; experimentals gated; cross-workspace GDPR denied by default.
cloud_privateSingle-tenant cloud preset; experimentals gated.

Authenticating an agent

Every request in a secured deployment carries:

Authorization: Bearer <PASETO v4 public token>
X-Cortex-Actor: user:alice

Self-hosted auth modes

With CORTEX_API_KEY set, the key value itself is a working Bearer credential (constant-time compared), alongside PASETO/JWT bearers. To run unauthenticated for local dev, start with CORTEX_INSECURE_NO_AUTH=1 — requests are served without a token (served as user:local, or as whatever X-Cortex-Actor names; it does not restrict the bind, so publish the port on loopback only, -p 127.0.0.1:3141:3141). With neither on a network-exposed bind, the server auto-generates a key and requires it. There is no /v1/auth/login.

On a network, send the key with every request

Every deployment preset requires the key: a request carrying only an X-Cortex-Actor header gets 401 MISSING_TOKEN.

Pick your auth path

You want to…UseGet a token by
Try the API in 60 s (prototype, AI self-install)Anonymous sign-upPOST /v1/auth/signup {} → token + actor + scope. Self-hosted, the in-binary minter is off by default on a keyed server (503 NOT_CONFIGURED) — enable with CORTEX_V1_MINTER_ENABLE=1; under CORTEX_INSECURE_NO_AUTH=1 signup is served. Off on CortexDB Cloud (CORTEX_DISABLE_ANON_SIGNUP=1): cloud accounts sign up at cortexdb.ai and create tokens on the dashboard.
Run a service accountServer-side mintToken with auth.mint, then POST /v1/auth/tokens with subject/ttl/scopes. In-binary minter is dev-only.
Production with permanent identitiesYour IdP issues PASETORegister your IdP's signing key (IssuerRegistry); CortexDB verifies.
Dashboard / MCP servercx_live_… API key from the dashboardA separate credential format the BFF exchanges for PASETO server-side.

Token claims

{
  "iss":  "https://idp.acme.com/realms/main",
  "sub":  "user:alice",
  "aud":  "cortexdb:tenant:acme",
  "exp":  1763212929,
  "jti":  "j_01HX...",
  "deployment": "cloud_shared_saas",
  "caps":   ["scope.read.local", "scope.write"],
  "scopes": ["scope.read:org:acme/dept:eng/*", "scope.write:org:acme/dept:eng/user:alice"]
}

X-Cortex-Actor must match sub. A token can only narrow what policy already allows — it cannot grant a capability the deployment denies.

  • caps narrows the token to the listed capabilities (empty inherits the deployment and tenant defaults).
  • scopes confines capabilities to scope subtrees. Each entry is <capability>:<scope-path>[/*]: an exact capability or a prefix (scope.read covers scope.read.local, scope.read.holistic, …), then the subtree it is confined to. A capability no entry names stays unconfined. A bare path such as "org:acme/dept:eng" is refused at mint with 422 INVALID_BODY ("expected <capability>:<scope-path>[/*]").
  • Neither claim makes the caller a member of a scope. Reading or writing a registered scope also needs a seat on its scope roster; a freshly minted agent that holds the right capabilities but no seat is refused with 403 POLICY_DENIED.

Application tokens on your own server

An application usually gets its own token instead of the operator key: minted by the server, confined to the scopes it uses. Three settings decide whether those tokens work with scopes the operator registered:

  • Turn the minter on. CORTEX_V1_MINTER_ENABLE=1. With a key set and the minter off, POST /v1/auth/tokens answers 503 NOT_CONFIGURED.
  • Put the tokens in the operator's tenant. The operator key acts in tenant default. A minted token acts in the tenant of the server's audience, CORTEX_V1_AUDIENCE, which is cortexdb:tenant:dev unless set. A scope belongs to the tenant that registered it, so with the default audience an app token is refused (403) on every scope the operator key registered. Start the server with CORTEX_V1_AUDIENCE=cortexdb:tenant:default and both share one tenant.
  • Give the token a seat. Register the scope with the token's subject among its members (writer to write, reader to read), or add it with PUT /v1/scopes/members.
docker run -d … -e CORTEX_API_KEY -e CORTEX_V1_MINTER_ENABLE=1 \
  -e CORTEX_V1_AUDIENCE=cortexdb:tenant:default cortexdb/cortexdb:<release>

curl -fsS --fail-with-body -X POST "$CORTEX_URL/v1/scopes" -H "Authorization: Bearer $CORTEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"path":"org:acme","members":[{"actor":"agent:assistant","role":"writer"}]}'

APP_TOKEN=$(curl -fsS --fail-with-body -X POST "$CORTEX_URL/v1/auth/tokens" -H "Authorization: Bearer $CORTEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"subject":"agent:assistant","ttl_seconds":86400,"caps":["scope.write","scope.read"],"scopes":["scope.write:org:acme/*","scope.read:org:acme/*"]}' \
  | jq -r .token)

The app then sends Authorization: Bearer $APP_TOKEN with X-Cortex-Actor: agent:assistant. Remember that recall's default view, holistic, needs scope.read.holistic (scope.read covers it). Tokens the server mints stop working when it restarts, though the data stays: mint new ones after a restart, or have your identity provider issue tokens.

Denials

A 403 cites the 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_shared_saas denies"
  },
  "retriable": false
}

Introspect the allow/deny surface with GET /v1/policy/effective?actor=…&scope=…. That endpoint returns allowed/denied as flat capability-name arrays (plus deployment_preset, rate_limits) — the per-entry tier/reason shown in the denial body is not returned there. See the Policy API reference.

Common capabilities

CapabilityGates
scope.read.local / .holistic / .descendread at scope / traverse ancestors / traverse descendants
scope.write / .elevated / .on_behalf_of / .about_otherwrite / write above natural scope / set observed_actor / set subject
forget.cascade.derived_only / .redact_eventsselective forget / blank event payloads
forget.gdpr / .cross_workspacetrue event deletion via /v1/erasures / propagate into co-owned workspaces
llm.invokecall /v1/answer
diagnostics.readrequest diagnostics ≠ "none"
understanding.synthesizetrigger /v1/understanding/synthesize

FAQ

Can an inner policy override an outer one? An inner allow can be more specific; an outer deny is always final.

Does CortexDB use flat tenant identifiers? No — hierarchical scopes, so policy inherits down the hierarchy.

See also

On this page