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

GET /v1/events

List raw events — the append-only record that every other layer is derived from.

GET
/v1/events
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 path to list (422 INVALID_SCOPE_GRAMMAR when it does not parse).

view?string

local (the default), granular, raw and structured read the scope exactly (structured still needs scope.read.holistic); holistic adds the authorized ancestors (never descendants); descend reads the authorized subtree. A walk never enters a private region it did not start in. Any other value, lineage included, is 422 INVALID_BODY (invalid view); the value is case-sensitive.

Default"local"

Value in

  • "local"
  • "granular"
  • "raw"
  • "structured"
  • "holistic"
  • "descend"
limit?integer

Page size, default 50, clamped to 1..1000.

Range1 <= value <= 1000
Default50
cursor?string

Opaque (le1. and base64url): the next_cursor of the page before, bound to the query that issued it (scope, view, caller, filters); any other value is 422 INVALID_CURSOR (since 0.14.0; before, a decimal offset). Absent: the first page.

since?string

RFC 3339 (write a UTC offset as Z, or percent-encode +): keeps events whose context.recorded_at is at or after it.

Formatdate-time
observed_actor?string

Keeps events whose observed_actor is exactly this actor id (for example user:alice).

modality?string

Keeps events whose modality is exactly this value.

labels?string

Comma-separated label names, each trimmed; keeps events carrying any of them (exact match).

intent?string

memory_kinds_v1: comma-separated context.intent values (memory kinds such as handover or skill); lists only events whose intent is exactly one of them.

at_least?string

consistency_tokens_v1: a consistency token; the read first waits until this node has reached it (408 WAIT_TIMEOUT past CORTEX_CONSISTENCY_WAIT_MS; 422 CONSISTENCY_TOKEN_INVALID for one that cannot be this deployment's), and the page then carries consistency_token (and consistency_reset, derived_through) after its own fields. These GET routes take it the same way: /v1/events, /v1/events/{id}, /v1/derivation/status, /v1/episodes, /v1/facts, /v1/facts/timeline, /v1/facts/{id}, /v1/beliefs, /v1/beliefs/why, /v1/beliefs/history, /v1/understanding, /v1/understanding/coverage, /v1/understanding/{id} and its /history and /related, the /v1/concepts aliases of those, /v1/scopes, /v1/scopes/list and /v1/experience/status. No other GET route takes it (the conflict and claim reads, synthesis status, and the lifecycle and audit reads among them). The wait and the token are at ?scope='s root; a scope that does not parse skips both, and the route then refuses it. access_restored_at accompanies consistency_reset when the token predates a restore of the root.

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

application/json

application/json

application/json

curl -X GET "https://example.com/v1/events?scope=string"
{  "items": [    {      "id": "string",      "scope": "string",      "caller": "string",      "observed_actor": "string",      "subject": {        "id": "string",        "type": "user"      },      "scope_key": "string",      "modality": "string",      "source": {        "schema_version": 2,        "source_id": "string",        "modality": "string",        "content_kind": "string",        "language": "string",        "source_fingerprint": "string",        "fragments": [          {            "fragment_id": "string",            "locator": {              "kind": "conversation",              "session_id": "string",              "turn_index": 0,              "speaker": "string",              "message_id": "string"            },            "byte_range": {              "start": 0,              "end": 1            },            "content_fingerprint": "string"          }        ]      },      "content": null,      "context": {        "observed_at": "2019-08-24T14:15:22Z",        "recorded_at": "2019-08-24T14:15:22Z",        "source_recorded_at": "2019-08-24T14:15:22Z",        "location": {},        "preceded_by": [          "string"        ],        "intent": "string",        "labels": [          "string"        ],        "timezone": "string",        "refers_to": [          {}        ]      },      "derives": [        "string"      ],      "wal_offset": 0,      "excluded_from_recall": true,      "language": "string",      "scripts": [        "string"      ],      "language_source": "hint",      "source_blob_ids": [        "string"      ],      "extract": [        "facts"      ],      "matched_fragments": [        {          "chunk_id": "55e808e1-8ddc-49f1-92c9-6bbdcdff1c83",          "text_source": "event_text",          "byte_range": {            "start": 0,            "end": 0          },          "locator": {},          "language": "string",          "matched_via": "vector"        }      ],      "atoms": [        {          "scope": "string",          "record": "string",          "created_at": "2019-08-24T14:15:22Z"        }      ],      "provenance": [        "string"      ]    }  ],  "next_cursor": "string",  "has_more": true}
GET
/v1/events/{id}
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

Path Parameters

id*string

The event id, evt_ followed by 1 to 188 characters of [A-Za-z0-9_-] (any other value answers 404).

Query Parameters

scope?string

event_scope_filter_v1 (0.10.2): return the event only when its own scope is exactly this scope path; an ancestor or descendant scope does not match, and a mismatch answers the same 404 as a missing id. An empty or malformed value answers 422 INVALID_SCOPE_GRAMMAR. Without it the read is authorized against the event's own scope only, which the static operator key (CORTEX_API_KEY) and auth-disabled mode pass for every scope and every billing tenant. A server that does not list event_scope_filter_v1 ignores the parameter, so send it only when the name is listed. Also what finds a sealed event or tells that an event expired (410), and on a cluster what routes the read; since 0.14.0 the operator plane decides on the event's own scope once it is read, never on ?scope=.

at_least?string

consistency_tokens_v1: a consistency token from an earlier response, unchanged. Before the route authorizes and runs the read, this node waits until its registry has reached the token's revision and its indexer has indexed below its offset (408 WAIT_TIMEOUT past CORTEX_CONSISTENCY_WAIT_MS, default 10000, at most 30000; 422 CONSISTENCY_TOKEN_INVALID for a token that cannot be this deployment's). The root is ?scope='s, else the token's own; a ?scope= that does not parse skips the wait. A successful response then carries the ConsistencyFields (consistency_token; consistency_reset, derived_through and access_restored_at when they apply) after its own fields.

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

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/v1/events/string"
{  "id": "string",  "scope": "string",  "caller": "string",  "observed_actor": "string",  "subject": {    "id": "string",    "type": "user"  },  "scope_key": "string",  "modality": "string",  "source": {    "schema_version": 2,    "source_id": "string",    "modality": "string",    "content_kind": "string",    "language": "string",    "source_fingerprint": "string",    "fragments": [      {        "fragment_id": "string",        "locator": {          "kind": "conversation",          "session_id": "string",          "turn_index": 0,          "speaker": "string",          "message_id": "string"        },        "byte_range": {          "start": 0,          "end": 1        },        "content_fingerprint": "string"      }    ]  },  "content": null,  "context": {    "observed_at": "2019-08-24T14:15:22Z",    "recorded_at": "2019-08-24T14:15:22Z",    "source_recorded_at": "2019-08-24T14:15:22Z",    "location": {},    "preceded_by": [      "string"    ],    "intent": "string",    "labels": [      "string"    ],    "timezone": "string",    "refers_to": [      {}    ]  },  "derives": [    "string"  ],  "wal_offset": 0,  "excluded_from_recall": true,  "language": "string",  "scripts": [    "string"  ],  "language_source": "hint",  "source_blob_ids": [    "string"  ],  "extract": [    "facts"  ],  "matched_fragments": [    {      "chunk_id": "55e808e1-8ddc-49f1-92c9-6bbdcdff1c83",      "text_source": "event_text",      "byte_range": {        "start": 0,        "end": 0      },      "locator": {},      "language": "string",      "matched_via": "vector"    }  ],  "atoms": [    {      "scope": "string",      "record": "string",      "created_at": "2019-08-24T14:15:22Z"    }  ],  "provenance": [    "string"  ]}

Event item shape

Real item shape — id, subject object, nested context

Each event is { id, scope, caller, observed_actor, subject: { id, type }, scope_key, modality, content, context: { observed_at, recorded_at, intent, labels }, wal_offset }. Note: id (not event_id); subject is an object { id, type } (not a string); observed_at / recorded_at / labels / intent are nested under context (not top-level). This is the same shape recall and GET /v1/events/{id} return. Events written with a source descriptor carry it back verbatim as source; the key is absent otherwise.

An event can also carry language, scripts and language_source (a non-English or explicitly tagged event; an event that is English by default has none), context.timezone and context.refers_to (when the capture sent them), and source_blob_ids (the uploaded blob a blob_ref event was made from). See Experience.

scope_key is the billing partition the event was captured under — the capturing credential's billing tenant, default for the static operator key (CORTEX_API_KEY) — not the event's scope, which is scope.

Listing by kind: intent

GET /v1/events?scope=…&intent=handover,skill (capability memory_kinds_v1) lists only the events whose context.intent is exactly one of the comma-separated values. It is how you list every statement of a memory kind whose newest one recall serves with newest_of_kinds.

Reading one event in one scope

GET /v1/events/{id}?scope=<scope> (capability event_scope_filter_v1) returns the event only when its own scope is exactly <scope>:

  • An event stored in a sibling, an ancestor or a descendant of <scope> gets the same 404 NOT_FOUND body as an unknown id ("event not found"), so the filter never tells you where an event lives.
  • An empty, blank or malformed value (?scope=, ?scope=%20, ?scope=nocolon) → 422 INVALID_SCOPE_GRAMMAR. Sending scope twice → a plain-text 400 ("duplicate field scope"). Neither falls back to a read without the filter.
  • Without the parameter nothing changes. Use it with operator-wide credentials: the static operator key (CORTEX_API_KEY) and an auth-disabled server read every scope of every tenant by id.
  • A server without the capability ignores the parameter and returns the event. Send it only when capabilities[] lists event_scope_filter_v1, or compare the returned scope with the one you expect.
  • It applies to this route only; GET /v1/facts/{id} has no scope filter.

Behavior notes

Content is always included

GET /v1/events always returns each event's full content. To leave content out, use recall with exclude_content: true.

  • Envelope: { items: [...], next_cursor, has_more }; next_cursor is omitted on the last page. Items are newest first by the server's context.recorded_at, whatever their observed_at. Default view is local; the enum is local / granular / raw / structured / holistic / descend (anything else → 422).
  • Filters that work: labels (comma-separated, any-match), observed_actor, modality, and since (RFC 3339). since compares context.recorded_at (when the server stored the event), not observed_at: an event observed in 2020 but written today passes since=2024-01-01.
  • There is no until filter: to bound the end of a time range, filter on context.recorded_at on your side.
  • GET /v1/events/{id} returns a single event, always with its full text (even when a recall served it as an excerpt); a missing, unknown, forgotten or cancelled id → 404. Every recalled layers.events item carries the same caller, observed_actor, subject, scope_key, scope and wal_offset as this route (see Recall).
  • limit is clamped rather than rejected: limit=0 returns one item, and limit=5000 returns 200. A missing scope query param → 400 (query-string validation returns 400; body validation returns 422).
  • Embedding-provider outages: an event accepted while the embedding endpoint is down stays captured until the provider returns, and is then indexed automatically with no operator action. During an outage the indexer holds rather than drops, so ?wait=indexed waits until the provider is back or its wait budget runs out (408 WAIT_TIMEOUT after about 30 s; see Experience).

On this page