What leaves a receipt
Each block a context read adds with
include (constraints, state, coordination and budget) leaves a receipt of its own, beside the context’s, with the block’s version and counts, never a value; typed state reads and coordination checks and declarations land on the same trail, without the handle. Content releases, corrected or deleted inferences, review requests and legal holds come in as admin, with the action and the target.
Identity changes, erasures, exports, source cutoffs, pattern retractions, simulations and review verdicts land on the same trail as the reads; control plane changes also reach each space’s chain as admin receipts: every configuration applied (config.applied, with the snapshot version and the document digest; who proposed and who approved stays in the control plane’s versioned history), every key issued, updated or revoked (key.issued, key.updated, key.revoked, with the target key:<key id>:source:<source id>, which says whose key it is) and every change to people’s access and to SSO (invitations, roles, deactivations, second factor resets, SSO connections, people created and roles synced at an SSO sign-in), which the cell pulls from the control plane every 15 s through a signed feed and writes once in each of the tenant’s spaces, by ids only. The actor of those receipts is the control plane. The receipt of a person’s action keeps their id (person_id), and a read returns the e-mail beside it (person_label), which stays out of the chain, like the profile’s pseudonym (pseudonym): any role that reads receipts reads the name.
A denied receipt keeps who tried, on which surface (pack, search, timeline, open or object) and why, in detail.reason: scope_missing (the key lacks the route’s scope), forbidden (the source’s purpose does not cover that object type, navigation is off for the source, the MCP tool is not offered to it or the token belongs to another source), verification_not_allowed (the requested level is not valid for the source: above its ceiling, or no_customer outside an internal agent) or about_without_link (a read on behalf of an organization with no active link). What was asked never goes in: neither the customer nor the query. An unknown key never reaches a space’s chain, so nobody outside can fill it. On an admin receipt, detail keeps the action (action), the target (target, when there is one) and the roles of the person or the id of the key that acted.
What a receipt holds
manifest_hash points to the provenance manifest of that delivery, which records:
- every item included, with the event it came from and, when memory recorded it, whose words it is (
spoken_by:customer,mixedorcompany), whether it is the customer’s own words about themselves for this reader (own_words) and the rule that released it above the category’s minimum level (rule: mirror, the mirror); the recheck at delivery time decides on the same data as the compiler; - every exclusion, with its reason:
policy,verificationorquarantinewhen the policy held the item back,budgetwhen it did not fit the budget (with the rule that decided:budgetfor a valuable item that did not fit,low_valuefor one below the minimum value,skeletonfor a line waiting in the reserve for the turn’s slots,viewfor a section the view does not carry andduplicatefor an open item another one already says or a fact a conversation pinned in the skeleton already says) andunusedwhen the source does not use that kind of item (A leaner context per agent); - the versions of the compiler, template, policy and extractor;
- tokens per section and the prompt-cache decision.
The slots receipt
A read withquery leaves a second read receipt, on the slots surface, with what the turn selected from memory. detail.slots is the slots manifest, as canonical JSON:
budget, the tokens the turn’s slots could add up to, andbudget_rule, the rule that set that budget:question, when the turn asked for a value (a number it names or a kind of value), a count or a day, orturn, on any other turn. In voice,questionmeans 100 tokens and up to three lines,turn60 and two; in the other views the rule is recorded and the budget is the view’s own;tokens, what the slots added up to, anditems, each slot withid,kind,channels,position,tokensand, for a value,value_type;derived, the kinds of the derived lines;channels, per channel, how many hits it had and whether it missed its deadline (skipped); andguards, when there were any, withid,value_typeand the control group’sarm.
detail.why says why each slot came in. Every item carries id, the position and contribution of each channel, the fused score and, when they hold, excerpt: true (the line was cut to the sentences that answer the turn) and atom: true (the conversation entered as its atom: the matter and the value that answer the turn, in one short line). Both appear only when true, and a line is never both. A derived line carries rule, the rule that wrote it, and what it counted: the count by the matter the extraction named writes count_matter or count_matter_complaints, with units, matched, matters (how many matter keys entered the count), counted and window_days; the count by words, count_topic or count_topic_complaints, with units and matched. A promise kept in the last 7 days, which the context keeps among the open items saying when it was kept, enters the manifest like any open item: an open_item entry of the pending section, with no field of its own; what sets it apart is the context line. None of it keeps the words or the number: the same turn over the same memory writes the same manifest. The POST /v1/context answer with explain: true carries in pack.slots[].why the same position, contribution and score and the excerpt; budget_rule and atom are in the receipt only.
Navigation receipts also record the query, as an HMAC under the space’s key, the ids of the returned items and how many were withheld. A search or a timeline read with filters also keeps an HMAC of the filters as sent, under the same key, in detail.filters_hash: the receipt proves which filters were asked without keeping what they say, and detail is chained like the rest.
The chain
Receipts are append-only. Each one carries its position (seq) and the hash of the previous receipt (prev_hash), chained per space, per slice and per day. detail is hashed like the rest; a receipt without detail has the hash it always had, so older receipts still verify. A small space has one slice; a large one has up to 16, chosen by the profile, so the busiest space still writes without a single bottleneck.
Once a day, when every receipt of that day is written, the heads of the slices are combined into a Merkle root. The root is kept in the cell’s database and in a copy outside it, in write-once storage (S3 with Object Lock in compliance mode): the copy is written only if it does not exist yet and is retained for five years, and nobody, the cell included, can delete or replace it before then. Changing any past receipt breaks every hash after it and no longer matches the root, and rewriting receipts, heads and the root in the database at once still no longer matches the copy.
The audit export: the root in your own chain
Niadra keeps no second audit chain for what the agents did: it hands its own to yours. The dailyaudit.root event, by webhook or through the notifications feed, carries the root of the day’s receipts and, in a space that records turn records, the root of the kept tier’s index (turns.root, with turns.count): each turn kept on the day has a row (the turn id, the source, the agent, the kind, the time, the content mode and the build hash) with its SHA-256 digest, and the digests, in turn id order, fold into a Merkle root with the RFC 6962 prefixes, an odd node carried up on its own. Only digests and counts leave: never what a turn read or said.
GET /v1/turns/index/{day} lists the rows of one UTC day with their digests, by cursor, so your company recomputes turns.root, and GET /v1/receipts/verify recomputes the receipts’ root, to check both and anchor them wherever it wants. A row that expires or is erased later leaves the root anchored as it was: the root proves what the day had, not what still exists. The index needs a key with the replay scope or a person with the integration or security role, and the turns feature. The receipt.recorded webhook delivers the chain links (slice, day, seq, prev_hash and hash) as soon as the writer stores them, for those who prefer to keep their copy on their own clock. Neither carries what was read.
No read leaves without a receipt. The queue confirms the receipt within 20 ms; when it does not, the receipt goes to the cell’s database as a write task, which writes it to the chain at least once and never twice, because the receipt id is unique. Only when both paths fail does it wait in the process memory; once that backlog is full, sensitive categories stop going out, and at twice that size reads pause until the receipts are written.
A quick continuity check over one page of a slice:
GET /v1/receipts/verify?day=2026-09-22 walks every slice of that day and answers valid, the count of receipts, the heads of each slice, any breaks it found, whether the recomputed root matches the anchored one (root_matches), whether the copy outside the database holds the same root (anchor_copy_matches) and whether that copy sits in write-once storage (anchor_locked). valid is true only with no breaks and no mismatch of the root or the copy. While the day is not anchored, those three fields are null; anchor_copy_matches is also null when the copy cannot be read at that moment, and false when it is missing or holds another root.
The receipt routes are governance routes: a Console person with the security role, or a key with the admin scope. Filter the list by profile_id, conversation_id, source_id, kind and a since/until window, and page with cursor, in the body of POST /v1/receipts/search: a conversation id may be a phone number or an e-mail, and it stays out of the URL. GET /v1/receipts takes the same filters in the query and stays. Each receipt in the answer carries, next to profile_id, the profile’s pseudonym, the name to show a person; it comes from the profile at read time and is not part of the chained receipt.
Three questions receipts answer
Why did the agent say X? At 2:07 pm the voice agent told Marina the $40 credit was already applied. The receipt ofcall-4471 lists, among the delivered items, the id of the billing agent’s 2:06 pm action (0192f7a2-9d52-7f4b-8a6e-3d5f7a9b1c24); the manifest shows it came from the credit action on invoice:erp:0823, confirmed by the ERP event. Receipt, manifest, item, event: four steps from the sentence to its source, and GET /v1/lineage/receipt/{receipt_id} walks them for you, returning the receipt, the delivered items with their origin and the exclusions.
Who saw fact F? GET /v1/lineage/fact/{fact_id} returns every receipt whose delivery carried that fact: each source that received it, with purpose, level and time.
What was withheld, and why? withheld says how many items the policy held back; the manifest says which reason applied to each (policy, verification or quarantine). The agent learns only the count, never the content, so it knows that verifying the customer releases more. The opposite question has an answer too: an item a reader at V1 saw in a category that asks for V2 carries rule: mirror in the manifest, the proof that it saw what the customer said themselves, not what the company holds about them.
Receipts in your SIEM
Receipts reach your SIEM seconds after they enter the chain, through the same delivery mechanism as webhooks. The stream is an endpoint in thewebhook-endpoints configuration document, changed by an approved diff, with a format:
ocsf: one OCSF 1.3 API Activity event (class_uid6003) per receipt, one JSON per line (application/x-ndjson);hec: the same event inside an HTTP Event Collector envelope (time,source,sourcetypeniadra:ocsf:api_activityandevent), also one per line.
event_types takes receipt kinds (read, history, analytics, admin, holdout, denied, agent_memory) or * for all. The signing secret and, when the collector asks for one, the token sent in the Authorization header (such as Splunk <token>) are written to the cell’s vault with PUT /v1/secrets/webhook/{id}, and the document points to them as spaces/<space id>/webhook/<id>, in secret_refs and in authorization_ref. Every secret reference (secret_refs, authorization_ref, an export’s credential_ref, a mapping’s secret_ref) must sit under spaces/<your space>/: the vault is the cell’s, shared by the spaces it serves, and the cell refuses a reference to another space at use (the delivery fails as secret_outside_space, the webhook answers 403, the export and the credential write are refused), and the control plane refuses a new document that names one.
webhook-id, webhook-timestamp and webhook-signature, over the raw body) and has the same retries for 24 hours and the same dead-letter queue, with a resend in the Console. To check the stream before you rely on it, a test delivery sends one OCSF event marked with unmapped.niadra.test, outside the chain. A SIEM stream is never the target of a warning rule: the control plane refuses the rule. The event says who read (the source or the person), the surface or the action, the profile and the object by id, never by phone or email, the purpose, the level, the policy version, whether the read was refused (status Failure, with the reason in status_detail) and the position on the chain in unmapped.niadra.chain (slice, day, seq, prev_hash and hash), so your SIEM can check the chain on its own. Like the receipt, it never carries what was read.
The continuous export also writes the receipts table to your bucket, at the frequency of its schedule.
Receipts and erasure
When a data subject is forgotten, read receipts are not rewritten: they already hold only ids and hashes, so they prove a read happened without keeping what was read. The erasure itself leaves anadmin receipt, the erasure receipt, which lists what was erased and the export runs that already contained it. Measurement rows follow the retention and policy of the receipt they belong to.
Next steps
Privacy, erasure and export
the erasure receipt and the lineage it walks.
Context use
each measured delivery carries the manifest hash its read receipt records.
Context and views
withheld items and the verification levels.
Search receipts
the endpoint in detail.

