> ## Documentation Index
> Fetch the complete documentation index at: https://docs.memory.anonalabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> The official TypeScript client for Anona Memory, with adapters for the Vercel AI SDK and the OpenAI Agents SDK.

```bash theme={null}
npm install @anona-labs/memory
```

Zero runtime dependencies. Runs on Node 18+, Bun, Deno, and Cloudflare Workers.

```typescript theme={null}
import { Anona } from "@anona-labs/memory";

const anona = new Anona({ apiKey: process.env.ANONA_API_KEY! });

await anona.record({ spaceId: "support", content: "Alice is a senior engineer at Google" });

const results = await anona.retrieve({ spaceId: "support", query: "Where does Alice work?" });
console.log(results[0]?.content);
```

Every method returns a promise. There is no separate async variant.

## Client initialization

```typescript theme={null}
const anona = new Anona({
  apiKey: "anona_live_YOUR_KEY",
  baseUrl: "https://api.anonalabs.com", // optional, this is the default
  timeoutMs: 30_000,                    // optional, per attempt — this is the default
  maxRetries: 2,                        // optional — this is the default; 0 turns retrying off
});
```

`timeoutMs` is per attempt, so with `maxRetries: 2` a call can take up to three
timeouts plus its backoffs. `reason` and `askAboutUser` override it to 90
seconds of their own, because a thorough synthesis legitimately runs that long.

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:

```typescript theme={null}
const anona = new Anona({ apiKey: "...", fetch: myInstrumentedFetch });
```

## Methods

| Method | Returns | Reference |
| - | - | - |
| `record({ spaceId \| route, content, context?, timestamp?, metadata?, tags?, background?, userId?, agentId?, sessionId?, fallbackSpaceId?, maxTargets? })` | `RecordResult` | [Memories](https://docs.memory.anonalabs.com/api-reference/memories.md) |
| `recordBatch({ spaceId \| route, items, userId?, agentId?, sessionId?, fallbackSpaceId?, maxTargets? })` | `BatchRecordResult` | [Memories](https://docs.memory.anonalabs.com/api-reference/memories.md) |
| `retrieve({ spaceId \| route, query, limit?, topK?, mode?, memoryType?, userId?, agentId?, sessionId?, memberId?, tags?, tagsMatch?, tagGroups?, preferObservations?, minScore?, asOf?, queryTimestamp?, occurredAfter?, occurredBefore?, fallbackSpaceId?, maxTargets? })` | `SearchResult[]` | [Retrieve](https://docs.memory.anonalabs.com/api-reference/retrieve.md) |
| `getContext({ spaceId, query, maxTokens?, ... })` | `string` | [Prompt-ready context](https://docs.memory.anonalabs.com/api-reference/retrieve.md#prompt-ready-context) |
| `retrieveReceipt({ spaceId, query, receiptDetail?, ... })` | `RetrieveWithReceipt` | [Context receipts](https://docs.memory.anonalabs.com/api-reference/context-receipts.md) |
| `getReceipt(requestId)` | `ContextReceipt` | [Context receipts](https://docs.memory.anonalabs.com/api-reference/context-receipts.md) |
| `explain(requestId, memoryId)` | `MemoryExplanation` | [Context receipts](https://docs.memory.anonalabs.com/api-reference/context-receipts.md) |
| `reason({ spaceId, query, userId?, agentId?, sessionId?, model?, depth?, tagGroups? })` | `InsightsResult` | [Reason](https://docs.memory.anonalabs.com/api-reference/reason.md) |
| `getJob({ spaceId, jobId })` | `JobStatus` | [Job status](https://docs.memory.anonalabs.com/api-reference/memories.md#job-status) |
| `listSpaces()` | `Space[]` | [Spaces](https://docs.memory.anonalabs.com/api-reference/spaces.md) |
| `getSpace(spaceId)` | `Space` | [Spaces](https://docs.memory.anonalabs.com/api-reference/spaces.md) |
| `createSpace({ name, description?, spaceType? })` · `listSpaceTypes()` | `Space` / `SpaceType[]` | [Spaces](https://docs.memory.anonalabs.com/api-reference/spaces.md) |
| `deleteSpace(spaceId)` | `void` | [Spaces](https://docs.memory.anonalabs.com/api-reference/spaces.md) |
| `listMemories({ spaceId, limit?, offset?, query?, memoryType?, state?, includeSources?, userId?, agentId?, sessionId?, memberId? })` | `MemoryListPage` | [Memories](https://docs.memory.anonalabs.com/api-reference/memories.md) |
| `getMemoryHistory({ spaceId, memoryId })` | `MemoryHistory` | [Memories](https://docs.memory.anonalabs.com/api-reference/memories.md) |
| `updateMemory({ spaceId, memoryId, ... })` | `MemoryItem` | [Memories](https://docs.memory.anonalabs.com/api-reference/memories.md) |
| `deleteMemory({ spaceId, memoryId })` | `void` | [Memories](https://docs.memory.anonalabs.com/api-reference/memories.md) |
| `uploadFiles({ spaceId, files, tags?, strategy?, userId?, agentId?, sessionId? })` | `UploadResult` | [Documents](https://docs.memory.anonalabs.com/api-reference/documents.md) |
| `listDocuments({ spaceId, limit?, offset? })` | `DocumentListPage` | [Documents](https://docs.memory.anonalabs.com/api-reference/documents.md) |
| `getDocument({ spaceId, documentId })` | `DocumentItem` | [Documents](https://docs.memory.anonalabs.com/api-reference/documents.md) |
| `deleteDocument({ spaceId, documentId })` | `void` | [Documents](https://docs.memory.anonalabs.com/api-reference/documents.md) |
| `getGraph({ spaceId, limit?, minCount? })` | `Graph` | [Graph](https://docs.memory.anonalabs.com/api-reference/graph.md) |
| `listEntities({ spaceId, limit?, offset? })` | `EntityListPage` | [Graph](https://docs.memory.anonalabs.com/api-reference/graph.md) |
| `getEntity({ spaceId, entityId })` | `EntityDetail` | [Graph](https://docs.memory.anonalabs.com/api-reference/graph.md) |
| `getUserProfile({ spaceId, userId, limit?, offset?, memoryType?, format?, contextMaxTokens? })` | `UserProfile` | [User profiles](https://docs.memory.anonalabs.com/api-reference/user-profiles.md) |
| `askAboutUser({ spaceId, userId, query, model? })` | `AskUserResult` | [User profiles](https://docs.memory.anonalabs.com/api-reference/user-profiles.md) |
| `getUsage()` | `UsageSnapshot` | [Usage](https://docs.memory.anonalabs.com/api-reference/usage.md) |
| `cancelJob({ spaceId, jobId })` | `JobCancelResult` | [Job status](https://docs.memory.anonalabs.com/api-reference/memories.md#job-status) |
| `getSpaceProfile(spaceId)` · `setSpaceProfile({ spaceId, mission, disposition })` — both keys required, either may be `null`, since the PUT is a full replace · `deleteSpaceProfile(spaceId)` | `SpaceProfile` / `void` | [Memory models](https://docs.memory.anonalabs.com/api-reference/models.md) |
| `listMemoryModels({ spaceId, limit?, offset?, tags? })` | `MemoryModelList` | [Memory models](https://docs.memory.anonalabs.com/api-reference/models.md) |
| `createMemoryModel({ spaceId, name, query, modelId?, tags?, maxTokens?, trigger? })` | `MemoryModel` — but see the note below: the API answers `202` with a job | [Memory models](https://docs.memory.anonalabs.com/api-reference/models.md) |
| `getMemoryModel({ spaceId, modelId })` · `updateMemoryModel({ spaceId, modelId, ... })` · `deleteMemoryModel({ spaceId, modelId })` | `MemoryModel` / `void` | [Memory models](https://docs.memory.anonalabs.com/api-reference/models.md) |
| `refreshMemoryModel({ spaceId, modelId })` · `clearMemoryModel({ spaceId, modelId })` · `getMemoryModelHistory({ spaceId, modelId })` | `MemoryModelJob` / `MemoryModel` / `MemoryModelHistory` | [Memory models](https://docs.memory.anonalabs.com/api-reference/models.md) |
| `listCatalogModels()` | `CatalogModelList` | [Model selection](https://docs.memory.anonalabs.com/api-reference/model-selection.md) |
| `getReasonSettings(spaceId)` · `setReasonSettings({ spaceId, model, depth })` · `resetReasonSettings(spaceId)` | `ReasonSettings` / `void` | [Model selection](https://docs.memory.anonalabs.com/api-reference/model-selection.md) |
| `listRules(spaceId)` · `createRule({ spaceId, name, content, priority?, isActive?, tags? })` · `updateRule({ spaceId, ruleId, ... })` · `deleteRule({ spaceId, ruleId })` | `Rule[]` / `Rule` / `void` | [Rules](https://docs.memory.anonalabs.com/api-reference/spaces.md#rules) |
| `getChatSettings(spaceId)` · `setChatSettings({ spaceId, memoryLimit?, memoryTokenBudget?, autoRecord?, memory? })` · `resetChatSettings(spaceId)` | `ChatSettings` / `void` | [Chat defaults](https://docs.memory.anonalabs.com/api-reference/chat.md#space-defaults) |
| `getExtractionSettings(spaceId)` · `setExtractionSettings({ spaceId, mode?, guidance?, customPrompt?, labels?, freeFormEntities? })` · `resetExtractionSettings(spaceId)` | `ExtractionSettings` / `void` | [Extraction settings](https://docs.memory.anonalabs.com/api-reference/extraction-settings.md) |
| `createWebhook({ spaceId, url, eventTypes?, enabled? })` · `listWebhooks(spaceId)` · `updateWebhook({ spaceId, webhookId, ... })` · `deleteWebhook({ spaceId, webhookId })` · `listWebhookDeliveries({ spaceId, webhookId, limit?, cursor? })` | `Webhook` / `WebhookDeliveryPage` / `void` | [Webhooks](https://docs.memory.anonalabs.com/api-reference/webhooks.md) |

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

`record`, `recordBatch` and `retrieve` take **either** `spaceId` **or**
`route: "auto"`, never both and never neither — the types make the wrong pairing
a compile error rather than a `422 route_conflict` found at runtime.
`fallbackSpaceId` and `maxTargets` apply only to the routed form.

> [!NOTE]
>
> Two places where the published types are currently narrower than the API, both
> tracked in ANO-443:
>
> * **`CatalogModel` is missing three fields that `GET /v1/models` always
>   returns**: `default` (whether this is the model a caller gets by omitting the
>   field), `provider` (which host serves it) and `supports_memory_operations`
>   (`false` for a model the chat proxy serves but `reason` and the memory models
>   cannot). They are present in the response, so reading them works — the type
>   just will not admit they exist. Cast, or read
>   [the catalog fields](https://docs.memory.anonalabs.com/api-reference/model-selection.md).
> * **`createMemoryModel` is typed `Promise<MemoryModel>`, and the API answers
>   `202` with a job** — `{ model_id, job_id, status }`, no `content`. Poll
>   `getJob` and then `getMemoryModel`. `refreshMemoryModel` is typed correctly as
>   a job.

### Parameters the REST API has and this client does not

| Endpoint | REST-only fields |
| - | - |
| `POST /v1/retrieve` | `expand_query`, `include_source`, `source_max_tokens`, and the connector provenance filters `source` / `repo` / `pull_request` / `author` / `merged_by`. (`format`, `context_max_tokens` and `block_order` are not missing — `getContext` is the method that sends them, and `retrieveReceipt` sends `receipt` / `receipt_detail`.) |
| `GET /v1/spaces/{space_id}/memories` | `tag_groups`, and the same provenance filters. |
| `GET /v1/spaces/{space_id}/documents` | `q`, `source`. |
| `POST /v1/spaces/{space_id}/users/{user_id}/ask` | `depth`. `askAboutUser` takes `model` only, so the depth falls through to the space's reason default. |
| Every endpoint | `org_id`. An API key already belongs to one organization, so there is nothing to choose — the field exists so a caller can *assert* which organization it meant and get `400 org_mismatch` rather than being served the key's. |

Also note `topK` is an **alias for `limit`**, not a candidate bound: it is sent
in place of `limit` and wins when both are given.

### record

```typescript theme={null}
const memory = await anona.record({
  spaceId: "support",
  content: "Alice works at Google and specializes in distributed systems",
  userId: "alice_123",
  tags: ["work"],
  metadata: { source: "crm" },
});
console.log(memory.memory_id);
```

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`.

> [!WARNING]
>
> **`background: true` is several 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, about 3-12 seconds for a short memory (longer content takes
> longer) versus 1-3 seconds queued. Use a blocking
> write only when you search for the memory immediately afterwards.
>
> ```typescript theme={null}
> const job = await anona.record({
>   spaceId: "customer-support-bot",
>   content: "Alice is a senior engineer at Google",
>   background: true,
> });
> await anona.getJob({ spaceId: "customer-support-bot", jobId: job.job_id });
> ```

`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](https://docs.memory.anonalabs.com/api-reference/retrieve.md#scoping-within-a-space).

> [!WARNING]
>
> A user id passed in `metadata` is stored but isolates nothing: `metadata` is returned
> with results, never filtered on. Use `userId`.

```typescript theme={null}
// Only Alice's memories
const results = await anona.retrieve({
  spaceId: "support",
  query: "Where does Alice work?",
  userId: "alice_123",
});
```

### recordBatch

Up to 100 memories in one call, always queued.

```typescript theme={null}
const batch = await anona.recordBatch({
  spaceId: "support",
  items: [{ content: "Bob works in Berlin" }, { content: "Carol joined in March" }],
});
console.log(batch.job_id, batch.accepted);
```

The 1-100 range is checked locally, so an oversized batch fails before the round trip.

### retrieve

```typescript theme={null}
const results = await anona.retrieve({
  spaceId: "support",
  query: "Where does Alice work?",
  limit: 10,
});

for (const r of results) {
  console.log(r.content, r.relevance_score);
  // A `note` names the facts it was synthesized from. Empty on a `fact`,
  // which is the evidence itself.
  for (const sourceId of r.source_ids) {
    console.log("  built from", sourceId);
  }
}
```

Returns the results array directly, and an empty array when nothing matches.

`source_ids` is always present, so it is safe to iterate without a guard. Because a
note is derived, its own `metadata` is empty and its `document_id` is `null`: those
belong to the facts behind it. Pass `preferObservations: false` to get those facts back
alongside their note, each carrying the metadata you wrote it with.

> [!NOTE]
>
> `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](https://docs.memory.anonalabs.com/api-reference/retrieve.md#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`, `tagGroups`, `minScore`, `asOf`.

`tagGroups` is a boolean tag filter, for what a flat list under one `tagsMatch`
cannot say: an OR of alternatives, or an exclusion. It is typed as a `TagGroup`
union, so a malformed filter is a compile error rather than a 422.

```ts theme={null}
const results = await anona.retrieve({
  spaceId: "support",
  query: "what is left to do",
  tagGroups: [
    { or: [{ tags: ["project:alpha"] }, { tags: ["project:beta"] }] },
    { not: { tags: ["status:archived"] } },
  ],
});
```

See [Compound tag filters](https://docs.memory.anonalabs.com/api-reference/retrieve.md#compound-tag-filters).

`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](https://docs.memory.anonalabs.com/api-reference/retrieve.md#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.

```typescript theme={null}
const res = await anona.retrieveReceipt({
  spaceId: "support",
  query: "who is on call for billing?",
  receiptDetail: "full",
});

console.log(res.memories.map((m) => m.content));

const receipt = await anona.getReceipt(res.receipt_id!);
for (const cut of receipt.excluded) {
  console.log(cut.reason, cut.detail);
}
```

`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.

```typescript theme={null}
const verdict = await anona.explain(res.receipt_id!, "mem_abc123");

verdict.outcome;  // "included" | "excluded" | "not_retrieved"
verdict.stage;    // "retrieval" | "fusion" | "rerank", when excluded
verdict.arms;     // { semantic: 812, keyword: null, ... }
```

`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](https://docs.memory.anonalabs.com/api-reference/context-receipts.md).

### reason

```typescript theme={null}
const answer = await anona.reason({
  spaceId: "support",
  query: "What have we learned about Alice's career?",
});
console.log(answer.insights);
```

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.

```typescript theme={null}
const profile = await anona.getUserProfile({ spaceId: "support", userId: "alice_123" });
console.log(profile.memory_count, profile.last_active);

// Prompt-ready instead of a list
const { context } = await anona.getUserProfile({
  spaceId: "support",
  userId: "alice_123",
  format: "block",
  contextMaxTokens: 500,
});

const answer = await anona.askAboutUser({
  spaceId: "support",
  userId: "alice_123",
  query: "How does she prefer to be contacted?",
});
console.log(answer.insights, answer.model);
```

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](https://docs.memory.anonalabs.com/api-reference/user-profiles.md).

### updateMemory

Correct a memory, or retire it without losing it.

```typescript theme={null}
// Fix the content
await anona.updateMemory({
  spaceId: "support",
  memoryId: "mem_1",
  text: "Alice moved to Berlin in March",
});

// Retire it. Reversible, unlike deleteMemory
await anona.updateMemory({
  spaceId: "support",
  memoryId: "mem_1",
  state: "invalidated",
  reason: "superseded by a later correction",
});
```

`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.

> [!NOTE]
>
> 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

```typescript theme={null}
const { history } = await anona.getMemoryHistory({ spaceId: "support", memoryId: "mem_1" });

for (const entry of history) {
  console.log(entry.changed_at, entry.previous_content);
}
```

Returns `{ memory_id, history }`, where each entry carries the previous content
and when it changed.

> [!NOTE]
>
> Entries come from two places: Anona's own consolidation rewriting a memory, and
> your edits with `updateMemory`. Editing a memory adds one entry carrying its
> `previous_content`, `changed_at` and the `reason` you passed, so an edit can be
> audited straight away. An empty `history` means the memory has never changed.

### 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`.

```typescript theme={null}
import { readFile } from "node:fs/promises";

const upload = await anona.uploadFiles({
  spaceId: "support",
  files: [{ data: await readFile("handbook.pdf"), filename: "handbook.pdf" }],
  tags: ["hr", "policy"],
});
console.log(upload.job_ids);
```

`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:

| Limit | Value |
| - | - |
| Files per request | 20 |
| Size per file | 25 MB |
| Total per request | 50 MB |

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](https://docs.memory.anonalabs.com/api-reference/documents.md).

## Vercel AI SDK

Wrap any model to give it memory. Relevant memories are recalled before the call and the
turn is recorded after it.

```typescript theme={null}
import { openai } from "@ai-sdk/openai";
import { generateText, wrapLanguageModel } from "ai";
import { Anona } from "@anona-labs/memory";
import { anonaMemory } from "@anona-labs/memory/vercel";

const anona = new Anona({ apiKey: process.env.ANONA_API_KEY! });

const model = wrapLanguageModel({
  model: openai("gpt-4o"),
  middleware: anonaMemory({ client: anona, spaceId: "support", limit: 8 }),
});

const { text } = await generateText({ model, prompt: "How should I contact Alice?" });
```

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`.

```typescript theme={null}
import { Agent, run } from "@openai/agents";
import { Anona } from "@anona-labs/memory";
import { anonaTools } from "@anona-labs/memory/openai-agents";

const anona = new Anona({ apiKey: process.env.ANONA_API_KEY! });

const agent = new Agent({
  name: "support",
  tools: anonaTools({ client: anona, spaceId: "support" }),
});

await run(agent, "What do you know about Alice?");
```

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

```typescript theme={null}
import { Anona, AnonaError } from "@anona-labs/memory";

try {
  await anona.retrieve({ spaceId: "support", query: "Where does Alice work?" });
} catch (error) {
  if (error instanceof AnonaError) {
    console.error(error.statusCode, error.code, error.requestId);
  }
}
```

A `429` is retried automatically on every call, with the wait the server named.
A `5xx` and a dropped connection are retried only where a replay is safe,
because a `5xx` may mean the request landed. These ten calls opt out:
`record`, `recordBatch`, `reason`, `askAboutUser`, `uploadFiles`,
`createSpace`, `createWebhook`, `createRule`, `createMemoryModel` and
`refreshMemoryModel`. Everything else — `retrieve`, `getContext`,
`retrieveReceipt` and every `GET`, `PUT` and `DELETE` — is retried. `4xx` is never retried — it means the request itself was wrong — and
`credits_exhausted` is not retried either, since it is a balance rather than a
window. The full matrix, including where this client and the Python one differ,
is [in the error reference](https://docs.memory.anonalabs.com/api-reference/errors.md#what-the-sdks-already-retry).

> [!NOTE]
>
> 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](https://docs.memory.anonalabs.com/api-reference/errors.md) for every code and which are safe to
retry.

## Browser usage

> [!WARNING]
>
> 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:

```typescript theme={null}
// Backend route
app.post("/api/memories/search", async (req, res) => {
  const results = await anona.retrieve({
    spaceId: req.body.spaceId,
    query: req.body.query,
  });
  res.json(results);
});
```

## Next steps

> **[Python SDK](https://docs.memory.anonalabs.com/sdk/python.md)**
>
> The same surface, for Python.

> **[API overview](https://docs.memory.anonalabs.com/api-reference/overview.md)**
>
> Every endpoint, request shape, and error code.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.