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

# Context Receipts

> A per-response manifest of what entered the prompt, what was cut, and why, retrievable after the fact.

`retrieve` (and the drop-in proxy) return a ranked list, but a lot happens between
"the engine found this" and "the model saw this": near-duplicates get collapsed,
low-relevance hits get dropped, results past your `limit` get cut, and, if you
rendered a prompt-ready block, whole memories can get dropped again to fit the
token budget. A context receipt is that decision, written down: what survived,
what didn't, and which reason removed it.

A receipt covers two families of cut. By default it reports the ones made after
retrieval returned, which is the fast path and costs nothing. Ask for
`receipt_detail: "full"` and it also reports the ones the search pipeline made
internally, so a memory that never made it out of ranking is accounted for
instead of simply absent.

<Note>
  **What a receipt does not prove.** A receipt reports what retrieval *returned*
  and what assembly *cut*. It does not prove retrieval was correct. It cannot
  tell you a relevant memory was missing from the candidates in the first place,
  and it is not an isolation guarantee. Treat it as a debugging tool first, an
  audit trail second, not a certificate that the right memories were found.
</Note>

<Note>
  **Best-effort, never load-bearing.** Asking for a receipt never changes what a
  call returns or how it's billed, and a request succeeds whether or not its
  receipt was actually stored. If you ask for one and it can't be built or saved
  for any reason, the call still completes normally; you just won't have a
  receipt to fetch afterward.
</Note>

## How it works

Every `retrieve` call already builds and stores a receipt, whether or not you
ask for one. That's what makes a receipt fetchable later for a request
nobody flagged in advance, which is the actual debugging case: grab the
request id out of your own logs (`X-Request-ID`) after the fact and fetch it,
even for a call that never set `receipt`. Passing `receipt: true` changes one
thing only: that call's own response hands you the id back directly, so you
don't have to go dig it out of your logs.

```http theme={null}
POST /v1/retrieve
Authorization: Bearer anona_live_YOUR_KEY
Content-Type: application/json
```

```json theme={null}
{
  "space_id": "customer-support-bot",
  "query": "What plan is this customer on?",
  "receipt": true
}
```

The response is unchanged except for one added field:

```json theme={null}
{
  "results": [ "…unchanged…" ],
  "usage": { "input_tokens": 8 },
  "receipt_id": "3f9a2b7c-1e4d-4a6b-9c3e-8d2f1a0b5c7e"
}
```

`receipt_id` only appears when you set `receipt: true` and a receipt was
actually built. Omitting `receipt` (the default) leaves the response exactly
as it was before this existed, field for field.

### Asking for the search pipeline's own cuts

`receipt_detail` controls how deep the receipt goes. It is independent of
`receipt`: one decides whether this response hands you the id, the other
decides what the receipt contains.

| Value               | What the receipt reports                                                                          |
| ------------------- | ------------------------------------------------------------------------------------------------- |
| `"basic"` (default) | Cuts made after search returned: `dedup`, `min_score`, `limit`, `budget`. Costs nothing extra.    |
| `"full"`            | The above, plus the cuts search made internally: `candidate_cap`, `rerank_rank`, `engine_budget`. |

```json theme={null}
{
  "space_id": "customer-support-bot",
  "query": "What plan is this customer on?",
  "receipt": true,
  "receipt_detail": "full"
}
```

<Note>
  **`"full"` can cost you a little latency.** Repeated searches are served from a
  cache, and the pipeline's internal decisions are not part of a cached answer.
  So the first `"full"` call for a given query may re-run the search to collect
  them, even when a `"basic"` call would have been served from cache. Later calls
  reuse that richer entry. Results are identical either way; only the timing
  differs. Leave it on `"basic"` for production traffic and reach for `"full"`
  when you're actually investigating something.
</Note>

<Note>
  `receipt_id` is the same value as the `X-Request-ID` header every response
  already carries. There's nothing extra to capture. If you log request ids
  today, you already have what you need to fetch the receipt later.
</Note>

## Fetch a receipt

```http theme={null}
GET /v1/receipts/{request_id}
Authorization: Bearer anona_live_YOUR_KEY
```

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://api.anonalabs.com/v1/receipts/3f9a2b7c-1e4d-4a6b-9c3e-8d2f1a0b5c7e" \
    -H "Authorization: Bearer anona_live_YOUR_KEY"
  ```

  ```python Python theme={null}
  import requests

  resp = requests.get(
      "https://api.anonalabs.com/v1/receipts/3f9a2b7c-1e4d-4a6b-9c3e-8d2f1a0b5c7e",
      headers={"Authorization": "Bearer anona_live_YOUR_KEY"},
  )
  receipt = resp.json()
  print(receipt["included"], receipt["excluded"])
  ```
</CodeGroup>

**Response** `200 OK`

```json theme={null}
{
  "request_id": "3f9a2b7c-1e4d-4a6b-9c3e-8d2f1a0b5c7e",
  "space_id": "customer-support-bot",
  "included": [
    {
      "memory_id": "mem_abc123def456",
      "relevance_score": 0.91,
      "token_estimate": 14
    }
  ],
  "excluded": [
    {
      "memory_id": "mem_9f21e2",
      "reason": "dedup",
      "detail": "covered by mem_abc123def456"
    },
    {
      "memory_id": "mem_7a10c4",
      "reason": "min_score",
      "detail": "score=0.12 floor=0.3"
    },
    {
      "memory_id": "mem_5b88e0",
      "reason": "limit",
      "detail": "rank=11"
    }
  ],
  "excluded_truncated": {},
  "token_accounting": {},
  "engine_stages": false
}
```

| Field                | Type    | Description                                                                                                                                                                                                                                                                                                                                                                       |
| -------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `request_id`         | string  | The id of the call this receipt was built for. Matches `receipt_id` and the `X-Request-ID` response header on that call.                                                                                                                                                                                                                                                          |
| `space_id`           | string  | The space the call searched.                                                                                                                                                                                                                                                                                                                                                      |
| `included`           | array   | What survived every cut, in result order. Each item is `{memory_id, relevance_score, token_estimate}`, with no memory content.                                                                                                                                                                                                                                                    |
| `excluded`           | array   | What was cut, in the order it could have been cut: the search pipeline's own cuts first (only when `receipt_detail` was `"full"`), then `dedup`, `min_score`, `limit`, and `budget`. Each item is `{memory_id, reason, detail}`. Capped at 50 entries per reason.                                                                                                                 |
| `excluded_truncated` | object  | `{}` unless a reason above hit the 50-entry cap. When one did: `{reason: count}`, how many additional cuts for that reason didn't fit in `excluded`. Always accurate, never silently dropped: if the manifest was cut, this says so and by how much.                                                                                                                              |
| `engine_stages`      | boolean | Whether this call asked the search pipeline to account for its own cuts (`receipt_detail: "full"`). When `false`, `excluded` covers only what happened after search returned, and its silence about a memory says nothing about whether search considered it.                                                                                                                     |
| `token_accounting`   | object  | `{}` unless the call rendered a prompt-ready block (`format: "block"` on retrieve, or a drop-in proxy call with memory injection on); there's no block to account for otherwise. When one was rendered: `{block_tokens, budget}`, where `block_tokens` is the rendered block's estimated size and `budget` is the ceiling it was rendered against (`null` if you didn't set one). |

<Note>
  **Why cap it.** The engine's own fan-out is bounded by tokens, not result
  count, so a space of short facts combined with a high `limit` and a
  `min_score` floor can drop several hundred results in one call, far more
  than a debugging manifest needs to be useful. `excluded` keeps the first 50
  per reason; `excluded_truncated` always reports how many more there were, so
  a capped list never reads as a complete one. The cap never affects `results`
  itself, only how much of the *cut* list gets written down.
</Note>

Receipts are kept for about an hour after the call that produced them, then
expire. There is no way to list receipts; fetch each one by the id its call
returned.

## The exclusion reasons

### After search returned (always available)

| `reason`    | What it means                                                                                                                                                                                                                        |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `dedup`     | A raw fact was dropped because a consolidated memory already returned covers it. `detail` names the covering memory's id.                                                                                                            |
| `min_score` | The result fell below the `min_score` floor you set. `detail` shows the score and the floor.                                                                                                                                         |
| `limit`     | The result ranked outside your requested `limit` (or `top_k`). `detail` shows its rank.                                                                                                                                              |
| `budget`    | Rendering a prompt-ready block (`format: "block"`, or the drop-in proxy's memory injection) had to drop the whole memory to fit `context_max_tokens`. `detail` shows its rank. Only ever appears when a block was actually rendered. |

`dedup`, `min_score`, and `limit` happen during retrieval itself, in that
order, so each stage only ever sees what survived the one before it, so a given
memory appears in `excluded` at most once. `budget` happens afterward, during
rendering, and only applies to memories that made it past all three.

### Inside search (`receipt_detail: "full"` only)

Search ranks far more candidates than it returns. These three reasons account
for memories that were considered and dropped before the reasons above ever
applied.

| `reason`        | What it means                                                                                                                                                                                                   |
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `candidate_cap` | The memory was found, but ranked too low to enter the final scoring round. `detail` shows the rank it reached and the cap. Usually means the query didn't match it strongly enough, not that anything is wrong. |
| `rerank_rank`   | It reached final scoring and placed below your `limit`. `detail` shows its position. Raising `limit` would bring it back.                                                                                       |
| `engine_budget` | It scored well enough to be returned, but was passed over to fit the response's own token budget: something ranked below it came back and this didn't. `detail` shows its position.                             |

<Note>
  The two families never overlap. The reasons above describe memories search
  never returned; the ones in the previous table describe memories it did return
  and something afterward removed. A memory appears under exactly one reason, so
  the counts in `excluded_truncated` stay accurate.
</Note>

## Account for one specific memory

The receipt tells you what was cut. It can't tell you about a memory that no
stage mentions at all, and that's usually the memory you're asking about:
you know it's in the space, and it isn't in your prompt.

`explain` answers exactly that, for one memory id, against one earlier call.

```http theme={null}
GET /v1/receipts/{request_id}/explain?memory_id=mem_abc123def456
Authorization: Bearer anona_live_YOUR_KEY
```

<CodeGroup>
  ```bash cURL theme={null}
  curl "https://api.anonalabs.com/v1/receipts/3f9a2b7c-1e4d-4a6b-9c3e-8d2f1a0b5c7e/explain?memory_id=mem_abc123def456" \
    -H "Authorization: Bearer anona_live_YOUR_KEY"
  ```

  ```python Python theme={null}
  import requests

  resp = requests.get(
      "https://api.anonalabs.com/v1/receipts/3f9a2b7c-1e4d-4a6b-9c3e-8d2f1a0b5c7e/explain",
      params={"memory_id": "mem_abc123def456"},
      headers={"Authorization": "Bearer anona_live_YOUR_KEY"},
  )
  print(resp.json())
  ```
</CodeGroup>

**Response** `200 OK`

```json theme={null}
{
  "memory_id": "mem_abc123def456",
  "outcome": "excluded",
  "stage": "fusion",
  "detail": "fusion rank 812, cap 300",
  "arms": {
    "semantic": 812,
    "keyword": null,
    "related": null,
    "time": null
  },
  "scores": { "fusion": 0.031 }
}
```

| Field       | Type   | Description                                                                                                                                    |
| ----------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `memory_id` | string | The memory you asked about.                                                                                                                    |
| `outcome`   | string | `included` (it came back on that call), `excluded` (search considered it and dropped it), or `not_retrieved` (no stage of search ever saw it). |
| `stage`     | string | Where it was dropped: `retrieval`, `fusion`, or `rerank`. `null` unless `outcome` is `excluded`.                                               |
| `detail`    | string | A short plain-language account of that drop.                                                                                                   |
| `arms`      | object | How each search method ranked it, or `null` for a method that didn't find it at all. Empty when nothing found it.                              |
| `scores`    | object | The scores it earned at the stage it reached.                                                                                                  |

### Reading the result

`not_retrieved` is the one worth acting on. It means no part of search matched
the memory, so raising `limit` or lowering `min_score` will not help. Either
the wording is too far from your query, or the memory isn't scoped where you're
searching. Check `user_id`, `agent_id`, `session_id`, and any `tags` on the
original call first.

`arms` tells you which kind of matching worked. A memory found only by
`keyword` and not by `semantic` usually means your query and the stored memory
share words but not meaning, and vice versa. `related` means it was reached
through another memory it's connected to, and `time` means it matched a date
range in the query.

<Note>
  **It replays the original search.** `explain` runs the same query again, pinned
  to the moment the original call ran, so it reports the pipeline as it actually
  behaved rather than as it would behave now. Memories written since then don't
  change the answer. This works on any call, including ones that never set
  `receipt` or `receipt_detail`, which is the point: the call you need to explain
  is rarely the one you flagged in advance.
</Note>

<Note>
  **Free, but gated.** `explain` costs no credits. It does run a real search
  under the hood, so it counts against your rate limit like any other call, and
  an organization that has run out of credits can't call it, the same answer
  `retrieve` would give.
</Note>

## On the drop-in proxy

The same tunable works on `/v1/chat/completions`, `/v1/responses`, and
`/v1/messages`. Set `receipt: true` in the request body, or send it once as a
header so a stock SDK never has to touch its request bodies:

```
X-Anona-Receipt: true
```

Body wins when both are set. A malformed header value comes back as `400
invalid_tunable`, never a server error.

Instead of a body field, the id comes back as a response header:

```
X-Anona-Receipt-Id: 3f9a2b7c-1e4d-4a6b-9c3e-8d2f1a0b5c7e
```

Fetch it the same way, from the same `GET /v1/receipts/{request_id}` endpoint.
On the proxy, `token_accounting` reflects what was actually injected into the
model call, the same rendering the proxy uses to build its own prompt, not a
separate estimate.

<Note>
  `receipt_detail` is retrieve only. Proxy receipts are always `"basic"`, so they
  report the cuts made after search returned and set `engine_stages` to `false`.
  `explain` still works on them: it replays the memory search the proxy ran, so
  you can account for a specific memory that never made it into a completion.
</Note>

## Isolation

A receipt can only be fetched by the organization whose API key made the
original call. A request for another organization's receipt id gets the same
`404` as an unknown or expired one; there's no way to distinguish "not yours"
from "doesn't exist."

The same rule holds one level down, for a space with per-space membership
enforced: only a member of the space the receipt names can fetch it, even
from inside the right organization. A colleague in the same org who isn't on
that space gets the same `404`, not a `403`, which would itself confirm the
receipt exists for a space they can't see.

## Pricing

Free. `GET /v1/receipts/{request_id}` reads something already computed and
already paid for at the time of the original call, so fetching it again costs
nothing.

## Error responses

| Status | Code                               | Cause                                                                                                                                                                                                       |
| ------ | ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 401    | `missing_auth` / `invalid_api_key` | Missing or invalid API key.                                                                                                                                                                                 |
| 404    | `receipt_not_found`                | No receipt with that id for your organization: never built, expired, or belongs to someone else. On `explain`, also returned for a receipt old enough to predate the endpoint, which has nothing to replay. |

See the full [error reference](/api-reference/errors).

## Next steps

<CardGroup cols={2}>
  <Card title="Retrieve API" icon="magnifying-glass" href="/api-reference/retrieve">
    Rank a space's memories against a query.
  </Card>

  <Card title="Chat completions" icon="comments" href="/api-reference/chat">
    The drop-in proxy that can build receipts for injected memory too.
  </Card>
</CardGroup>
