Skip to content
Beta. This surface may change before GA; breaking changes are dated in the changelog.

Receipts

Every read — /context, /search, /query — returns a receipt id:

The receipt snapshot includes branchId. For a branch read, this is the branch’s immutable identity and the head served at read time. Pass ?branch=<branchId> to receipt listing, inspection, now, pinned-document, and who-was-served reads when using a main-scoped key. A branch-scoped key is limited to its own receipts, even if it guesses a receipt id from main.

One Brain · chain of custodyIllustrative lifecycle
01Sources
Records
Documents
Agent writes
02Review
ProposedHuman or policy decisionAccept · edit · reject
03Context
Accepted stateCited answer[1] source passage
04Receipt
Verifiedrcp_019fWhat the read consumed
{
"data": {
"text": "Acme renewed at $120k for 24 months on 2026-03-14.",
"citations": [
{
"source": "hubspot:deal:412",
"slug": "brain-evidence-0dd3e2449851d9ba41924c42bd7a61375a8b78050bfe2ca7192eb918d626236a",
"quote": "Acme renewed at $120k for 24 months."
}
],
"staged": 0,
"warnings": [],
"receipt": "rcp_019ffefe",
"truncated": false
}
}

Log it next to whatever your agent said. It is the difference between “our model probably had the renewal note” and “here is the exact version of the renewal note it was given, at 11:02 on 14 August.”

Every serve is its own receipt, with its own time and consumer. The same request made twice, against the same state, returns two receipts, so a listing by time window and who was served count each read. Retrying one request after a dropped connection is a new read.

A receipt records which key made the read — key — and that binding is what makes it evidence rather than a log line anyone could have written. It never changes, whoever reads the receipt back. Fetch a receipt with the Brain key that made the read, or with a key that holds review or admin — your management key does. Who was served needs review and hands back receipt ids, so a reviewer can open the receipts it lists; an operator auditing their own Brain is the reader receipts are for. Any other key gets a 404, the same answer a wrong id gets. Receipts outlive their Brain: after DELETE /v1/brains/{handle} the management key still fetches the receipt (a Brain key answers 404 once its Brain is deleted, as it does for every read). What changed since and the text it pinned need a live Brain, and answer 404 once it is deleted.

Terminal window
curl "$API/v1/brains/tenant:acme/receipts/rcp_019ffefe" \
-H "authorization: Bearer $NICIA_BRAIN_KEY"
{
"data": {
"receipt": {
"id": "rcp_019ffefe",
"kind": "context",
"consumerId": "user:4471",
"key": { "id": "key_7f3a1c", "name": "Acme web app" },
"snapshot": {
"brainId": "brain_3a91c7",
"generation": "6f1e4d6b-9f0c-4a21-8f3e-1f1a2b3c4d5e",
"stateVersion": "sv_019ffefe",
"schemaVersion": "nicia-base",
"policyVersion": "pol_nicia_open"
},
"committedAt": "2026-08-14T11:02:44Z",
"items": [
{
"source": "hubspot:deal:412",
"version": 2,
"status": "active"
}
]
}
}
}

items pins the exact version of every source. A document of a named source also carries document: { source, id }. If the document has been rewritten twice since, or retired, the receipt still names the version that was served, and the key that made the read can still fetch the text that version held.

kind says what the read was: context, search, query, plan (a snapshot plan page), or document (a read of the text an earlier receipt pinned).

Same scoping: the key that made the read, or a key with review or admin.

Terminal window
curl "$API/v1/brains/tenant:acme/receipts/rcp_019ffefe/now" \
-H "authorization: Bearer $NICIA_BRAIN_KEY"
{
"data": {
"changed": [
{
"source": "hubspot:deal:412",
"shownVersion": 2,
"currentVersion": 4,
"status": "superseded"
}
],
"unchanged": 3,
"deleted": 0,
"evidence": [],
"answers": []
}
}

This answers the question that actually comes up in an incident: the agent told a customer $120k — was that wrong at the time, or has it changed since? Here it was right when it was said.

changed[] names versions rather than text: currentVersion is the version the Brain serves now, and a document that was retired reports deleted. The text the receipt pinned is its own read. No read returns a document’s current text on its own — GET /documents/{id} reports its status and version, and /context and /search serve its passages. Re-sending a document under the same id counts — it replaces the document, and every receipt that cited the old version reports superseded from that moment. A cited source can also report held: under a legal hold, every item a receipt cited reports held rather than superseded or unchanged, so a hold is never mistaken for “nothing changed.”

A version number can stay the same while what it rests on moves. For every standing answer a receipt served, answers[] compares the validity it was served with (as the receipt recorded it) with its validity now. since is one of:

  • grounds_moved: something the served version rests on moved after the read. This includes an unchanged version whose claim changed, whose quoted source text changed, whose source was retired, or that gained new evidence. movedSince names each reason.
  • replaced: a new version was accepted.
  • unchanged: nothing monitored moved after the read. An answer served while already needs_review stays unchanged if nothing further moved.
  • unknown: the receipt recorded no validity (it predates validity), or the check cannot tell now.

It reports what changed after the read, never that the answer was wrong when it was served.

{
"data": {
"changed": [],
"unchanged": 2,
"deleted": 0,
"evidence": [],
"answers": [
{
"id": "refund-answer",
"shownVersion": 1,
"currentVersion": 1,
"since": "grounds_moved",
"servedValidity": {
"state": "current",
"reasons": [],
"affirmed": { "position": 9, "at": "2026-09-28T09:00:00.000Z" },
"monitored": {
"claims": ["refund-window"],
"sources": ["refund-policy"]
},
"observedThrough": { "position": 9, "at": "2026-09-28T09:00:00.000Z" }
},
"validity": {
"state": "needs_review",
"reasons": [
{
"kind": "input_changed",
"recordId": "refund-window",
"changed": { "position": 14, "at": "2026-09-28T10:30:00.000Z" }
}
],
"affirmed": { "position": 9, "at": "2026-09-28T09:00:00.000Z" },
"monitored": {
"claims": ["refund-window"],
"sources": ["refund-policy"]
},
"observedThrough": {
"position": 15,
"at": "2026-09-28T11:00:00.000Z"
}
},
"movedSince": [
{
"kind": "input_changed",
"recordId": "refund-window",
"changed": { "position": 14, "at": "2026-09-28T10:30:00.000Z" }
}
]
}
]
}
}

A receipt names versions; this read returns what they held. It answers the full text of every document version the receipt pinned, in the receipt’s own order, even when the document has since been rewritten or retired — the passages the answer quoted came from that text.

Terminal window
curl "$API/v1/brains/tenant:acme/receipts/rcp_019ffefe/documents" \
-H "authorization: Bearer $NICIA_BRAIN_KEY"
{
"data": {
"documents": [
{
"source": "hubspot:deal:412",
"version": 2,
"contentHash": "sha256:4be1f07d5b8a3c1e9f2a6d0b7c4e8a13f5d9b2c6e0a7f4d1b8c3e6a9f2d5b0c7",
"text": "Acme renewed at $120k for 24 months. Signed 2026-03-14."
}
],
"receipt": "rcp_019fff2a"
}
}
  • Only through the receipt, only for its key. The key that made the read, or a key with review or admin, gets the text; any other key gets 404. A version no read was ever served — a snapshot’s document that was never applied — is pinned by no receipt, so there is nothing to read it through.
  • Exact or nothing. contentHash is sha256: and the digest of text. Stored content that no longer matches the version it was written as fails the read with an error; it is never served in the pinned version’s place.
  • Documents only. Records a receipt pinned are not included: a record’s earlier values are not served, and the record read returns its current one.
  • A read of its own. Handing text back is content leaving the Brain, so each page commits a receipt of kind document over the versions it returned and answers its id as receipt. It takes a consumerId like any other read.
  • Paged. Up to 25 documents per page, 10 by default (limit); walk the rest with cursor=<nextCursor>.

Pass a consumerId on any read — /context, /search, or /query — and it is recorded on the receipt:

Terminal window
curl -X POST "$API/v1/brains/tenant:acme/context" \
-H "authorization: Bearer $NICIA_KEY" \
-H "content-type: application/json" \
-d '{ "prompt": "…", "consumerId": "user:4471" }'

consumerId is a label you choose — user:4471, job:nightly-scoring, session:abc — 1 to 512 characters; empty or longer is a 400 naming the field. It is correlation only. It never restricts what a call can reach, so scoping still comes from the key.

List receipts newest first, without their items. Filter by consumerId, kind, key, and a committed-time window — since inclusive, until exclusive, so adjacent windows never count a receipt twice. A key lists the receipts it committed; a key with admin lists every key’s, or one key’s with key. review opens a receipt it is handed the id of, but does not list other keys’ receipts. That is how a batch eval walks exactly the reads one run made:

Terminal window
curl "$API/v1/brains/tenant:acme/receipts?consumerId=job:nightly-scoring&since=2026-09-01T00:00:00Z&until=2026-09-02T00:00:00Z" \
-H "authorization: Bearer $NICIA_BRAIN_KEY"
{
"data": {
"receipts": [
{
"id": "rcp_019ffefe",
"kind": "context",
"consumerId": "job:nightly-scoring",
"key": { "id": "key_7f3a1c", "name": "Scoring job" },
"snapshot": {
"brainId": "brain_3a91c7",
"generation": "6f1e4d6b-9f0c-4a21-8f3e-1f1a2b3c4d5e",
"stateVersion": "sv_019ffefe",
"schemaVersion": "nicia-base",
"policyVersion": "pol_nicia_open"
},
"itemCount": 3,
"committedAt": "2026-09-01T02:14:09Z"
}
],
"nextCursor": "rcp_019ffefe"
}
}

A receipt is identified by what the read served, not when it happened. A read that serves exactly what an earlier read served, under the same request, returns the earlier receipt and its original committedAt rather than a new one, so a since window and the who-was-served reads below list each distinct serve once. A job that reads unchanged state every day appears in these listings on the day it first read it. Count reads from your own consumerId log, not from receipt counts.

Each receipt’s items are GET /receipts/{id}, and the text its documents held is GET /receipts/{id}/documents — so an eval can pin every outcome to the exact context it was given, even after the documents changed. Up to 250 receipts per page; walk the rest with cursor=<nextCursor>. A listing carries no content: nothing a read handed out comes back through it.

After a correction, the question is who was told the old version. Ask the record, with a key that holds review:

Terminal window
curl "$API/v1/brains/tenant:acme/records/acme-co/reads?stale=true" \
-H "authorization: Bearer $NICIA_BRAIN_KEY"
{
"data": {
"reads": [
{
"receipt": "rcp_019ffefe",
"kind": "query",
"consumerId": "user:4471",
"key": { "id": "key_7f3a1c", "name": "Support bot" },
"committedAt": "2026-08-14T11:02:44Z",
"servedVersion": 1,
"currentVersion": 2,
"status": "superseded"
}
]
}
}

Every receipted read that served the record, from any key, newest first, with the version it served, the version the Brain serves now, and status: current, superseded, retracted (a retracted statement), deleted (a deleted record or a retired document, with no currentVersion), or held under a legal hold. A record’s version numbers restart when it is deleted and created again at the same address, so a read of the deleted record is deleted even though the address serves again: currentVersion is absent and recreatedVersion names the new record’s version, which is not a later version of the one served. GET /receipts/{id}/now reports the same item as deleted with recreatedVersion. stale=true keeps only the reads whose version is no longer current — a correction’s blast radius. The same read exists for a document, GET /documents/{id}/reads, and for a document of a named source, GET /sources/{name}/documents/{id}/reads. Reading the text a key reads back through a receipt is a read too, of kind document, so it appears here beside the read that pinned it.

This is metadata: receipt ids, keys, consumers and version numbers, never field values or text. It needs review because it describes every key’s reads; the content stays behind the receipt reads above.

When something turns out to have been wrong, write the correction — same document id, or a new warranted fact — rather than quietly forgetting the old number. The next /context is current. The receipt from the old read is stale: GET /receipts/{receiptId}/now reports superseded, and ?stale=true on the record’s or document’s reads lists every receipt that was served the old version, with its consumer and key — who needs to hear about it. To learn that a correction landed without polling each address, follow the event stream: record.accepted, record.deleted, statement.retracted, document.served and document.retired name the address that changed, which is the address to ask for its reads. Reads themselves are not events; this page is where they are.

A receipt records what was served; the context check grades it. In the product, open a served context and run the check: it reports where the sources behind that answer contradict each other (each contradiction finding is judged by a live model before it is shown), where the same wording was sent twice, and where the payload spent budget on words that carried no information.

The check is a report, run after the fact against a payload you already served — it is not a gate in the read path, and nothing blocks or rewrites an answer. It runs on the knowledge you have now, from the receipted reads you have already made: reads served over the API, over MCP, or under a Brain key. Reading a Brain by clicking around the console is not one of them and mints no receipt, so a Brain nobody has queried through a credential yet has nothing here to grade. Once one has, there is nothing to migrate before finding out what your agents are actually reading.

Receipts are retained indefinitely — nothing deletes one. They survive a document’s retirement (a document is never deleted) and Brain deletion, and outlive the Brain they are evidence about — an answer you gave a customer stays explainable after they churn. GET /receipts/{id} keeps answering for the management key after the Brain is deleted. The Brain export carries them as receipts.json: every receipt the exporting key can list, with its snapshot, request and render hashes, and the version of every source it pinned — but not the text a read was handed, which stays behind the receipt reads.

  • Review — deciding what lands in the first place.
  • Context — where receipts come from.
  • Events and webhooks — what changed, to find the reads a change made stale.