HTTP API reference
Derived from server/routes/ and server/models.py. The running server also serves Swagger UI at /docs, which is generated from the same models — use it when you want to try a request rather than read about one.
Base URL — http://localhost:8000 under the shipped compose file.
Auth — every route except /health requires the API key, as either header:
Authorization: Bearer $SODAMEM_API_KEY
X-API-Key: $SODAMEM_API_KEYScope — request bodies embed the four scope fields; user_id is required. See Scoping.
Route index
| method | path | what it does |
|---|---|---|
GET | /health | liveness; unauthenticated, touches no store |
POST | /v1/memories | ingest messages, extract facts |
POST | /v1/memories/batch | ingest up to 500 sessions in one call |
GET | /v1/memories | list memories in a scope |
GET | /v1/memories/{id} | one memory |
PATCH | /v1/memories/{id} | supersede with a new version |
DELETE | /v1/memories/{id} | archive, or purge when enabled |
POST | /v1/search | ranked hits, zero LLM calls |
GET/POST | /v1/context | prompt-ready block with citations, zero LLM calls |
POST | /v1/answer | planner + reader answer path |
GET | /v1/events | every add, supersede and delete, with its reason |
GET | /v1/entity_timeline | one entity's history in order |
GET | /v1/explore | walk the graph outward from a starting point |
POST | /v1/refine | refine a search |
POST | /v1/maintenance/dream | rebuild entity profiles |
GET | /v1/jobs/{id} | status of an async job |
GET | /v1/metrics | per-route latency percentiles |
GET | /v1/usage | cumulative LLM token spend |
GET | /metrics | Prometheus exposition |
GET | /v1/admin/config | effective configuration |
GET/POST | /v1/admin/keys | list / mint named API keys |
DELETE | /v1/admin/keys/{id} | revoke a named key |
GET | /v1/admin/requests | rolling request log |
GET | /v1/admin/stats | disk and workload shape |
Writing
POST /v1/memories
curl -X POST localhost:8000/v1/memories \
-H "Authorization: Bearer $SODAMEM_API_KEY" -H "Content-Type: application/json" \
-d '{
"user_id": "alice",
"session_id": "s1",
"session_time": "2023-05-25",
"messages": [{"role": "user", "content": "Actually I moved from Kauai to Oahu."}]
}'| field | type | notes |
|---|---|---|
messages | array, min 1 | {role: user|assistant|system, content} |
session_id | string | groups turns into one conversation |
session_time | string / number | when the conversation happened |
infer | bool | run extraction (default) or store as given |
async_mode | bool | return a job_id instead of blocking |
Synchronous responses return facts_extracted, spans_written, turns_written. Async returns {job_id, status: "pending", session_id} — poll GET /v1/jobs/{id}.
Writing needs a model
Extraction calls the configured LLM provider. Reads do not. See Environment variables · LLM.
POST /v1/memories/batch
Same shape, but sessions is an array of up to 500 entries. The response carries succeeded, failed, and a per-item results array with the index, so one bad session does not fail the batch.
PATCH /v1/memories/{id}
A correction, not an edit. Closes the old version with a valid_until, writes a new one, links them with SUPERSEDES, and returns both ids.
DELETE /v1/memories/{id}
Archives by default: the memory leaves search and context, the record and its provenance stay. ?purge=true physically erases and cascades — only when SODAMEM_ALLOW_PURGE=true, and it is irreversible.
Reading
POST /v1/search
curl -X POST localhost:8000/v1/search \
-H "Authorization: Bearer $SODAMEM_API_KEY" -H "Content-Type: application/json" \
-d '{"user_id":"alice","query":"favorite color","top_k":10}'top_k is 1–100, default 10. Returns hits[] with id, content, score, session_id, occurred_at, metadata, plus a degraded[] array naming any retrieval leg that could not run — an empty vector index degrades the result rather than failing the request.
/v1/context
Takes a JSON body on POST, and also answers a plain GET with query parameters, since it is a pure read.
curl -X POST localhost:8000/v1/context \
-H "Authorization: Bearer $SODAMEM_API_KEY" -H "Content-Type: application/json" \
-d '{"user_id":"alice","query":"what do they prefer?","token_budget":1000}'Returns a prompt-ready block, already deduplicated and inside token_budget, with the citations behind every line. Zero LLM calls, and there is no parameter that can change that — the organizer tier is Python-only on purpose.
POST /v1/answer
The planner loop. Costs tokens; use it for questions build_context cannot answer in one shot.
Graph
| route | returns |
|---|---|
GET /v1/entity_timeline | one entity's history in order, each item still pointing at its source |
GET /v1/explore | the graph walked outward from a starting point |
POST /v1/refine | a refined search over a prior result |
Audit
GET /v1/events returns every add, supersede and delete with its reason. This is what makes "why did the agent forget X" answerable after the fact.
Jobs
GET /v1/jobs/{id} — job records live in the control-plane database (/data/.control/), so status survives a restart: a job in flight during a deploy no longer answers 404.
Maintenance
POST /v1/maintenance/dream rebuilds entity profiles. Idempotent, resumable, and safe to overlap — a second call while one is running returns status:"already_running" and does nothing. The response carries remaining_stale, so an hourly cron converges without anyone picking a batch size.
curl -X POST localhost:8000/v1/maintenance/dream \
-H "Authorization: Bearer $SODAMEM_API_KEY" -H "Content-Type: application/json" \
-d '{"user_id":"u1","async_mode":true}'Admin
See Self-hosting · Operating it. Named keys are for attribution, not isolation: there are no roles and no per-key scopes.
Errors
Errors return {code, message, details}. code is stable and meant to be matched on; message is meant to be read.

