CortexDB Docs
Docs for CortexDB 0.12.8, the version running on CortexDB Cloud (api.cortexdb.ai).
SDKs & Clients

Python SDK

Reference for cortexdbai — V1Client, AsyncV1Client, and the typed exception hierarchy.

The official Python client for CortexDB v1. V1Client is the full surface (50 methods); the async AsyncV1Client covers a subset of it — see Async variant before you build on it.

pip install cortexdbai

Requires Python 3.10+. PyPI package: cortexdbai. Python module: cortexdb.v1. The SDK version is independent of the server version; see Versioning and SDK compatibility.

Construction

from cortexdb.v1 import V1Client

client = V1Client(
    api_url="https://api-v1.cortexdb.ai",
    actor="user:alice",
    bearer="v4.public.eyJpc3MiOi...",
    timeout=30.0,
)
ArgumentDefaultNotes
api_urlhttp://localhost:3141v1 surface URL. Defaults to a local server; pass https://api-v1.cortexdb.ai for cloud.
actor"user:default"Sent as X-Cortex-Actor; must match the token's sub.
bearerNonePASETO v4 public token — or, self-hosted, the server's CORTEX_API_KEY value. Optional only when the server runs with CORTEX_INSECURE_NO_AUTH=1.
timeout60 sPer-request timeout.

Use it as a context manager so the underlying requests.Session is cleaned up:

with V1Client(api_url="...", actor="user:alice", bearer=tok) as client:
    pack = client.recall(scope="org:acme/user:alice")

Auth

client.whoami()
# → { caller, tenant_id, deployment_preset, effective_capabilities, token }

client.mint_token(
    subject="user:alice",
    ttl_seconds=3600,
    scopes=["org:acme/user:alice"],
)
# → { "token": "v4.public...", "expires_at": "2026-05-15T11:42:00Z" }

whoami key is effective_capabilities

The capability list is returned under effective_capabilities (snake_case). mint_token accepts scopes (plural) and caps alongside the singular scope.

Write path

client.experience(
    scope="org:acme/user:alice",
    text="Acme upgraded to 200 seats.",
    role="user",
    observed_at="2026-05-15T10:42:00Z",
    idempotency_key="alice-chat-001",
    wait=None,
)

experience() modality is optional

Pass the content with text=, json_data=, transcript=, blob_id=, or triple= (plus role=). modality= (an open-set label) and source= are optional keywords: if modality is omitted it is taken from source=, else defaults to conversation.

client.experience_bulk(
    scope="org:acme/user:alice",
    items=[
        {"text": "Acme upgraded to 200 seats.", "role": "user",
         "observed_at": "2026-05-15T10:42:00Z", "idempotency_key": "k1"},
        {"text": "Renewal is due in March.", "role": "user",
         "observed_at": "2026-05-15T10:43:00Z", "idempotency_key": "k2"},
    ],
)

experience_bulk() takes no ordering= parameter

experience_bulk(scope, items, *, wait=None) is the full signature — there is no ordering= keyword. Items are ingested in list order; encode temporal order in each item's observed_at. Each item is a flat dict with exactly one of text / json_data / blob_id / triple, plus optional role, observed_at, labels, idempotency_key, modality, source (not a wire envelope).

wait ∈ None | "accepted" | "captured" | "indexed" | "consolidated" ("accepted" is an explicit alias for the None default). With wait omitted, a write returns captured (202) as soon as the WAL append succeeds — it is not fully indexed yet; pass wait="indexed" to block until BM25 + HNSW have the event. For full envelope shape (subjects, directives, blob refs, triples) see Experience Envelope.

Read path

client.recall(
    scope="org:acme/user:alice",
    view="holistic",
    query="What did we decide about Acme?",
    include=["events", "beliefs", "facts", "episodes"],
    budgets={"max_tokens": 4000},
    temporal={"natural": "last 30 days"},
)
# → StratifiedPack: layers, context_block, provenance, diagnostics, pack_id

client.answer(
    scope="org:acme/user:alice",
    question="Did Acme renew?",
    view="holistic",
    temporal={"natural": "last 30 days"},
)
# → { answer, citations, provenance, diagnostics, pack_id, as_of }

answer_model is a cloud default

claude-opus-4-6 is the cloud default model. Self-hosted, /v1/answer routes through whatever you configure in the answer lane (CORTEX_ANSWER_*). An answer_model= argument is only echoed in diagnostics.answer_model; the configured model answers (details). /v1/answer is disabled on a self-hosted instance until that lane is set. See Self-hosting defaults.

Layer reads

client.events(scope="org:acme/user:alice", view="local", limit=50)
client.episodes(scope="org:acme/user:alice", view="local")
client.facts(scope="org:acme/user:alice", subject="ent_acme_corp", predicate="deal_stage")
client.beliefs(scope="org:acme/user:alice", view="local")
client.belief_why(belief_id="belief_01HX...")
client.understanding(scope="org:acme/user:alice", view="local")

facts() wraps subject/predicate only — no as_of=

facts(scope, *, view='local', subject=None, predicate=None) is the full signature. There is no as_of= keyword — passing one raises TypeError. For as-of / temporal fact queries call the REST endpoint directly (GET /v1/facts?as_of=…&min_confidence=…&include_superseded=…) or GET /v1/facts/timeline.

Facts / Beliefs need enrichment on self-host

On a content-only self-hosted instance the Facts and Beliefs layers stay empty — they are LLM-derived and require enrichment configured. events/episodes/recall work regardless. See Self-hosting defaults.

Trigger an async synthesis pass:

client.build_episodes(scope="org:acme/user:alice")
client.build_beliefs(scope="org:acme/user:alice")
client.synthesize(scope="org:acme/dept:eng", topics=["sales_process"])

synthesize() takes no force= parameter

synthesize(scope, *, topics=None) is the full signature — there is no force= keyword.

Forget

client.forget(
    scope="org:acme/user:alice",
    layers=["beliefs"],
    predicate="is_likely_to_renew",
    cascade="derived_only",
    reason="User retracted speculation",
)

forget() targets by field

forget() takes explicit targeting fields (there is no selector= dict or idempotency_key=):

forget(scope, *, layers=None, about_subject=None, about_entity=None,
       predicate=None, memory_ids=None, cascade='derived_only',
       confirm_all=False, reason=None, from_preview_id=None)

Use predicate=, about_subject=, about_entity=, or memory_ids=[…] — not selector={…}. The audit note field is reason (not audit_note), and idempotency_key is not accepted on forget().

To purge specific records, pass their ids:

client.forget(scope="org:acme/user:alice", memory_ids=["evt_01HX...", "fact_01HX..."])

If a storage backend fails mid-delete the server returns 502 FORGET_BACKEND_FAILED rather than reporting success — deletes are idempotent, so retry the same call. For GDPR reference-counted erasure use the /v1/erasures family directly — see Erasures.

Audit

client.audit_list(actor="user:alice", capability="forget.gdpr", limit=100)
client.audit_verify(audit_id="audit_01HX...", body="<canonicalized JSON>")

Admin

client.layer_stats()   # GET /v1/admin/layers/stats (experimental)

Exception hierarchy

All HTTP errors raise a subclass of cortexdb.v1.V1Error:

from cortexdb.v1 import (
    V1Error,                # base
    V1APIError,             # generic API error envelope
    V1AuthError,            # 401
    V1PolicyDeniedError,    # 403
    V1RateLimitError,       # 429
    V1NotConfiguredError,   # 503
    V1ConnectionError,      # network failure
    V1TimeoutError,         # timeout
)

try:
    client.experience(scope="...", text="...", observed_at="...", idempotency_key="...")
except V1PolicyDeniedError as e:
    print(f"denied: {e.error_code} — {e.message}")
    print(e.details)          # capability/tier context, when the server provides it
except V1RateLimitError:
    time.sleep(1.0)

API error attributes

V1APIError and its subclasses carry status, error_code, message, request_id, details, and retriable. Read policy context on V1PolicyDeniedError from error_code and details.

What 0.12.0 sees from the server

Checked with cortexdbai 0.12.0 against the current server (no client change is needed):

  • A wait="indexed" (or "consolidated") write that runs out of the server's wait budget raises V1APIError after about 30 s — before the client's own 60 s timeout — with status 408, error_code WAIT_TIMEOUT, retriable True and details.status_url (details.event_ids from experience_bulk). The write is durable: poll write_status, or re-send with the same idempotency_key (see Experience).
  • A forget (or erasure) that meets 409 AUTHORIZATION_STATE_CHANGED raises a plain V1APIError with retriable True, not V1PolicyDeniedError. Nothing was deleted; retry after a second.
  • There is no method for GET /v1/events/{id}, so its ?scope= filter has no SDK argument; call the route directly (see Events).

Async variant

AsyncV1Client is backed by httpx.AsyncClient and every method on it is async. It is not a drop-in mirror of V1Client.

from cortexdb.v1 import AsyncV1Client

async with AsyncV1Client(
    api_url="https://api-v1.cortexdb.ai",
    actor="user:alice",
    bearer=tok,
) as client:
    pack = await client.recall(scope="org:acme/user:alice", view="holistic")
    ans  = await client.answer(scope="org:acme/user:alice", question="Did Acme renew?")

The async client is a subset: 32 methods vs 53

The sync client has 53 methods and the async client 32. Twenty-two methods exist only on V1Client and raise AttributeError on the async client — notably every layer read and the retention / WAL operator methods:

events · facts · beliefs · episodes · understanding · belief_why · build_beliefs · build_episodes · synthesize · audit_list · audit_verify · index_audit · layer_stats · write_status · close · retention_policy_get · retention_policy_set · retention_policy_delete · retention_status · retention_sweep · wal_status · wal_compact

Close the async client with aclose() (there is no close()), or use async with as above. If you need the layer reads in an async program, call them on a V1Client in a thread executor or use the REST endpoints directly. Also note AsyncV1Client.recall() takes a narrower argument set than the sync one — it has no include=, budgets=, temporal= or filters= parameters (it does take language=, scripts= and query_variants=).

Method index

Capabilities, chunks and language fields

  • version() returns {version, crate_version, …, capabilities} and capabilities() returns just the capability list (GET /v1/admin/version). Gate every new request field on it: a server that doesn't list a capability refuses its fields with 422 INVALID_BODY.
  • event_chunks(event_id) shows how a long event was chunked (GET /v1/admin/events/{id}/chunks): chunk_count, chunker_version, chunks, language, scripts, source_blob_ids, stored_text_bytes.
  • iter_matched_fragments(pack) and fragment_text(text, fragment) (import from cortexdb.v1, not the package top level) walk a recall pack's matched_fragments and slice each span out of the event's text; an excerpted item yields no span.
  • experience() takes language=, scripts=, timezone= and refers_to=; recall() takes language=, scripts= and query_variants= — see Languages & Multilingual Memory, Experience and Recall.
  • delete_blob(blob_id, missing_ok=True) returns {…, "already_deleted": true} for a blob that is already gone instead of raising 404 NOT_FOUND.

Retention and WAL operator methods

retention_policy_get / retention_policy_set / retention_policy_delete / retention_status / retention_sweep / wal_status / wal_compact wrap the operator routes /v1/admin/retention, /v1/admin/retention/policies, /v1/admin/retention/sweep, /v1/admin/wal and /v1/admin/wal/compact. Expiry is irreversible erasure, so the server defaults dry_run to true on each of them. Sync client only.

The sections above cover the common paths. The full V1Client surface also includes these, several of which map to endpoints documented elsewhere in this reference:

AreaMethods
Composecompose → POST /v1/compose
Bi-temporal recordsquery_claims · claim_history · list_conflicts · get_conflict · resolve_conflict → Claims & Conflicts
Layer historybelief_history · concept_history
Erasureerase · erasure_preview · forget_preview → /v1/erasures
Importimport_data · import_status → /v1/import
Blobsupload_blob · delete_blob → /v1/blobs
Beliefs (write)create_belief
Vocabulariesupdate_vocabulary → /v1/vocabularies
Authsignup · mint_token(subject=, scope=, caps=, scopes=) · whoami → /v1/auth
Serverversion · capabilities → /v1/admin/version
Documentsevent_chunks → GET /v1/admin/events/{id}/chunks
Auditaudit_chain · audit_chain_verify
Write statuswrite_status — "did my write land?" by idempotency_key or event_id
Lifecycleclose (sync) · aclose (async)

On this page