Skip to main content
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.
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.
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.

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.
The response is unchanged except for one added field:
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.
"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.
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.

Fetch a receipt

Response 200 OK
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.
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)

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

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.
Response 200 OK

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

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

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

See the full error reference.

Next steps

Retrieve API

Rank a space’s memories against a query.

Chat completions

The drop-in proxy that can build receipts for injected memory too.