CortexDB Docs
Docs for CortexDB 0.15.2, the version running on CortexDB Cloud (api.cortexdb.ai).
API Reference

Review

Hold candidate memories for a person or a service to approve before CortexDB stores them — the review queue and its decisions.

GET
/v1/review
AuthorizationBearer <token>

Authorization: Bearer . Four credentials authenticate: (1) the operator key, the static CORTEX_API_KEY: the trusted operator, acting as the principal X-Cortex-Actor names (default user:local), in billing tenant default; (2) a PASETO v4 public token this server's minter signed (POST /v1/auth/tokens, POST /v1/auth/signup, the mint_offline tool), of audience CORTEX_V1_AUDIENCE or, with CORTEX_V1_TENANT_AUDIENCES=1, a billing tenant's own cortexdb:tenant:; (3) since 0.14.0 (principal_chain_v1), a JWT (RS256 or ES256) or PASETO v4 public token of an issuers-file entry (CORTEX_AUTH_ISSUERS_FILE; [auth] issuers_file on a cluster data node), whose subject, audience and token id (:) the entry maps; (4) a commitment transport token, on /v1/memory/commitments/* only. HS256, none and every symmetric JWT algorithm are refused. With auth disabled (no operator key: CORTEX_API_KEY unset on a loopback bind, or CORTEX_INSECURE_NO_AUTH) no token is verified and every request is served as the trusted operator, as the X-Cortex-Actor it names or user:local. On the operator key, with auth disabled and in the unsigned development mode, an X-Cortex-Actor that is not a principal id is 400 INVALID_HEADER, details {part: header, field: X-Cortex-Actor, reason: the parse error} (since 0.14.0; before, 401 INVALID_BODY). X-Cortex-Actor must equal the token's sub; a token of an issuers-file entry in prefix, hash or directory mode and a call of /mcp may leave it out (when present it must match). Any other token sent without it is verified first: a bearer of no known token form (a mistyped operator key) is 401 MISSING_TOKEN, any other bad token gets its own refusal, as with the header, and only a token that verifies answers 401 ACTOR_MISMATCH, the header is required. Every authenticated response carries X-Cortex-Token-Expires-In and X-Cortex-Token-Expires-At (and a Warning header within 72 h of expiry); every error of the auth layer carries X-Cortex-Request-ID.

Authentication refusals, the same on every authenticated route, none retriable unless marked. A token that carries none of the principal-chain claims and was not signed by an issuers-file entry gets each code with no details, as in 0.13.x; an entry's token, and a token that carries a chain claim other than minter, gets details.issuer (the entry's id, minter, commitment or unknown) and the details named here. 401 MISSING_TOKEN: no Authorization: Bearer header, or a bearer that is neither the operator key nor a PASETO v4 public or JWT token (a wrong operator key), with or without X-Cortex-Actor. 401 INVALID_TOKEN: a PASETO token no registered key verifies, a token signed by a key other than the issuer its iss names included. 401 INVALID_TOKEN_SIGNATURE: a bad signature, an iss no issuer is registered under, a JWT algorithm other than RS256 or ES256, a malformed token, or, on a token without chain claims, an iat ahead of the clock; for an issuers-file entry's token details.reason issuer_disabled, token_type, critical_header, algorithm, kid_revoked or unknown_kid (none for a bad signature). 401 EXPIRED_TOKEN. 401 REVOKED_TOKEN: the token's id is revoked (an entry's token goes by :). 401 WRONG_TENANT: an aud the token's issuer may not name, or one that names no tenant. 401 INVALID_TOKEN_CLAIM (since 0.14.0), details.claim and details.reason: a principal-chain claim malformed (malformed, not_an_agent, same_as_sub, not_a_service, person_with_agent, run_start_without_run, after_iat, run_too_old, out_of_range; unknown_critical_claim, with details.critical); an issuers-file entry's token without exp, iat or jti (missing), with iat or nbf ahead of the clock (ahead), or living longer than the entry's max_lifetime_s (claim exp, lifetime_over_maximum, details.lifetime_s, details.ceiling_s); a token this server's minter signed whose exp - iat is above the ceiling for its subject as this server runs it (CORTEX_MINT_MAX_LIFETIME_PERSON, _AGENT and _SERVICE since 0.14.1; by default 7,776,000 s for a user:, 604,800 s for an agent:, 2,592,000 s for a service: or system:) (claim exp, lifetime_over_ceiling, details.issuer minter, details.lifetime_s, details.ceiling_s; checked before the revocation, and counted instead while CORTEX_MINT_LIFETIME_ENFORCE=off); and, whatever issuer signed it, a token whose act names an AI employee of its tenant, the self of an employee record that is not delegable (claim act, act_not_delegable). 401 CLAIM_NOT_PERMITTED (since 0.14.0), details.claim: a chain claim the token's issuer is not bound to assert (an issuers-file entry's [issuer.claims]; minter from any issuer but this server's minter; the minter's own chain claims without the chain assertion), or an issuers-file entry's scopes entry outside its token's tenant root org: (claim scopes, details.entry). 401 SUBJECT_UNMAPPED (since 0.14.0), details.reason: an issuers-file entry's subject mapping produced no principal (missing, not_a_principal, kind, service, foreign_namespace, personal_data_in_id, malformed, too_long, not_in_directory, directory_not_here); and, on every route whatever issuer signed the credential, a principal whose entry in its tenant's directory (org:) is deactivated (deactivated; never the trusted operator). 410 TENANT_DELETED (since 0.14.0): the credential's tenant root, org:, is being deleted or was (POST /v1/erasures/tenant); never the trusted operator. 503 REVOCATION_UNAVAILABLE (retriable): the revocation list cannot answer, so every token is refused as an outage, never 401 REVOKED_TOKEN, and the list still fails closed: on a cluster data node while the node's revocation lease has expired (the metadata group has not confirmed its revocation set for 10 s, or the node has just started), and on a single node once the list's journal failed (a refused write latches the list). 503 ISSUER_KEYS_UNAVAILABLE (retriable): an issuers-file entry's JWKS cannot be fetched and no pinned or cached key verifies the token; Retry-After is the whole seconds until the key cache's next fetch (its backoff, 3 s doubling to 60 s; 3 s before the first fetch), at least 1, also in details.retry_after_seconds. 503 WAL_UNAVAILABLE (retriable): the directory did not read, so a directory-mode subject's principal, or a principal's deactivation, is unknown.

Authorization refusals any route gives where it checks a capability or a roster, none retriable. 403 POLICY_DENIED, details.capability and details.denied_by_tier (deployment, tenant, actor or scope): the deployment's or the tenant's policy denies the capability, the token's caps leave it out, a scopes entry names it but not at this scope, or the scope's roster does not admit the caller. Since 0.14.0 a token that carries scopes holds only the capabilities an entry names (: its subtree, ! that one scope, :* every scope and the checks made without one); any other is refused with details.link confinement, and counted instead while CORTEX_TOKEN_CONFINEMENT_ENFORCE=off. An agent chain token (one that carries act or run, or app or client resolving to agent) is confined so whatever the switch, and without scopes holds no capability. A tenant-audience token's :* entry at a scope outside its own org: root is details.link tenant. A token another principal minted (minter a principal other than its sub) also needs that minter to hold the capability under the deployment's and the tenant's policy and the role on the scope's roster: details.link minter, denied_by_tier scope, counted instead while CORTEX_MINTER_LINK_ENFORCE=off. A read whose credential stops being current while it runs (expired or revoked, or its issuers-file entry disabled or reloaded without its audience) is refused 403 POLICY_DENIED before it delivers. 403 APP_NOT_ADMITTED (since 0.14.0): in a tenant whose root sets apps_enforced, a chain whose application (app, or unlisted) is not admitted at the scope. 403 RUN_REQUIRED (since 0.14.0), details.scope: on the write routes (POST /v1/experience, each item of /v1/experience/bulk and /v1/experiences, each JSONL import line, POST /v1/beliefs), a token with act and no run.

The operator key under CORTEX_OPERATOR_DATA_READS (since 0.14.0, operator_break_glass_v1): allow, audit or deny; unset, deny on the cloud_shared_saas preset and allow on the other presets and with auth disabled; a tenant's own mode (PUT /v1/admin/access/tenant-settings) overrides it in that tenant. 403 BREAK_GLASS_REQUIRED, details {mode, class, route}: under deny, without a well-formed X-Cortex-Break-Glass, an operator-key action of class stored_rows (a route that returns stored rows, or a forget or erasure preview whose selector names about_subject, about_entity or predicate), admin_content (a content-returning admin route), private_region (a read whose scope or extra_scopes lie in a private region) or in_band (a mint of a token that carries a data capability, can mint, carries a registry-write capability or is for an Owner of a private region; a registration, an auto-provisioning first write included; a roster or tree edit that widens a private region or adds a service: principal; a write into a private region or a shared org-tree scope; a migration out of a private region; lowering a tenant's mode from deny; a deployment minter's mint of a token that carries a data capability). 400 INVALID_HEADER, in every mode: that header malformed, sent twice or longer than 512 bytes, details {part: header, field: X-Cortex-Break-Glass, reason: what is wrong with it}. Both refusals write the request's audit row as decision deny, each with its denial (check operator_access, path request#x-cortex-break-glass, decided_by_tier request; reason the 403's class, or break_glass_invalid for the 400, which names no scope), within the operator key's bound (its billing tenant and X-Cortex-Actor) (AuditRow). Its form: reason=incident|legal|support|security|migration|restore|other; ticket=1 to 64 characters of [A-Za-z0-9._:-]; optionally note=percent-encoded UTF-8 of at most 256 bytes; no other key, separated by semicolons. It is read only on the operator key's requests and on a deployment minter's POST /v1/auth/tokens, and ignored on every other credential; a cross-origin browser page cannot send it. 503 NOTICE_UNAVAILABLE: under deny, a header-bearing read of a private region whose notice to the region's person did not commit, so nothing was delivered: details.reason moving, limit, members_behind (Retry-After: 30) or refused (details.refusal), each retriable; arming_disabled and reserved_path_in_use, not retriable. A by-id read's notice is its row's region's, committed once the row is read (since 0.14.0; scope_unnamed is gone).

Before authentication, a build that enforces licenses answers every data route 402 LICENSE_EXPIRED, or 402 LICENSE_REQUIRED when it never held a license, not retriable; health, version, ready, /metrics, the published contract (the OpenAPI document, GET /v1/schemas and /v1/schemas/{name}), the console, every GET /v1/admin/* route, GET /v1/auth/whoami, POST /v1/export and, since 0.14.0, deleting data (POST /v1/forget and its preview, POST /v1/erasures and its preview, /v1/erasures/person, /v1/erasures/tenant, /v1/erasures/{id}/cancel, and GET /v1/erasures/{id}, /verify and /receipt) pass. Not an authentication refusal, and answered only where a write needs a newer shard apply format than the node's shard is armed for (change sets, application records, directory and SCIM writes, a person's erasure and a tenant's deletion, writes that carry atoms or provenance, rehomes and watermarks, a declared belief with provenance): 503 FORMAT_UPGRADE_PENDING, details {reason, format, node, paths, count} (reason, format and node alone on the directory, SCIM, rehome and watermark writes; reason alone on an application record or a reference root), retriable with Retry-After: 30 for members_behind only (arming_disabled and reserved_path_in_use wait for the operator). A plain node arms for format 10 at its first boot of 0.14.0 under the default ceiling, one way, and then never answers it.

In: header

Query Parameters

scope*string

The scope to read (422 INVALID_SCOPE_GRAMMAR when it does not parse); its root routes the request on a cluster.

state?"open"
Default"open"

Value in

  • "open"
topic?string
rule?string
descend?boolean

true adds the authorized descendants' items; needs scope.read.descend.

Defaultfalse
cursor?string

The previous page's next_cursor (400 INVALID_QUERY for any other value).

limit?integer
Range1 <= value <= 200
Default50

Header Parameters

X-Cortex-Break-Glass?string

operator_break_glass_v1 (API reference §8.15.3): read only on a request the operator key (CORTEX_API_KEY) authenticates, and on a deployment minter's POST /v1/auth/tokens; ignored on every other credential. reason=<incident|legal|support|security|migration|restore|other>; ticket=<1 to 64 of [A-Za-z0-9._:-]>[; note=<percent-encoded UTF-8, at most 256 bytes>], exactly one header of at most 512 bytes; any other form is 400 INVALID_HEADER in every mode (details {part: header, field: X-Cortex-Break-Glass, reason}), and the request's audit row a deny. Under CORTEX_OPERATOR_DATA_READS=deny (the deployment's, or the tenant's own) an operator-key request that reads stored rows, a content-returning admin route, a private region's read, or an in-band path needs it, else 403 BREAK_GLASS_REQUIRED (details {mode, class, route}). A well-formed header is written to the audit row as {reason, ticket}, never the note.

Lengthlength <= 512
X-Cortex-Subject-Token?string

subject_token_shadow_v1: on a request the operator key authenticates, the token of the principal the operator acts for, verified by the same verifier (without waiting on the network and without the X-Cortex-Actor step) and evaluated in shadow by the access report (GET /v1/admin/access/report). It never changes the response: a repeated, oversized (over 8,192 bytes) or invalid header, or the operator key itself, is recorded as the shadow's invalid_token and the request runs as sent.

Lengthlength <= 8192

Response Body

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/v1/review?scope=string"
{  "items": [    {      "held_id": "hld_0196f2a1c3d47e8f9a0b1c2d3e4f5a6b",      "event_id": "evt_0196f2a0c1d27b3e9a4f5d6c7b8a9e02",      "scope": "org:acme/clinic:north/user:p17",      "candidate": {        "kind": "triple",        "subject": "patient",        "predicate": "has_condition",        "object": "asthma",        "quote": "my asthma flared up",        "topic": "health",        "confidence": 1      },      "rule": "review.topic.health",      "policy_hash": "dp1:4be1c0d2e3f405162738495a6b7c8d9e",      "held_at": "2026-11-07T10:05:00Z",      "lapse_at": "2026-12-07T10:05:00Z",      "source": {        "snippet": "…my asthma flared up again last night…",        "observed_at": "2026-11-07T10:04:51Z"      }    }  ],  "open": 14,  "next_cursor": "1762509900000000:hld_0196f2a1c3d47e8f9a0b1c2d3e4f5a6b",  "has_more": true}
POST
/v1/review/decisions
AuthorizationBearer <token>

Authorization: Bearer . Four credentials authenticate: (1) the operator key, the static CORTEX_API_KEY: the trusted operator, acting as the principal X-Cortex-Actor names (default user:local), in billing tenant default; (2) a PASETO v4 public token this server's minter signed (POST /v1/auth/tokens, POST /v1/auth/signup, the mint_offline tool), of audience CORTEX_V1_AUDIENCE or, with CORTEX_V1_TENANT_AUDIENCES=1, a billing tenant's own cortexdb:tenant:; (3) since 0.14.0 (principal_chain_v1), a JWT (RS256 or ES256) or PASETO v4 public token of an issuers-file entry (CORTEX_AUTH_ISSUERS_FILE; [auth] issuers_file on a cluster data node), whose subject, audience and token id (:) the entry maps; (4) a commitment transport token, on /v1/memory/commitments/* only. HS256, none and every symmetric JWT algorithm are refused. With auth disabled (no operator key: CORTEX_API_KEY unset on a loopback bind, or CORTEX_INSECURE_NO_AUTH) no token is verified and every request is served as the trusted operator, as the X-Cortex-Actor it names or user:local. On the operator key, with auth disabled and in the unsigned development mode, an X-Cortex-Actor that is not a principal id is 400 INVALID_HEADER, details {part: header, field: X-Cortex-Actor, reason: the parse error} (since 0.14.0; before, 401 INVALID_BODY). X-Cortex-Actor must equal the token's sub; a token of an issuers-file entry in prefix, hash or directory mode and a call of /mcp may leave it out (when present it must match). Any other token sent without it is verified first: a bearer of no known token form (a mistyped operator key) is 401 MISSING_TOKEN, any other bad token gets its own refusal, as with the header, and only a token that verifies answers 401 ACTOR_MISMATCH, the header is required. Every authenticated response carries X-Cortex-Token-Expires-In and X-Cortex-Token-Expires-At (and a Warning header within 72 h of expiry); every error of the auth layer carries X-Cortex-Request-ID.

Authentication refusals, the same on every authenticated route, none retriable unless marked. A token that carries none of the principal-chain claims and was not signed by an issuers-file entry gets each code with no details, as in 0.13.x; an entry's token, and a token that carries a chain claim other than minter, gets details.issuer (the entry's id, minter, commitment or unknown) and the details named here. 401 MISSING_TOKEN: no Authorization: Bearer header, or a bearer that is neither the operator key nor a PASETO v4 public or JWT token (a wrong operator key), with or without X-Cortex-Actor. 401 INVALID_TOKEN: a PASETO token no registered key verifies, a token signed by a key other than the issuer its iss names included. 401 INVALID_TOKEN_SIGNATURE: a bad signature, an iss no issuer is registered under, a JWT algorithm other than RS256 or ES256, a malformed token, or, on a token without chain claims, an iat ahead of the clock; for an issuers-file entry's token details.reason issuer_disabled, token_type, critical_header, algorithm, kid_revoked or unknown_kid (none for a bad signature). 401 EXPIRED_TOKEN. 401 REVOKED_TOKEN: the token's id is revoked (an entry's token goes by :). 401 WRONG_TENANT: an aud the token's issuer may not name, or one that names no tenant. 401 INVALID_TOKEN_CLAIM (since 0.14.0), details.claim and details.reason: a principal-chain claim malformed (malformed, not_an_agent, same_as_sub, not_a_service, person_with_agent, run_start_without_run, after_iat, run_too_old, out_of_range; unknown_critical_claim, with details.critical); an issuers-file entry's token without exp, iat or jti (missing), with iat or nbf ahead of the clock (ahead), or living longer than the entry's max_lifetime_s (claim exp, lifetime_over_maximum, details.lifetime_s, details.ceiling_s); a token this server's minter signed whose exp - iat is above the ceiling for its subject as this server runs it (CORTEX_MINT_MAX_LIFETIME_PERSON, _AGENT and _SERVICE since 0.14.1; by default 7,776,000 s for a user:, 604,800 s for an agent:, 2,592,000 s for a service: or system:) (claim exp, lifetime_over_ceiling, details.issuer minter, details.lifetime_s, details.ceiling_s; checked before the revocation, and counted instead while CORTEX_MINT_LIFETIME_ENFORCE=off); and, whatever issuer signed it, a token whose act names an AI employee of its tenant, the self of an employee record that is not delegable (claim act, act_not_delegable). 401 CLAIM_NOT_PERMITTED (since 0.14.0), details.claim: a chain claim the token's issuer is not bound to assert (an issuers-file entry's [issuer.claims]; minter from any issuer but this server's minter; the minter's own chain claims without the chain assertion), or an issuers-file entry's scopes entry outside its token's tenant root org: (claim scopes, details.entry). 401 SUBJECT_UNMAPPED (since 0.14.0), details.reason: an issuers-file entry's subject mapping produced no principal (missing, not_a_principal, kind, service, foreign_namespace, personal_data_in_id, malformed, too_long, not_in_directory, directory_not_here); and, on every route whatever issuer signed the credential, a principal whose entry in its tenant's directory (org:) is deactivated (deactivated; never the trusted operator). 410 TENANT_DELETED (since 0.14.0): the credential's tenant root, org:, is being deleted or was (POST /v1/erasures/tenant); never the trusted operator. 503 REVOCATION_UNAVAILABLE (retriable): the revocation list cannot answer, so every token is refused as an outage, never 401 REVOKED_TOKEN, and the list still fails closed: on a cluster data node while the node's revocation lease has expired (the metadata group has not confirmed its revocation set for 10 s, or the node has just started), and on a single node once the list's journal failed (a refused write latches the list). 503 ISSUER_KEYS_UNAVAILABLE (retriable): an issuers-file entry's JWKS cannot be fetched and no pinned or cached key verifies the token; Retry-After is the whole seconds until the key cache's next fetch (its backoff, 3 s doubling to 60 s; 3 s before the first fetch), at least 1, also in details.retry_after_seconds. 503 WAL_UNAVAILABLE (retriable): the directory did not read, so a directory-mode subject's principal, or a principal's deactivation, is unknown.

Authorization refusals any route gives where it checks a capability or a roster, none retriable. 403 POLICY_DENIED, details.capability and details.denied_by_tier (deployment, tenant, actor or scope): the deployment's or the tenant's policy denies the capability, the token's caps leave it out, a scopes entry names it but not at this scope, or the scope's roster does not admit the caller. Since 0.14.0 a token that carries scopes holds only the capabilities an entry names (: its subtree, ! that one scope, :* every scope and the checks made without one); any other is refused with details.link confinement, and counted instead while CORTEX_TOKEN_CONFINEMENT_ENFORCE=off. An agent chain token (one that carries act or run, or app or client resolving to agent) is confined so whatever the switch, and without scopes holds no capability. A tenant-audience token's :* entry at a scope outside its own org: root is details.link tenant. A token another principal minted (minter a principal other than its sub) also needs that minter to hold the capability under the deployment's and the tenant's policy and the role on the scope's roster: details.link minter, denied_by_tier scope, counted instead while CORTEX_MINTER_LINK_ENFORCE=off. A read whose credential stops being current while it runs (expired or revoked, or its issuers-file entry disabled or reloaded without its audience) is refused 403 POLICY_DENIED before it delivers. 403 APP_NOT_ADMITTED (since 0.14.0): in a tenant whose root sets apps_enforced, a chain whose application (app, or unlisted) is not admitted at the scope. 403 RUN_REQUIRED (since 0.14.0), details.scope: on the write routes (POST /v1/experience, each item of /v1/experience/bulk and /v1/experiences, each JSONL import line, POST /v1/beliefs), a token with act and no run.

The operator key under CORTEX_OPERATOR_DATA_READS (since 0.14.0, operator_break_glass_v1): allow, audit or deny; unset, deny on the cloud_shared_saas preset and allow on the other presets and with auth disabled; a tenant's own mode (PUT /v1/admin/access/tenant-settings) overrides it in that tenant. 403 BREAK_GLASS_REQUIRED, details {mode, class, route}: under deny, without a well-formed X-Cortex-Break-Glass, an operator-key action of class stored_rows (a route that returns stored rows, or a forget or erasure preview whose selector names about_subject, about_entity or predicate), admin_content (a content-returning admin route), private_region (a read whose scope or extra_scopes lie in a private region) or in_band (a mint of a token that carries a data capability, can mint, carries a registry-write capability or is for an Owner of a private region; a registration, an auto-provisioning first write included; a roster or tree edit that widens a private region or adds a service: principal; a write into a private region or a shared org-tree scope; a migration out of a private region; lowering a tenant's mode from deny; a deployment minter's mint of a token that carries a data capability). 400 INVALID_HEADER, in every mode: that header malformed, sent twice or longer than 512 bytes, details {part: header, field: X-Cortex-Break-Glass, reason: what is wrong with it}. Both refusals write the request's audit row as decision deny, each with its denial (check operator_access, path request#x-cortex-break-glass, decided_by_tier request; reason the 403's class, or break_glass_invalid for the 400, which names no scope), within the operator key's bound (its billing tenant and X-Cortex-Actor) (AuditRow). Its form: reason=incident|legal|support|security|migration|restore|other; ticket=1 to 64 characters of [A-Za-z0-9._:-]; optionally note=percent-encoded UTF-8 of at most 256 bytes; no other key, separated by semicolons. It is read only on the operator key's requests and on a deployment minter's POST /v1/auth/tokens, and ignored on every other credential; a cross-origin browser page cannot send it. 503 NOTICE_UNAVAILABLE: under deny, a header-bearing read of a private region whose notice to the region's person did not commit, so nothing was delivered: details.reason moving, limit, members_behind (Retry-After: 30) or refused (details.refusal), each retriable; arming_disabled and reserved_path_in_use, not retriable. A by-id read's notice is its row's region's, committed once the row is read (since 0.14.0; scope_unnamed is gone).

Before authentication, a build that enforces licenses answers every data route 402 LICENSE_EXPIRED, or 402 LICENSE_REQUIRED when it never held a license, not retriable; health, version, ready, /metrics, the published contract (the OpenAPI document, GET /v1/schemas and /v1/schemas/{name}), the console, every GET /v1/admin/* route, GET /v1/auth/whoami, POST /v1/export and, since 0.14.0, deleting data (POST /v1/forget and its preview, POST /v1/erasures and its preview, /v1/erasures/person, /v1/erasures/tenant, /v1/erasures/{id}/cancel, and GET /v1/erasures/{id}, /verify and /receipt) pass. Not an authentication refusal, and answered only where a write needs a newer shard apply format than the node's shard is armed for (change sets, application records, directory and SCIM writes, a person's erasure and a tenant's deletion, writes that carry atoms or provenance, rehomes and watermarks, a declared belief with provenance): 503 FORMAT_UPGRADE_PENDING, details {reason, format, node, paths, count} (reason, format and node alone on the directory, SCIM, rehome and watermark writes; reason alone on an application record or a reference root), retriable with Retry-After: 30 for members_behind only (arming_disabled and reserved_path_in_use wait for the operator). A plain node arms for format 10 at its first boot of 0.14.0 under the default ceiling, one way, and then never answers it.

In: header

Header Parameters

X-Cortex-Break-Glass?string

operator_break_glass_v1 (API reference §8.15.3): read only on a request the operator key (CORTEX_API_KEY) authenticates, and on a deployment minter's POST /v1/auth/tokens; ignored on every other credential. reason=<incident|legal|support|security|migration|restore|other>; ticket=<1 to 64 of [A-Za-z0-9._:-]>[; note=<percent-encoded UTF-8, at most 256 bytes>], exactly one header of at most 512 bytes; any other form is 400 INVALID_HEADER in every mode (details {part: header, field: X-Cortex-Break-Glass, reason}), and the request's audit row a deny. Under CORTEX_OPERATOR_DATA_READS=deny (the deployment's, or the tenant's own) an operator-key request that reads stored rows, a content-returning admin route, a private region's read, or an in-band path needs it, else 403 BREAK_GLASS_REQUIRED (details {mode, class, route}). A well-formed header is written to the audit row as {reason, ticket}, never the note.

Lengthlength <= 512
X-Cortex-Subject-Token?string

subject_token_shadow_v1: on a request the operator key authenticates, the token of the principal the operator acts for, verified by the same verifier (without waiting on the network and without the X-Cortex-Actor step) and evaluated in shadow by the access report (GET /v1/admin/access/report). It never changes the response: a repeated, oversized (over 8,192 bytes) or invalid header, or the operator key itself, is recorded as the shadow's invalid_token and the request runs as sent.

Lengthlength <= 8192

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/v1/review/decisions" \  -H "Content-Type: application/json" \  -d '{    "scope": "org:acme/clinic:north",    "decisions": [      {        "held_id": "hld_0196f2a1c3d47e8f9a0b1c2d3e4f5a6b",        "decision": "approve",        "reason": "confirmed with patient"      },      {        "held_id": "hld_0197a2b3c4d5e6f708192a3b4c5d6e7f",        "decision": "reject",        "bind": "subject_predicate"      }    ]  }'
{  "results": [    {      "held_id": "hld_0196f2a1c3d47e8f9a0b1c2d3e4f5a6b",      "decision": "approved",      "decision_id": "dec_0196f3b0c1d27b3e9a4f5d6c7b8a9e05",      "fact_id": "fact_0196f3b0c1d27b3e9a4f5d6c7b8a9e06"    },    {      "held_id": "hld_0197a2b3c4d5e6f708192a3b4c5d6e7f",      "decision": "rejected",      "decision_id": "dec_0196f3b0c1d27b3e9a4f5d6c7b8a9e07",      "correction_id": "cor_0196f3b0c1d27b3e9a4f5d6c7b8a9e08"    },    {      "held_id": "hld_0198a2b3c4d5e6f708192a3b4c5d6e7f",      "error_code": "REVIEW_ITEM_DECIDED",      "message": "the held candidate was decided before",      "retriable": false,      "details": {        "decision_id": "dec_0196f3b0c1d27b3e9a4f5d6c7b8a9e09"      }    }  ]}

A derivation policy can hold a candidate memory for review instead of storing it. A held candidate is in no store and no index: no recall, answer, fact or belief read sees it until a reviewer approves it.

Needs capability derivation_review_v1; experimental. Both routes need data format 11 (503 FORMAT_UPGRADE_PENDING until then).

What is held

A policy holds a candidate when:

  • its topic or field is in review.hold.topics or review.hold.predicates;
  • the policy's mode is review (every candidate is held);
  • a writer rule refuses it and review.hold.refused_writers is on;
  • the model's confidence is below review.hold.confidence_below.

Each new held candidate is announced on the lifecycle stream as derivation_held, with its ids, rule and topic, never its words.

Reviewers

A reviewer is a Writer of the scope, or a principal (or a member of a set) the policy names in review.reviewers, with Reader on the scope, so a service can review too. Reading the queue needs scope.read.local; deciding needs scope.write.

Read the queue

GET /v1/review?scope=… lists the open candidates held at exactly the scope, oldest first. descend=true adds those of the scopes below it the reviewer may read. Filter by topic or rule.

curl -sS "$CORTEX_URL/v1/review?scope=org:acme/clinic:north&topic=health&limit=20" \
  -H "Authorization: Bearer $CORTEX_TOKEN"
{"items": [{"held_id": "hld_0196f2a1c3d47e8f9a0b1c2d3e4f5a6b", "event_id": "evt_0196f2a0…",
            "scope": "org:acme/clinic:north",
            "candidate": {"kind": "triple", "subject": "patient", "predicate": "has_condition",
                          "object": "asthma", "quote": "my asthma flared up", "topic": "health",
                          "confidence": 1},
            "rule": "review.topic.health", "policy_hash": "dp1:4be1c0d2e3f405162738495a6b7c8d9e",
            "held_at": "2026-11-07T10:05:00Z", "lapse_at": "2026-12-07T10:05:00Z",
            "source": {"snippet": "…my asthma flared up again last night…",
                       "observed_at": "2026-11-07T10:04:51Z"}}],
 "open": 14, "next_cursor": "…", "has_more": true}

source is the event's text around the candidate's quote, so the reviewer can judge it in context. An item is served only to a reviewer who may read its event; open counts every item the filters select.

Decide

POST /v1/review/decisions decides 1 to 100 candidates at once. Each decision stands alone: one refusal never stops the others, and each result answers its own.

curl -sS -X POST "$CORTEX_URL/v1/review/decisions" \
  -H "Authorization: Bearer $CORTEX_TOKEN" -H "Content-Type: application/json" \
  -d '{"scope": "org:acme/clinic:north", "decisions": [
        {"held_id": "hld_0196f2a1c3d47e8f9a0b1c2d3e4f5a6b", "decision": "approve", "reason": "confirmed with patient"},
        {"held_id": "hld_0197a2b3c4d5e6f708192a3b4c5d6e7f", "decision": "reject", "bind": "subject_predicate"}]}'
{"results": [{"held_id": "hld_0196f2a1…", "decision": "approved",
              "decision_id": "dec_0196f3b0…", "fact_id": "fact_0196f3b0…"},
             {"held_id": "hld_0197a2b3…", "decision": "rejected",
              "decision_id": "dec_0196f3b0…", "correction_id": "cor_0196f3b0…"}]}
DecisionWhat happens
approveThe candidate becomes a fact on every replica (fact_id in the result), recallable from then on, and stamped review.approved.<decision id> under include=derivation. Approval works from the held candidate itself, so it still works after its event was sealed or expired
rejectThe candidate is erased and a correction (origin review) keeps its claim from being learned again. bind is claim (default) or subject_predicate

A candidate nobody decides lapses review.lapse_days after it was held (30 by default): it is erased and leaves the queue. A later re-extraction that states an approved claim again accepts it rather than holding it twice; one that states a rejected claim is suppressed.

A refused decision's result carries error_code: NOT_FOUND (no such held candidate you may see), REVIEW_ITEM_DECIDED (decided before: approved, rejected, lapsed or re-gated; details.decision_id), or PROVENANCE_ERASED (its event was erased meanwhile). A storage error is retriable: a retry answers REVIEW_ITEM_DECIDED when the first attempt committed.

Erasure and audit

A held candidate and its decision cite their event: erasing the event erases both, and an erasure's /verify counts any left (governed_records). A decision's reason is erased with it. Every decision writes an audit row (action: "review_decision") holding a hash of the result, never its words.

In the console

The admin console's Memory › Review page lists the queue, with a counter in the navigation, and approves or rejects one candidate at a time or in bulk.

On this page