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 cortexdbaiRequires 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,
)| Argument | Default | Notes |
|---|---|---|
api_url | http://localhost:3141 | v1 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. |
bearer | None | PASETO 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. |
timeout | 60 s | Per-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.
wait ∈ None | "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:
| Area | Methods |
|---|---|
| Compose | compose → POST /v1/compose |
| Bi-temporal records | query_claims · claim_history · list_conflicts · get_conflict · resolve_conflict → Claims & Conflicts |
| Layer history | belief_history · concept_history |
| Erasure | erase · erasure_preview · forget_preview → /v1/erasures |
| Import | import_data · import_status → /v1/import |
| Blobs | upload_blob · delete_blob → /v1/blobs |
| Beliefs (write) | create_belief |
| Vocabularies | update_vocabulary → /v1/vocabularies |
| Auth | signup · mint_token · whoami → /v1/auth |
| Audit | audit_chain · audit_chain_verify |
| Write status | write_status — "did my write land?" by idempotency_key or event_id |
| Lifecycle | close (sync) · aclose (async) |