Ingest memories
Async ingest. Returns 202 + job_id by default; the extraction
runs in the background and the caller polls
GET /v1/memories/jobs/{job_id}.
With ?wait=true the server holds the connection up to ~30
seconds waiting for extraction to terminate. If it finishes in
time, the response is 200 OK with the terminal job inline
(status: "succeeded" or "failed"). If the wait window
elapses first, the response falls back to 202 + pending and
the caller resumes the normal polling pattern.
Authorizations
Long-lived org API key. Alternative: Authorization: Bearer <key>.
Query Parameters
When true, hold the connection up to ~30s waiting for extraction to terminate. Returns 200 + terminal job inline on success; falls back to 202 + pending on timeout. Default false → 202 + pending immediately.
Body
POST /v1/memories — async ingest from a message list.
Required entities: user_id and conv_id. user_id scopes
a user's per-user memory namespace, so a user's facts accumulate in
one place rather than fragmenting per-conversation. conv_id
anchors every extracted memory to a conversation for replay, export,
and bulk retract. agent_id and app_id remain optional — set
either, both, or neither. There is no auto-default app_id rule.
extract_artifacts opts in to the artifact-extraction stage
(off by default — the most expensive stage and most callers don't
need it). Episodes are always extracted (conv_id is
guaranteed). Facts are always extracted.
There is no client-facing pipeline hint: the server selects its extraction strategy automatically based on the number of messages.
Chat-style turns to extract memories from. Must be non-empty — an empty list returns 400 invalid_messages (the route raises this explicitly so the wire error carries the stable code rather than a generic Pydantic validation message). Server picks live vs batch extraction by length; no client-facing strategy hint.
User identifier. REQUIRED. Keys the per-user session namespace so a user's facts accumulate across their conversations rather than fragmenting per-conv. Also stored as an indexed filter axis on every row.
1"alice"
Conversation identifier. REQUIRED. Anchors every extracted memory to a conversation for replay, export, and bulk retract.
1"conv-2026-05-15-abc"
Optional agent scope. Indexed alongside the other entity ids.
Optional app scope. Indexed alongside the other entity ids.
Optional strptime format string used to parse each message's date field into a real timestamp on the batch-extraction path (e.g. "%Y-%m-%d %H:%M:%S"). When omitted, or when a date value fails to parse, the turn keeps its raw date string with no parsed timestamp. Only consulted on the batch path (message count above the live threshold); the live path ignores it.
"%Y-%m-%d %H:%M:%S"
When true, extracted artifacts are also stored and returned as artifact memories, in addition to fact + episode extraction. Off by default — most expensive stage and most callers don't need it. Routing (live vs batch) is unaffected and still chosen by message count.
When true, ingest through the agentic (task-aware) path: recall-first extraction + a Stage-2 noise filter + agentic artifact detection + a structured session gist. Intended for tool-using / coding-agent transcripts. Off by default (conversational extraction).
Optional list of group ids the client wants associated with this ingest. Each id must be a registered group on the org (see POST /v1/groups). Ids that are unknown or archived are dropped silently and echoed back in result.ignored_group_ids — a single stale id will not fail the ingest, since the chat path tolerates partial tagging better than a hard reject. After extraction, a classifier judges each memory personal-vs-shareable: personal memories are never group-tagged (they stay in the author's personal scope); shareable memories are tagged with every prompt-less (catch-all) group in the valid subset, plus whichever prompted groups the classifier matched. Tagged rows become reachable via filters: {group_ids: <id>} on search. The capped length is MAX_GROUP_IDS_PER_INGEST.
Response
Inline terminal response. Returned when ?wait=true is set and the extraction pipeline completes inside the wait window (~30s). Body is the terminal IngestJobResponse with status: "succeeded" (carrying result) or "failed" (carrying error).
Returned by POST /v1/memories (with status 202 by default,
200 when ?wait=true succeeds inline) and by
GET /v1/memories/jobs/{job_id}. Same wire shape across
every read of a job's lifecycle.
Opaque job id of the form job_<32-hex-chars>.
"job_a1b2c3d4e5f6071829304a5b6c7d8e9f"
Lifecycle state. pending → enqueued. running → extraction in progress. succeeded → result is set. failed → error is set. Terminal jobs (succeeded / failed) are retained for 24h before TTL sweep returns 404 job_not_found.
pending, running, succeeded, failed ISO-8601 timestamp the job was created.
Constant discriminator for the resource type.
"ingest_job"ISO-8601 timestamp of the most recent state transition.
Populated when status = succeeded; null otherwise.
Populated when status = failed; null otherwise.