> ## Documentation Index
> Fetch the complete documentation index at: https://developers.meshapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Memory

> Store per-user guardrails, preferences, and facts once, then attach them to any request with a single header.

Memory lets you keep durable context about an end user — the tone they prefer, rules your assistant must never break, facts it should remember — outside your prompt. You store it once against a **memID**, then attach that memID to a chat completion with one header. Mesh composes the stored items into a leading system message before the request goes upstream.

This is not conversation history. It is the small, stable set of things that should be true of *every* request for that user.

## Authentication

Memory management accepts **either** authentication method:

| Method                  | How                             | Typical use                                 |
| ----------------------- | ------------------------------- | ------------------------------------------- |
| **RSK key** (`rsk_...`) | `Authorization: Bearer rsk_...` | Your backend, storing facts as they come up |
| **Mesh Dashboard**      | Handled automatically           | Reviewing and editing memory in the UI      |

Both resolve to the same owner, so the memories your backend writes are the ones the dashboard shows.

Attaching a memory at inference time always uses your `rsk_...` key, the same as any other chat completion.

***

## Creating a memID

```bash theme={null}
curl https://api.meshapi.ai/v1/memories \
  -H "Authorization: Bearer <YOUR_RSK_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "mem_user_8412",
    "name": "Preferences for user 8412",
    "description": "Tone, language, and standing facts."
  }'
```

The `slug` is the memID — the value you will send in the `x-mem-id` header. Pick something stable and derived from your own user id: **it cannot be renamed**, because requests already sending it would stop resolving. A slug may not be UUID-shaped (that would be ambiguous with a memory id) and may not contain spaces.

`description` is for your own reference and is never sent to the model.

***

## Adding items

An item is a **guardrail**, a **preference**, or a **fact**. The three behave differently at request time, and choosing correctly matters more than anything else on this page.

```bash theme={null}
curl https://api.meshapi.ai/v1/memories/mem_user_8412/items \
  -H "Authorization: Bearer <YOUR_RSK_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "item_type": "guardrail",
    "content": "Never discuss pricing for enterprise plans."
  }'
```

| Type           | How it is used at request time                                                                                 | Key      |
| -------------- | -------------------------------------------------------------------------------------------------------------- | -------- |
| **guardrail**  | Always sent, in full, ahead of everything else. Never trimmed by the token budget, never expired by retention. | —        |
| **preference** | Sent in full, subject to the token budget. Deduplicated by `key` across attached memIDs.                       | Optional |
| **fact**       | Retrieved by relevance to the current request, within the token budget. Indexed for semantic search.           | —        |

Guardrails are the only type with a completeness guarantee. Anything that must never be silently dropped — a compliance rule, a hard constraint — belongs in a `guardrail`, not a `preference`.

A keyed preference is how you avoid contradictions when a request attaches more than one memID:

```bash theme={null}
curl https://api.meshapi.ai/v1/memories/mem_user_8412/items \
  -H "Authorization: Bearer <YOUR_RSK_KEY>" \
  -H "Content-Type: application/json" \
  -d '{ "item_type": "preference", "key": "tone", "content": "Answer in two sentences, no preamble." }'
```

<Note>
  Facts are embedded when you add or edit them, which is a billed embedding
  call. Guardrails and preferences are not embedded — they are always
  deterministic.
</Note>

***

## Attaching memory to a request

Send the memID in the `x-mem-id` header:

```bash theme={null}
curl https://api.meshapi.ai/v1/chat/completions \
  -H "Authorization: Bearer <YOUR_RSK_KEY>" \
  -H "Content-Type: application/json" \
  -H "x-mem-id: mem_user_8412" \
  -d '{
    "model": "openai/gpt-4o-mini",
    "messages": [{ "role": "user", "content": "Draft a reply to this complaint." }]
  }'
```

The response tells you which memIDs actually contributed:

```
X-Mesh-Memory-Applied: mem_user_8412
```

That header lists only memIDs whose content **survived** the token budget — so it is the answer to "did my memory actually reach the model", not just "did I ask for it".

### Multiple memIDs

Attach up to **16**, comma-separated. Order is precedence, left-most wins:

```bash theme={null}
-H "x-mem-id: mem_user_8412,mem_org_defaults"
```

* All distinct **guardrails** from every memID ship, deduplicated by text.
* **Preferences** sharing a `key` collapse to one — the left-most memID's value.
* **Facts** from all attached memIDs compete for relevance within one budget.

This is how you layer a per-user memory over org-wide defaults without the two contradicting each other.

<Note>
  `x-mem-id` takes **slugs**, not memory ids. The management API accepts either
  a slug or a UUID, but the header always matches on slug.
</Note>

### How facts are selected

Guardrails and preferences are deterministic — the same items resolve on every request. Facts are not: Mesh embeds your latest user message and retrieves the facts closest to it, so a memID holding hundreds of facts contributes only the handful that bear on the question in front of it.

<Note>
  That retrieval issues one embedding call, billed to your account and recorded
  in your usage as `memory_search_embed`. It is small, but it is not free — and
  it is one call per request, not one per attached memID.
</Note>

### Token budget

The assembled block is capped at roughly **1,500 tokens**. Guardrails are kept in full and are never counted against that cap; preferences, then facts, are added while the block stays inside it, and a line that would overflow is dropped. A memID whose every line was dropped is *not* listed in `X-Mesh-Memory-Applied`.

Injected text becomes part of the real prompt sent to the provider, so **it is tokenized and billed like any other prompt content**. A large memID raises the cost of every request that attaches it.

***

## Retention

By default items are kept until you delete them. Set `retention_days` and Mesh stops sending — then deletes — preferences and facts that age past it:

```bash theme={null}
curl -X PATCH https://api.meshapi.ai/v1/memories/mem_user_8412 \
  -H "Authorization: Bearer <YOUR_RSK_KEY>" \
  -H "Content-Type: application/json" \
  -d '{ "retention_days": 30 }'
```

Three things to know:

* **Guardrails are exempt.** They never expire. A retention policy is for stored personal data, and silently dropping a safety rule is the opposite of what you asked for.
* **Existing items are re-dated from when they were created**, not from when you set the policy. Setting 30 days on a memory holding a 40-day-old fact expires that fact immediately.
* **Expiry takes effect on read.** An expired item stops being sent straight away; the row is deleted by a nightly sweep shortly after.

Send `"retention_days": null` to go back to keeping items forever.

***

## Reviewing and editing

### Inspect a memID

```bash theme={null}
curl https://api.meshapi.ai/v1/memories/mem_user_8412 \
  -H "Authorization: Bearer <YOUR_RSK_KEY>"
```

Returns the memory plus its items, with `expires_at` on each and `item_counts` by type.

### List memIDs

```bash theme={null}
curl "https://api.meshapi.ai/v1/memories?q=user_84&limit=50" \
  -H "Authorization: Bearer <YOUR_RSK_KEY>"
```

`q` matches on slug and name. Organisation owners and admins see every memID in the organisation; members see their own.

### Edit an item

Only the fields you include change:

```bash theme={null}
curl -X PATCH https://api.meshapi.ai/v1/memories/mem_user_8412/items/<ITEM_ID> \
  -H "Authorization: Bearer <YOUR_RSK_KEY>" \
  -H "Content-Type: application/json" \
  -d '{ "content": "Answer in one short paragraph." }'
```

`item_type` cannot be changed — it decides both how the item is composed and whether it is indexed for search. Delete and re-add instead. Editing a fact re-indexes it.

### Turn a memID off without deleting it

```bash theme={null}
curl -X PATCH https://api.meshapi.ai/v1/memories/mem_user_8412 \
  -H "Authorization: Bearer <YOUR_RSK_KEY>" \
  -H "Content-Type: application/json" \
  -d '{ "is_active": false }'
```

Requests sending a disabled memID succeed and simply carry no memory.

### Delete

```bash theme={null}
# one item
curl -X DELETE https://api.meshapi.ai/v1/memories/mem_user_8412/items/<ITEM_ID> \
  -H "Authorization: Bearer <YOUR_RSK_KEY>"

# the whole memID, and everything in it
curl -X DELETE https://api.meshapi.ai/v1/memories/mem_user_8412 \
  -H "Authorization: Bearer <YOUR_RSK_KEY>"
```

Both return `204 No Content`. Deleting a memID removes its items and its stored facts from search, and is not reversible — it is the primitive to reach for when an end user asks you to erase what you hold about them.

***

## Usage

```bash theme={null}
curl "https://api.meshapi.ai/v1/memories/usage?window_days=30" \
  -H "Authorization: Bearer <YOUR_RSK_KEY>"
```

Per memID: requests, an estimate of the tokens the memory block added, when it was last used, and a per-key breakdown. Plus the count and cost of fact-search embeddings over the window.

<Note>
  `injected_tokens_est` is Mesh's own count of the block it prepended — no
  provider reports the split between injected memory and the rest of your
  prompt. Treat it as an estimate, not a charge. `tracked_since` tells you how
  far back attribution data actually goes.
</Note>

***

## Interactions worth knowing

<Warning>
  **Memory-attached requests are never cached.** A request carrying `x-mem-id`
  is personalized to one user, so serving it from the shared
  [response cache](/docs/capabilities/caching) could hand one user's context to
  another. Mesh bypasses the cache read *and* the cache write for these
  requests, unconditionally.

  If you lean on the gateway cache for cost control, attaching memory removes
  that saving entirely — you pay full price for every call.
</Warning>

**Memory failures are silent by design.** If a memID does not exist, is inactive, belongs to someone else, or the lookup itself fails, the request proceeds *without* the memory rather than erroring. A memory problem can never take down your inference path — but a typo in `x-mem-id` produces a perfectly successful, completely un-personalized response. `X-Mesh-Memory-Applied` is how you tell those two apart.

**Spend caps are re-checked after injection.** Injected tokens grow the prompt, so the cap is evaluated again against the assembled request: a key close to its limit can be rejected once memory is attached, even though the raw request would have passed.

***

## Who can see what

|                            | You (the owner) | Org owner / admin | Other members |
| -------------------------- | --------------- | ----------------- | ------------- |
| **See it in the list**     | Yes             | Yes               | No            |
| **Read its items**         | Yes             | Yes               | No            |
| **Delete it**              | Yes             | Yes               | No            |
| **Edit / add items**       | Yes             | No                | No            |
| **Attach with `x-mem-id`** | Yes             | No                | No            |

Two asymmetries are deliberate. Admins can **delete** a colleague's memID but not edit it, because deletion is the right-to-be-forgotten primitive and has to work after someone has left the company. And attachment stays with the owner: the gateway matches `x-mem-id` against the calling key's own owner, so an admin can read a memID in the dashboard that their own keys cannot use. The dashboard labels those rows rather than offering a header snippet that would quietly do nothing.

***

## Related

* [Caching](/docs/capabilities/caching) — why memory-attached requests bypass the response cache
* [Prompt Templates](/docs/capabilities/prompt-templates) — static, shared prompt scaffolding, versus memory's per-user context
* [Usage & Monitoring API](/docs/reference/usage-api) — where `memory_search_embed` charges show up
