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>.
Required alongside the API key (no key→org reverse index).
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 keys
xmem's per-user session-cache namespace (one MultiKBSet per
(org, user), 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 — most expensive stage and most callers don't
need it). Episodes are always extracted (conv_id is now
guaranteed). Facts are always extracted. (Per SoT decision #4.)
There is no client-facing pipeline hint: the server picks live
vs batch by len(messages) (SoT decision #4 + Settings
threshold).
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, run the artifact-extraction stage in addition to fact + episode extraction. Off by default — most expensive stage and most callers don't need it. Setting this routes the request through the batch extraction path regardless of message count.
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. The valid subset is stamped on every memory row written by this ingest, making the rows 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.