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"
}
| Field | Type | Required | Notes |
|---|---|---|---|
| scope | string | yes | Hierarchical type:id/type:id/... path. See Scopes. |
| modality | string | yes | One of conversation, document, tool_result, observation, feedback, imported. Unknown values stored verbatim but don't trigger structured extraction. |
| content | object | yes | Discriminated union on kind — see below. |
| context | object | yes | observed_at (RFC 3339) + optional source_recorded_at, location, preceded_by[], intent, labels[]. |
| observed_actor | object | no | Who performed the experience. Defaults to caller. If different, requires scope.write.on_behalf_of. |
| subject | object | no | Who/what the memory is about. Defaults to observed_actor. If different, requires scope.write.about_other. |
| directives | object | no | Per-call overrides — extract[], consolidate_into, confidence_floor, ttl_for_belief_layer, embed. |
| idempotency_key | string | yes | ≤ 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=:
| Value | Returns when | Typical latency |
|---|---|---|
| (omitted) | WAL append | ~5 ms (202) |
| captured | WAL fsync | ~10 ms (200) |
| indexed | BM25 + HNSW insert | ~100–500 ms (200) |
| consolidated | Beliefs/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
| HTTP | error_code | When |
|---|---|---|
| 401 | actor_mismatch | X-Cortex-Actor header doesn't match token sub |
| 403 | policy_denied | Capability missing — response cites tier + capability |
| 409 | idempotency_conflict | Same key, different body |
| 422 | invalid_envelope | Validation failed — details.field + details.reason |
| 503 | wal_unavailable | WAL 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"
}
| Field | Type | Notes |
|---|---|---|
| items | array | ≤ 1000 envelope items |
| ordering | enum | strict_temporal (default — preserve observed_at order, slower) or batch_throughput (out-of-order allowed). |
| directives | object | Applied 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..."
}