Skip to main content
Zero runtime dependencies. Runs on Node 18+, Bun, Deno, and Cloudflare Workers.
Every method returns a promise. There is no separate async variant.

Client initialization

The client does not read an environment variable automatically, so pass apiKey explicitly. Requests carry no state between calls, so a single client can be shared across your whole application. fetch is injectable, which is useful for tests or a custom transport:

Methods

Response objects keep the API’s field names, so they are snake_case: memory_id, relevance_score, space_id, job_ids.

record

Pass background: true to queue the write instead of waiting on extraction. The call then returns job_id with status: "processing" and memory_id: null, so poll it with getJob.
background: true is about ten times faster, and should be your default. A blocking write waits for a language model to extract facts from the text before it returns. Measured against production, 12-17 seconds versus 1-3 seconds queued. Use a blocking write only when you search for the memory immediately afterwards.
userId, agentId and sessionId scope the memory inside the space, so one space can serve many end users without their memories mixing: only a retrieve carrying the same scope sees it. See Scoping.
A user id passed in metadata is stored but isolates nothing: metadata is returned with results, never filtered on. Use userId.

recordBatch

Up to 100 memories in one call, always queued.
The 1-100 range is checked locally, so an oversized batch fails before the round trip.

retrieve

Returns the results array directly, and an empty array when nothing matches.
relevance_score is a composite of four factors, not a normalized probability, so it can exceed 1.0. It is null for memories returned outside a ranked recall.
mode: "fast" skips the neural rerank pass for lower latency, at some cost to relevance quality. See Latency modes. Retrieval collapses a consolidated memory and the raw facts behind it into one result by default. Pass preferObservations: false to see both layers. Other filters: memoryType, tags, tagsMatch, minScore, asOf. asOf restricts the search to memories recorded at or before an ISO 8601 instant, so you get what the space knew then. queryTimestamp is not a filter. It moves the “now” that recency scoring and relative dates in the query are measured against, and never removes a result. See Searching over time.

retrieveReceipt, getReceipt and explain

retrieve returns a plain array, which has nowhere to carry the id of the receipt for that search. retrieveReceipt is the same search and returns both.
receiptDetail: "full" also asks the search to account for its own cuts, so the receipt can explain memories that were ranked and dropped before limit or a relevance floor ever applied. It can cost latency on the first call for a given query, so leave it "basic" for production traffic. receipt_id is null only when the receipt could not be stored. A receipt is a debugging aid and never load-bearing, so the search itself still succeeded and memories is complete either way. explain answers the question the receipt cannot: why one specific memory is not in your results.
not_retrieved is the one to act on: nothing matched the memory at all, so a bigger limit or a lower floor will not bring it back, and the query wording or the scope you searched is what to change. arms shows which kind of matching found it, and how well: found by keyword but not semantic usually means your query shares words with the memory but not meaning. Every search builds a receipt whether or not you asked for one, so getReceipt and explain also work on a request id from your own logs. See Context receipts.

reason

Returns the whole envelope, so usage is available alongside insights.

getUserProfile and askAboutUser

Everything a space has learned about one end user, and a question answered from that user’s memories only. Both need userId to be the same value your writes are scoped with.
A userId nobody has recorded under is not an error: it comes back with memory_count: 0 and an empty memories, because a user is a scope tag created by the first write naming it rather than a resource you register. An unknown space is still a 404. memory_count can also go down between two reads, since consolidation folds several raw facts into one note and the default view counts the note. Read it as how many distinct things are currently known about this user, not as an ingestion counter. See User profiles.

updateMemory

Correct a memory, or retire it without losing it.
state: "invalidated" drops the memory out of retrieve, consolidation and reasoning while keeping it for audit; "active" puts it back. That is a supersession, not a delete, so prefer it over deleteMemory, which is permanent. Editable fields: text, context, occurredStart, occurredEnd, memoryType, entities, state, reason. At least one is required. A call with nothing to change fails locally, before any request. reason is kept on the memory’s history so an audit shows why it changed, not merely that it did.
Memories the system synthesised from your raw facts cannot be edited. They are derived, so the API rejects the attempt rather than letting a synthesis drift away from the evidence under it.

getMemoryHistory

Returns { memory_id, history }, where each entry carries the previous content and when it changed.
history is frequently empty. It reflects supersession inside the memory layer, not edits you make with updateMemory. A memory you have just edited will still report an empty history. Treat it as a view onto how the system’s own understanding evolved, not as an audit log of your writes; for the latter, the reason you pass to updateMemory is what gets retained.

uploadFiles

Uploads files into a space so retrieval can draw on their content. Ingestion is asynchronous and returns job_ids, so poll each with getJob.
data accepts a Blob, File, ArrayBuffer, or Uint8Array. A Node Buffer works unchanged. There is no filesystem-path overload, which is what keeps the package usable on edge runtimes. All three limits are enforced before anything is sent, so an oversized upload fails instantly rather than after transferring the body: Supported formats are PDF, DOCX, DOC, PPTX, PPT, XLSX, XLS, HTML, TXT, Markdown, CSV, JPEG/PNG images, MP3/WAV audio and MP4/MOV/WEBM/MKV video. Images, audio and video are read into text. See Documents.

Vercel AI SDK

Wrap any model to give it memory. Relevant memories are recalled before the call and the turn is recorded after it.
Requires ai v5 or newer. Recording is fire-and-forget by default so memory never adds latency to a response; pass await: true to block until the write lands, or record: false to recall without writing. Streaming is supported, and the turn is recorded when the stream completes, not at the first token. Memory failures never break the model call. If recall or recording fails, the request proceeds without it.

OpenAI Agents SDK

Exposes memory as two tools the model can call: remember and recall.
A failing tool returns its error message as text rather than throwing, so a memory outage degrades the run instead of aborting it.

Error handling

429 and 5xx are retried automatically with jittered backoff. 4xx is never retried, it means the request itself was wrong.
A 503 arriving without a requestId may be a gateway timeout whose body was stripped in transit. Report it with a timestamp rather than treating it as a malformed response.
See the error reference for every code and which are safe to retry.

Browser usage

Never call the Anona API directly from client-side JavaScript. The API key would be visible to anyone who opens the network tab. Route every call through your own backend.
The package runs in the browser so it can be used in trusted contexts such as an internal tool behind auth, or a browser extension. For a public web app, keep the key on your server:

Next steps

Python SDK

The same surface, for Python.

API overview

Every endpoint, request shape, and error code.