CortexDB Docs
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 (42 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 (current release: 0.11.2). Python module: cortexdb.v1. The SDK version is independent of the server version (this reference targets server v0.9.9) — 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() has no modality= parameter

The SDK infers modality from the content you pass — use text=, json_data=, transcript=, blob_id=, or triple= and pass role=. There is no modality= keyword on experience(); passing one raises TypeError. (The REST body does take a modality field — the modality lives on the wire envelope, not the SDK call.)

client.experience_bulk(
    scope="org:acme/user:alice",
    items=[
        {"modality": "conversation", "content": {...}, "context": {...}, "idempotency_key": "k1"},
        {"modality": "conversation", "content": {...}, "context": {...}, "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 context.observed_at.

waitNone | "accepted" | "captured" | "indexed" | "consolidated" ("accepted" is an explicit alias for the None default, server ≥ v0.8.7). 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",
    answer_model="claude-opus-4-6",
    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_*) — the model name here is overridden. /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, not a selector= dict

The v1 docs showed selector={…} and audit_note= / idempotency_key=. The real signature targets records by explicit fields:

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. (The v1 docs referenced .tier / .capability attributes on V1PolicyDeniedError — those do not exist; read policy context from error_code and details.)

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: 28 methods vs 42

Verified by introspecting cortexdbai 0.11.1. Fifteen methods exist only on V1Client and raise AttributeError on the async client — notably every layer read:

events · facts · beliefs · episodes · understanding · belief_why · build_beliefs · build_episodes · synthesize · audit_list · audit_verify · index_audit · layer_stats · write_status · close

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= parameter.

Method index

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

AreaMethods
ComposecomposePOST /v1/compose
Bi-temporal recordsquery_claims · claim_history · list_conflicts · get_conflict · resolve_conflictClaims & 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 · whoami/v1/auth
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