REST API
HTTP/JSON is Memoturn’s primary protocol: stateless, serverless-friendly, and trivially callable from agent tools. The surface has three groups: the agent-memory API (per profile), the data-plane API (per database), and the control-plane platform API.
A machine-readable OpenAPI 3.1 spec covers every route below — use it for codegen, request validation, or exploring the API in any OpenAPI viewer. A test in the repository keeps it in lockstep with the router.
Addressing: one base URL per database (https://{db}.{region}.memoturn.dev). Against a single
node — the prototype default — the same data-plane routes are addressed as /v1/db/{db}/...,
where {db} may be name or name@branch. A branch can also be selected with the
Memoturn-Branch header.
Every response carries a Memoturn-Txid header. Requests may carry Memoturn-Min-Txid for a
read-your-writes floor and Memoturn-Consistency: primary|cached to pick a read mode. A write
may carry Memoturn-Durability: durable to be acked only after it ships to object storage. See
consistency.
Authentication is a bearer token: a per-database or namespace JWT for memory and data-plane routes, the platform key for control-plane routes. See security.
Agent-memory API
Section titled “Agent-memory API”Per-profile routes. A profile is one database named {ns}--{profile}; isolation between
profiles is structural — no operation touches two profiles. Full semantics:
memories and recall.
| Method | Path | Description |
|---|---|---|
| POST | /v1/memory/{ns}/{profile}/memories | Batch ingest (idempotent; auto-creates the profile) |
| POST | /v1/memory/{ns}/{profile}/recall | Hybrid keyword + topic + vector query |
| POST | /v1/memory/{ns}/{profile}/extract | Server-side LLM distill then ingest (503 if the node has no extractor) |
| GET | /v1/memory/{ns}/{profile}/memories/{id} | One memory with its supersession chain |
| DELETE | /v1/memory/{ns}/{profile}/memories/{id} | Forget (hard delete) |
| GET | /v1/memory/{ns}/{profile}/sessions | List sessions |
| DELETE | /v1/memory/{ns}/{profile}/sessions/{sid} | End session: delete its task memories (?turns=true drops the transcript too) |
| GET | /v1/memory/{ns}/{profile}/policy | The profile’s governance policy: override + effective values (read scope) |
| PUT | /v1/memory/{ns}/{profile}/policy | Set or clear a tighten-only profile override (admin scope; loosening is 409) |
| POST | /v1/memory/{ns}/{profile}/erasures | Verifiable erasure: hard-forget now, history rewrite + signed receipt after the grace window (202 + coupon) |
| GET | /v1/memory/{ns}/{profile}/erasures | List erasure coupons (newest first) |
| GET | /v1/memory/{ns}/{profile}/erasures/{id} | One coupon — completed carries the signed receipt |
| GET | /v1/memory/{ns} | List profiles in the namespace (namespace token) |
Ingest
Section titled “Ingest”A batch is atomic and returns one txid; concurrent batches may group-commit and share a
txid. Memory IDs are content-addressed, so re-ingesting
the same memory is a no-op reported as duplicate. Embeddings are bring-your-own by default;
with node-side auto-embedding enabled, omitted embeddings are filled in outside
the write path.
POST /v1/memory/acme/alice/memories{ "memories": [ { "type": "fact", "topic_key": "user.editor-theme", "summary": "prefers dark mode", "content": {"preference": "dark"}, "keywords": "theme ui", "embedding": [0.1, 0.2] }, { "type": "event", "summary": "deployed v2 to prod", "content": {"version": "v2"}, "session_id": "s-417", "source": "claude-code" }, { "type": "task", "summary": "follow up on refund #88", "content": {}, "ttl": 86400 }] }201{ "results": [ { "id": "mem_9f2c...", "status": "created", "superseded": ["mem_31ab..."] }, { "id": "mem_77d0...", "status": "created", "superseded": [] }, { "id": "mem_c4e1...", "status": "created", "superseded": [] } ], "txid": 42 }status is created, revived (an old superseded memory re-asserted and active again), or
duplicate (already active). Ingesting a fact or instruction whose topic_key has an active
memory supersedes the old row in the same transaction — history is preserved, not deleted.
source records which agent wrote the memory;
it is excluded from the content hash, so duplicate/revived keep the first writer’s
attribution.
Recall
Section titled “Recall”At least one of query (keyword channel), embedding (vector channel), or topic_key (exact
topic channel) is required. Channels are merged by reciprocal-rank fusion; superseded and expired
rows are dropped; each hit reports the channels that found it. An empty result is a valid answer.
POST /v1/memory/acme/alice/recall{ "query": "what theme does the user like?", "embedding": [0.1, 0.2], "topic_key": "user.editor-theme", "types": ["fact"], "source": "claude-code", "k": 8 }200{ "memories": [ { "id": "mem_9f2c...", "type": "fact", "topic_key": "user.editor-theme", "summary": "prefers dark mode", "content": {"preference": "dark"}, "keywords": "theme ui", "session_id": null, "source": "claude-code", "created_at": 1765000000, "superseded_by": null, "score": 0.064, "channels": ["topic", "keyword", "vector"] } ], "txid": 42 }session_id and source narrow recall to one session’s or one agent’s memories;
include_superseded: true exposes superseded rows; include_turns: true (requires embedding)
additionally searches the verbatim transcript and returns matching turns in a separate turns
array, never mixed into the fused ranking. See sessions.
Extract
Section titled “Extract”Opt-in per node (MEMOTURN_EXTRACT_API_KEY); unconfigured nodes return 503. The LLM call runs
before any database write and is structured-output-constrained; proposals then flow through the
ordinary idempotent ingest. dry_run: true returns proposals without writing. See
extraction. A namespace whose
governance policy sets ai_egress.extract: deny gets a
403 before any model is called (the same applies to /ask via ai_egress.ask).
POST /v1/memory/acme/alice/extract{ "turns": [ {"role": "user", "content": "I'm vegan now"} ], "session_id": "s-417", "source": "claude-code", "dry_run": false }Data-plane API
Section titled “Data-plane API”Per-database routes — the multi-model substrate under every profile. See data model.
| Method | Path | Description |
|---|---|---|
| POST | /v1/sql | Atomic statement batch |
| GET / PUT / DELETE | /v1/kv/{ns}/{key} | KV read / write (?ttl=) / delete; reads accept ?consistency= |
| GET | /v1/kv/{ns}?prefix= | List keys by prefix |
| POST | /v1/docs/{collection}/find | Query documents (filter, sort, limit, skip) |
| POST | /v1/docs/{collection}/insert | Insert documents |
| POST | /v1/docs/{collection}/update | Update by filter ($set/$unset/$inc/$push) |
| POST | /v1/docs/{collection}/delete | Delete by filter |
| POST | /v1/docs/{collection}/indexes | Index a document path (generated column + B-tree) |
| POST | /v1/vectors/{collection} | Upsert an embedding |
| POST | /v1/vectors/{collection}/search | ANN search |
| POST | /v1/memory/{session}/turns | Append a transcript turn |
| GET | /v1/memory/{session}/turns?last=20 | Read the recent window |
| POST | /v1/memory/{session}/search | Semantic search over turn embeddings |
| POST | /v1/sync | Ship this branch’s state to object storage now |
POST /v1/db/agent-42/sql{ "stmts": [ { "q": "SELECT count(*) FROM orders WHERE status = ?", "params": ["open"] } ] }200{ "results": [ [ { "count(*)": 3 } ] ], "txid": 17 }User SQL cannot reference reserved __memoturn_ tables (KV, docs, memories, transcripts),
however the name is quoted. Sandbox escapes — ATTACH, VACUUM INTO, PRAGMA writable_schema —
are rejected, and so is transaction control (BEGIN, COMMIT, SAVEPOINT, …; trigger bodies
are fine): each statement batch is already one atomic unit, and the engine owns transaction
boundaries. Benign read-only PRAGMAs (integrity_check, table_info) pass. Mutating
statements need write scope; a read token gets 403.
The value is the raw request/response body. PUT /v1/kv/scratch/plan?ttl=3600 writes with a
TTL; GET /v1/kv/scratch/plan?consistency=primary forces a strongly consistent owner read
(default is cached — eventually consistent, txid disclosed). Prefix listing returns:
GET /v1/kv/scratch?prefix=step:{ "keys": ["step:1", "step:2"] }Documents
Section titled “Documents”POST /v1/db/agent-42/docs/notes/find{ "filter": { "kind": "fact", "score": { "$gt": 0.5 } }, "sort": { "score": -1 }, "limit": 10 }200{ "docs": [ { "_id": "01J...", "kind": "fact", "score": 0.9 } ] }insert takes { "docs": [...] } and returns { "ids": [...] }; update takes
{ "filter", "update", "multi" } and returns { "modified": n }; delete returns
{ "deleted": n }; indexes takes { "path": "score" }.
Vectors and transcript turns
Section titled “Vectors and transcript turns”POST /v1/db/agent-42/vectors/notes/search{ "vector": [0.1, 0.2], "k": 8 }200{ "hits": [ { "id": "01J...", "distance": 0.13 } ] }Turns: POST /v1/memory/{session}/turns with { "role": "user", "content": {...}, "embedding": [...] } returns { "seq": n }; GET .../turns?last=20 and
POST .../search with { "vector", "k" } both return { "turns": [...] }.
Control-plane API
Section titled “Control-plane API”Platform routes, authenticated with the platform key.
| Method | Path | Description |
|---|---|---|
| POST | /v1/databases | Create a database ({name}) |
| GET | /v1/databases?cursor= | List databases |
| DELETE | /v1/databases/{db} | Delete a database — write tokens minted before the deletion are revoked (403); see security |
| POST | /v1/databases/{db}/branches | Fork a branch ({name, from?, checkpoint?, ttl?}; ttl makes a burner branch) |
| POST | /v1/databases/{db}/branches/{branch}/checkpoint | Tag the current state ({name}) |
| POST | /v1/databases/{db}/branches/{branch}/rewind | Rewind to a checkpoint or txid ({to}) |
| POST | /v1/databases/{db}/tokens | Mint a per-database token ({scope, expires_in}) |
| POST | /v1/namespaces/{ns}/tokens | Mint a namespace token covering every profile under it |
| GET | /v1/namespaces/{ns}/policy | The namespace governance policy |
| PUT | /v1/namespaces/{ns}/policy | Set the namespace governance policy (retention, memory aging, audit, AI egress) |
| GET | /v1/namespaces/{ns}/audit?from=&to=&action=&profile=&outcome=&limit=&cursor= | Page the namespace audit stream — platform key or a namespace admin token |
| GET | /v1/databases/{db}/usage | Usage counters |
POST /v1/databases/agent-42/tokens{ "scope": "write", "expires_in": 3600 }200{ "token": "eyJ..." }Token scopes are read (recall, get), write (ingest, forget, session end), and admin
(checkpoint, rewind). Branch operations are O(1) manifest writes — see
branching.
Limits
Section titled “Limits”The request surface is bounded by default — every knob is per node, tunable via configuration:
- Bodies over 32 MiB return
413; requests have a 30 s wall-clock budget and a global in-flight cap (1024). - Control endpoints (provision, branches, tokens) are rate-limited (10 req/s sustained);
overruns return
429. - Memory ingest batches cap at 1,000 memories per request; recall
kand result limits clamp to 1,000. - Document filters reject nesting deeper than 32 levels and
$in/$ninarrays over 1,000 items.
The same surface is available through the CLI, the TypeScript SDK, the Python SDK, and the MCP server.