---
title: "Receipts"
description: "Every authenticated tool call gets an Obol idempotency key and a gateway-minted receipt recording what policy allowed, what dispatch observed, and how much that evidence is worth."
---
Invariant 7: every tool call gets an idempotency key and a receipt. The receipt names the policy that decided the call, the safe upstream id the vendor returned, the vendor status, and the trust class of the evidence behind all of it.
The gateway is the only receipt authority ([ADR-0019](/concepts/invariants)). No connector, no worker, no catalog broker, and no control-plane consumer mints a receipt or assigns an evidence class. Control reads receipts off a Redis stream and projects them into Postgres; it copies fields and drops malformed envelopes rather than repairing them.
## What a receipt answers
A receipt answers three separate questions, and keeps them separate on purpose:
1. **Was this allowed?** The authorization summary names the policy pack, its revision, the decision, the matched policies, and the approval that unblocked it.
2. **What did dispatch observe?** The dispatch summary carries the vendor HTTP status, the vendor operation status, the safe upstream id, a response digest, a typed error code, and whether the failure was retryable.
3. **How much is that worth?** The verification summary carries a conclusion, the effect claim it supports, and the evidence trust class that supports it.
The first question is tier-independent. The third is not. A native route the gateway executed itself can produce `gateway_observed` evidence; a federated catalog route cannot, and its evidence is `untrusted` or `broker_attested`. "Receipts prove what a tool call actually did" is true natively and false on a federated route. The receipt always names the class — read it. See [Evidence trust](/receipts/evidence).
## One invocation, many receipts
The gateway mints an `InvocationId` before the visibility and admission checks run, and keeps it across approval, transport retries, and safe redispatch. That id is the stable identity of one logical tool call.
A `ReceiptId` is not. Each revision of a receipt gets a fresh `rcp_` id and an incremented `receipt_revision`, so an invocation whose verification conclusion changes later — a readback landed, a signed webhook arrived — produces a new receipt in the same history. Listing endpoints show the latest revision per invocation; the revisions endpoint returns the immutable history.
A revision is minted only when the public verification summary changes. Evidence attempts that leave the summary unchanged advance the evidence sequence and emit a `VerificationEvent`, not a receipt ([ADR-0046](/concepts/invariants)).
## Fields
| Field | Meaning |
|---|---|
| `invocation_id` | Stable identity of the logical call, minted pre-admission |
| `receipt_id` / `receipt_revision` | This revision. The id changes per revision; the invocation id does not |
| `workspace_id`, `key_id`, `request_id` | Tenant, virtual key, inbound request |
| `tool` | Fully namespaced `.` |
| `connection_id` | The workspace's connector instance, or null on a hidden or unresolved call |
| `request_hash` | Canonical hash of the arguments. A conflicting hash under the same idempotency key never dispatches |
| `idempotency_key` | The `idt_` key for this call. See [Idempotency](/receipts/idempotency) |
| `state` | `awaiting_approval`, `rate_limited`, `not_ready`, `denied`, `in_flight`, `completed`, or `inconclusive` |
| `attempt_count` | Durably reserved attempts, so the receipt cannot disagree with the number of outbound calls |
| `authorization` | Policy id, revision id, decision, matched policies, approval id |
| `dispatch` | Dispatch state, evidence trust, vendor status, safe upstream id, response digest, error code, retryability |
| `verification` | Conclusion, method, effect claim, evidence trust, verifier version, evidence digest, assertion outcomes |
| `route` | The route decision record, on a routed `cap.*` call only |
| `latency_ms`, `usd_micros` | Observed latency and reserved cost |
Raw response bodies, credentials, request headers, and compared assertion values never enter a receipt. Assertion results carry a name and an outcome, never the value that was compared.
## An example
A native Stripe refund, dispatched and observed by the gateway:
```json Receipt
{
"schema_version": 1,
"receipt_id": "rcp_0000000000000000000000000000001",
"receipt_revision": 1,
"invocation_id": "inv_0000000000000000000000000000001",
"workspace_id": "ws_acme_prod",
"key_id": "key_support_live",
"request_id": "req_0000000000000000000000000000002",
"tool": "stripe.create_refund",
"connection_id": "conn_stripe",
"request_hash": "3f9c4e2b1a7d8c6e5f4a3b2c1d0e9f8a7b6c5d4e3f2a1b0c9d8e7f6a5b4c3d2e",
"idempotency_key": "idt_00000000000000000000000000000002",
"state": "completed",
"attempt_count": 1,
"authorization": {
"policy_id": "pol_support_v1",
"revision_id": "rev_0001",
"decision": "allow",
"matched_policies": ["support-refund-cap"],
"approval_id": null
},
"dispatch": {
"state": "accepted",
"evidence_trust": "gateway_observed",
"vendor_http_status": 200,
"vendor_operation_status": "succeeded",
"upstream_id": "re_123",
"response_digest": null,
"error_code": null,
"retryable": false
},
"verification": {
"state": "inconclusive",
"method": "none",
"verifier_version": null,
"evidence_digest": null,
"assertions": []
},
"latency_ms": 412,
"usd_micros": 10000,
"created_at": "2026-08-28T12:00:02Z",
"updated_at": "2026-08-28T12:00:02Z"
}
```
Read the two `evidence_trust` fields separately. `dispatch.evidence_trust` is the provenance of the transport facts; `verification.evidence_trust` is the provenance of whatever evidence produced the conclusion, and it is absent when nothing did.
## Which calls get a receipt
Every authenticated `tools/call` does, including calls that stop before dispatch. A denied call, a rate-limited call, and a call held for approval each finalize with a receipt whose `state` says so and whose `dispatch.state` is `not_attempted`.
Hidden and unknown tools follow [ADR-0015](/gateway/mcp)'s non-revealing client behavior — the caller learns nothing about a tool the key cannot see — while still producing an owner-visible receipt. That is the point: the audit log is for the workspace, not for the agent.
## How a receipt reaches you
The gateway writes an `InvocationAuditEnvelope` — one receipt paired with its verification event — to a workspace-qualified Redis stream, and acknowledges it there before exposing receipt metadata. A control-plane consumer drains that stream per workspace, projects each envelope into a queryable row, and keeps the whole envelope beside it.
Delivery is at-least-once and revisions can arrive out of order, so the projection is a conditional upsert that only moves forward: an incoming revision must be strictly greater than the stored one, and the stored workspace must match. Pending deliveries are reclaimed with bounded `XAUTOCLAIM` pages from a persisted scan position, and each drain commits before acknowledging ([ADR-0042](/concepts/invariants)).
## Reading receipts
Receipts are exposed through the control plane, scoped to a workspace and gated on the reader's operator role.
```bash List receipts
curl "https://control.tryobol.dev/api/v1/workspaces/ws_acme_prod/receipts?limit=50&verification_state=inconclusive" \
-H "Authorization: Bearer $OBOL_OPERATOR_TOKEN"
```
You can filter by invocation `state`, `evidence_trust`, `verification_state`, `decision`, `tool`, `key_id`, `connection_id`, and a UTC window. Pagination is a versioned opaque cursor over `(occurred_at, invocation_id)` descending; a malformed cursor is rejected rather than silently reset to now. Legacy RFC3339 `before` values are still accepted as timestamp bounds.
`GET /workspaces/{workspace_id}/receipts/{receipt_id}` returns the detail projection plus the whole envelope the gateway wrote, and `.../revisions` returns the immutable revision history.
## Next
The five trust classes, which tier can reach which, and why `verified` is a conclusion rather than a class.
Obol-minted keys, capability scope, retry safety, and disabled broker retries.
Response bindings, verification plans, readback probes, and per-tier limits.
Signed vendor events as evidence, and how a subscription is provisioned.