Anonymous signup, token mint, identity introspection, revocation.
Auth
CortexDB authenticates every request with a PASETO v4 public token. There are three documented ways to obtain one — see the decision table on the Authorization concept page.
Self-hosted servers additionally key their auth mode off CORTEX_API_KEY:
- Unset — local no-auth mode. Every request is served as
user:local(tenantdefault); outside Docker the server binds loopback only, and the startup log announces LOCAL NO-AUTH MODE. - Set — Bearer auth is required, and the key value itself is a working credential: send
Authorization: Bearer <CORTEX_API_KEY>(constant-time compared). A wrong or missing key returns 401. PASETO v4 / JWT bearer tokens are accepted alongside it.
Required headers on every request
Authorization: Bearer <PASETO v4 public token>
X-Cortex-Actor: user:alice
X-Cortex-Actor must match the token's sub claim, otherwise the server returns 401 actor_mismatch.
POST /v1/auth/signup — Anonymous sign-up
Capability: none. Public endpoint.
Availability on self-hosted servers. Anonymous signup (and the ephemeral dev token minter) is enabled only under the
dev_localpreset with noCORTEX_API_KEYset. Once a key is configured, signup returns 503 (unless the minter is explicitly re-enabled withCORTEX_V1_MINTER_ENABLE=1).
Mint an anonymous PASETO token, an actor ID, and a default scope path in one round trip. Designed for the "Try CortexDB" flow and for AI agents that need to self-install with zero human input. Free-tier — 7-day token TTL (refresh by calling signup again).
Request
POST /v1/auth/signup
Content-Type: application/json
{}
The body is currently always {} — future revisions may accept hints like preferred region, but no fields are required.
Response
{
"token": "v4.public.eyJpc3MiOi...",
"jti": "jti_019e2f...",
"expires_at": "2026-06-15T17:10:12Z",
"user_id": "user:u_019e2f188c1579...",
"scope": "org:u_019e2f.../user:u_019e2f...",
"tier": "free"
}
The returned token carries the free-tier capability set: scope.read.local, scope.read.holistic, scope.read.descend, scope.write, scope.create.user, scope.create.agent, scope.create.ws, scope.create.project, understanding.read, understanding.synthesize, forget.cascade.derived_only, lifecycle.subscribe, blob.upload, blob.read, audit.read, vocabulary.read, temporal.phrases.read. Notably not included: diagnostics.read, auth.mint, forget.gdpr.
Token lifecycle
- TTL is 7 days for signup tokens (
/v1/auth/signup); tokens minted via/v1/auth/tokensuse the caller-specifiedttl_seconds(default 1 hour). - No renew endpoint — call
/v1/auth/signupagain for a fresh anonymous identity. (To migrate data to a permanent account, see the Pricing page; the bridge from anonymous to permanent is an account upgrade, not a token renewal.) - The server stamps every authenticated response with
X-Cortex-Token-Expires-In(seconds remaining) andX-Cortex-Token-Expires-At(RFC 3339). AWarning: 199header fires when ≤72 h remain — read it on every response and refresh ahead of time. - Also useful:
client.whoami()returns the same expiry plus the effective capability set.
Don't bury the token. A week sounds long until your app embeds an anonymous-signup token in a saved-state blob and ships it. Two patterns that survive that:
- Refresh on 401. Catch
V1AuthError/V1Errorwitherror_code == "TOKEN_EXPIRED"once, callV1Client.signup()(or your IdP equivalent), retry the original call. Don't loop more than once — if the second call also 401s, surface the error.- Refresh ahead of time. Persist
expires_atnext to the token. WhenX-Cortex-Token-Expires-Indrops under ~86 400 (one day) or theWarning: 199header appears, refresh in the background before the next user-facing call.Service accounts on
/v1/auth/tokensare short-lived by design (default 1 hour) — the expected pattern is "mint per session" or "mint per job," not "mint once at boot and cache forever."
POST /v1/auth/tokens — Mint a token
Capability: auth.mint (typically granted only to server-side service accounts, IdP bridges, and the dashboard's BFF — not present on free-tier anonymous tokens).
Short-lived PASETO v4 public tokens for M2M flows, SDK examples, and bootstrapping. The in-binary minter is a dev convenience, not a production issuer: on self-hosted servers it is enabled only under dev_local with no CORTEX_API_KEY set (setting a key disables it unless CORTEX_V1_MINTER_ENABLE=1). Production token issuance should come from an external OIDC provider or the separate cortex-auth-ref issuer.
{
"subject": "user:alice",
"ttl_seconds": 3600,
"scopes": ["org:acme/user:alice"]
}
| Field | Type | Notes |
|---|---|---|
subject | string | Required. Sets sub on the minted token. |
ttl_seconds | int | Default 3600. Hard ceiling: tenant-configured (typ. 86400). |
scopes | string[] | Limits scopes claim on the minted token. Optional; if omitted, the token inherits the caller's scope set. |
Response
{
"token": "v4.public.eyJpc3MiOi...",
"expires_at": "2026-05-15T11:42:00Z"
}
Use the returned token as Authorization: Bearer <token> on subsequent calls.
GET /v1/auth/whoami
Identity introspection. Cheap healthcheck for "is my token still valid?"
Response
{
"caller": "user:alice",
"tenant_id": "acme",
"deployment_preset": "cloud_shared_saas",
"token": {
"jti": "j_01HX...",
"iss": "https://idp.acme.com/realms/main",
"exp": 1763212929
},
"effective_capabilities": [
"scope.read.local",
"scope.read.holistic",
"scope.write",
"forget.cascade.derived_only"
]
}
The effective_capabilities field is the intersection of the token's claimed caps and what the deployment + tenant + scope tiers allow. It's the authoritative answer to "what can this token do right now."
POST /v1/auth/revoke
Add a token JTI to the revocation list. Subsequent calls with that token return 401.
{
"jti": "j_01HX...",
"reason": "Token leaked in support ticket #4421"
}
Returns 204. Revocation propagates across all server nodes within the configured cluster gossip interval (default 5 s).
Error semantics
| HTTP | error_code | When |
|---|---|---|
| 401 | MISSING_TOKEN | No Authorization header on a non-public route |
| 401 | INVALID_TOKEN_SIGNATURE | Token didn't verify against any registered issuer |
| 401 | TOKEN_EXPIRED | Token exp is in the past |
| 401 | actor_mismatch | X-Cortex-Actor doesn't match the token's sub |
| 401 | WRONG_TENANT | Token's aud doesn't match the deployment's tenant binding |
| 403 | policy_denied | Token verified but missing the required capability — response cites tier + capability |