Capture an experience into CortexDB. Replaces /v1/remember and /v1/episodes.

POST /v1/experience

The single write endpoint. Captures one experience — a structured envelope describing something an actor said, did, observed, or imported — and queues it for async indexing, fact extraction, belief revision, and understanding synthesis.

For batch ingest (≤1000 items / call) use POST /v1/experience/bulk. For larger volumes use POST /v1/import/jsonl.

Capability

scope.write (or scope.write.elevated when writing above the actor's natural level).

Headers

Authorization: Bearer <PASETO v4 public token>
X-Cortex-Actor:   user:alice
Content-Type:     application/json

Request body — the Experience Envelope

{
  "scope": "org:acme/dept:eng/user:alice",
  "modality": "conversation",
  "content": {
    "kind": "message",
    "role": "user",
    "text": "Just got off a call with Priya at Acme."
  },
  "context": {
    "observed_at": "2026-05-15T10:42:00Z",
    "labels": ["acme"],
    "intent": "deal_status_update"
  },
  "idempotency_key": "alice-chat-001"
}
FieldTypeRequiredNotes
scopestringyesHierarchical type:id/type:id/... path. See Scopes.
modalitystringyesOne of conversation, document, tool_result, observation, feedback, imported. Unknown values stored verbatim but don't trigger structured extraction.
contentobjectyesDiscriminated union on kind — see below.
contextobjectyesobserved_at (RFC 3339) + optional source_recorded_at, location, preceded_by[], intent, labels[].
observed_actorobjectnoWho performed the experience. Defaults to caller. If different, requires scope.write.on_behalf_of.
subjectobjectnoWho/what the memory is about. Defaults to observed_actor. If different, requires scope.write.about_other.
directivesobjectnoPer-call overrides — extract[], consolidate_into, confidence_floor, ttl_for_belief_layer, embed.
idempotency_keystringyes≤ 64 chars. Repeated calls with the same key + same body are deduplicated. Same key + different body → 409.

Content kinds

{ "kind": "message", "role": "user|assistant|tool|system", "text": "...", "media": [{ "blob_id": "blob_...", "alt": "screenshot" }] }
{ "kind": "text",    "text": "..." }
{ "kind": "json",    "data": { ... } }
{ "kind": "blob_ref","blob_id": "blob_..." }
{ "kind": "triple",  "triple": { "subject": {...}, "predicate": "...", "object": {...} } }

Sync via ?wait=

By default writes are async and return 202 Accepted as soon as the WAL append succeeds. Opt into synchronous behaviour with ?wait=:

ValueReturns whenTypical latency
(omitted)WAL append~5 ms (202)
capturedWAL fsync~10 ms (200)
indexedBM25 + HNSW insert~100–500 ms (200)
consolidatedBeliefs/Understanding touched~500–3000 ms (200)

Response — async (default)

HTTP/1.1 202 Accepted
X-Cortex-Policy: tier=scope; decision=allow; capability=scope.write
X-Cortex-Stability: stable
{
  "event_id": "evt_01HX...",
  "status": "captured",
  "wal_offset": 134892,
  "lifecycle_stream": "/v1/lifecycle/stream?event_id=evt_01HX..."
}

Response — sync (?wait=indexed)

{
  "event_id": "evt_01HX...",
  "status": "indexed",
  "wal_offset": 134892,
  "stages_completed": ["captured", "extracted", "indexed"],
  "derives": ["fact_01HX...", "belief_01HX..."],
  "elapsed_ms": { "capture": 4, "extract": 410, "index": 88 }
}

Errors

HTTPerror_codeWhen
401actor_mismatchX-Cortex-Actor header doesn't match token sub
403policy_deniedCapability missing — response cites tier + capability
409idempotency_conflictSame key, different body
422invalid_envelopeValidation failed — details.field + details.reason
503wal_unavailableWAL not writable (rare; disk pressure)

POST /v1/experience/bulk

Ingest up to 1000 items per call. Always returns 202; resolve per-item outcomes by subscribing to the lifecycle stream or polling /v1/experience/by-idempotency-key/{key}.

{
  "scope": "org:acme/dept:eng/user:alice",
  "items": [
    { "modality": "conversation", "content": { ... }, "context": { ... }, "idempotency_key": "k1" },
    { "modality": "conversation", "content": { ... }, "context": { ... }, "idempotency_key": "k2" }
  ],
  "ordering": "strict_temporal"
}
FieldTypeNotes
itemsarray≤ 1000 envelope items
orderingenumstrict_temporal (default — preserve observed_at order, slower) or batch_throughput (out-of-order allowed).
directivesobjectApplied to every item unless the item has its own.

Response:

{
  "batch_id": "batch_01HX...",
  "accepted": 1000,
  "lifecycle_stream": "/v1/lifecycle/stream?batch_id=batch_01HX..."
}