Search memories
POST/api/v1/memory/search
In the SDKs: search · search_full · search_self
Returns memories, ordered most useful first. Two more fields can appear beside it: images when you pass max_images, and verify_used when you pass verify — the number of extra passes that actually ran, which is what you are billed for.
In the SDKs, search() returns the merged list of memories only and does not carry those two fields. Use search_full() (searchFull() in TypeScript) when you pass either option, or the results you paid for have nowhere to arrive.
Authentication
Every call carries your API key in an X-API-Key header. Keys are created in the console.
X-WOS-ModeloptionalPicks the memory model that answers. Omit it and the account default is used.
Request body
JSON, required. Out-of-range values are refused with a 400 rather than quietly clamped.
user_idstring requiredThe store to operate on. Stores are explicit: create one first or use the built-in "default".
querystring requiredmax_resultsinteger 5–20 · default 10How many memories to return. Out of range is refused, not clamped — asking for 20 and silently getting 10 reads as "that is all there is".
speakerstringRecall only this person’s words ("me" or a registered name). Unregistered → 404.
cache_controlobjectOpt-in recall caching: first call writes the cache (billed 2x query tokens for 5m TTL, 3x for 1h), hits within the TTL bill 0.1x. Any write to the store invalidates instantly.
filtersobjectNarrow the search to part of the store. Applied BEFORE ranking, so you get the best matches within the filter rather than a filtered top-N. Filtering behaves identically in every language. Unlisted keys are dropped rather than rejected — a typo silently widens the search.
form"memoir" | "archive"Render each returned memory’s time in this form (Scroll 1.2 and newer; HTTP header equivalent: X-WOS-Form). An unknown form is a 400.
tzintegerUTC offset in hours, used only while a
formrenders the times.max_imagesinteger 0–5 · default 1How many image memories the answer may carry. 0 asks for none. Out of range is refused, not clamped — silently cutting 5 to 1 would leave you believing you got five. Requires an image-capable model; an older one is refused rather than answering with no images.
verifyinteger 0–3 · default 0Memory verification passes. After the first retrieval, ask the engine again up to this many times, each time telling it what was already returned so it skips those and reaches further back. No LLM is called at any value. Each pass is one more engine call and can add up to
max_resultsmore memories, so it costs more — you are billed for what is delivered. Stops early when a pass finds nothing new. Default 0, so existing calls are unchanged. Higher values help most on questions that need several distinct memories from far apart in the history; they do little on a single-fact lookup.
Returns
The 200 body. Fields nested one level are shown as parent.child.
memoriesMemory[]memories.idstringmemories.contentstringmemories.categorystringCategory of the memory, e.g. "general".
memories.similaritynumberHow close this memory is to the query (0–1), higher is closer. It is NOT the ranking key — results already arrive best-first, and ordering by this field instead produces a WORSE order, not the same one. Take
memoriesin the order given. There is noscorefield.memories.importancenumberHow much weight this memory carries, as the engine assigned it.
memories.time_bucketstringMonth bucket, e.g. "2026-07". Omitted when temporal fields are stripped.
memories.is_supersededbooleanTrue if a later memory has superseded this one.
memories.superseded_bystring,nullId of the memory that superseded this one, or null.
memories.created_atstringWhen the memory was stored (RFC3339). Omitted when temporal fields are stripped.
memories.event_datestringWhen the content actually happened (RFC3339), if known.
memories.speakerstringWHO said it: "me" (the agent itself) or a registered person. Absent = untagged.
verify_usedintegerVerification passes actually performed. Present only when
verifywas sent. Lower than requested means the store had nothing further to add.
Example
curl -X POST https://api.wontopos.com/api/v1/memory/search \
-H "X-API-Key: $WOS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"user_id":"alice","query":"what does alice drink?","max_results":10}'Status codes
| Status | Meaning |
|---|---|
| 200 | Success. |
| 400 | verify outside 0–3, or max_images outside 0–5. Refused rather than clamped: a silently reduced value looks like a smaller answer, not a rejected request. |
| 401 | Missing or invalid API key. |
| 403 | verify or max_images on a model whose engine cannot serve it (Tablet 2 and newer can). Refused instead of accepted-and-ignored, so you are never billed for passes that did not run or told an image was stored when it was dropped. The message names the model. |
| 404 | Store does not exist — or the speaker filter names an unregistered person. |
| 429 | Rate limited (per-account, per-tier RPM). Retry after the indicated delay. |
Rate limits per tier and the full status list are in Errors & limits.
In the SDKs
The Python, TypeScript, and Rust SDKs wrap this endpoint so you do not build the request by
hand — pip install wontopos, npm i wontopos, or cargo add wontopos, then the method list is on the SDK reference. A coding agent can take the whole API in one file
at llms.txt, or over MCP with npx -y wontopos-mcp.