Docs

Receipts and evidence bundles

What a K-Block receipt is, when one is issued, what its .kbundle evidence bundle contains, and how it can be verified without trusting K-Block Cloud.

A receipt is a signed record of how a protected action ended. It points at the chain of signed objects behind the action — the request, the approval or rejection, the execution and its result — and comes with an evidence bundle (a .kbundle file) that anyone can verify offline.

Use receipts when you need to show, after the fact, who authorized a tool call, what exactly was authorized and what the Gateway recorded as the result. This page explains the concepts; to check a receipt step by step, see Verify a receipt.

What a receipt is

Every protected action is a thread of signed K-Block objects. A receipt is one more signed object, of type RECEIPT, that covers a path through that thread, from its first object (the TASK) to the object that ended it (the head). Its payload names:

  • the workflow the thread follows, its root and its head;
  • the IDs of every object it covers;
  • the identity document each signer used (pins);
  • the outcome, and a summary of the action.

The receipt is signed by the Gateway's service identity (see Identities and KIDs). Because every object is content-addressed and signed, changing any signed content in the bundle makes verification fail. The dashboard's receipt page also compares the bundle's SHA-256 with the value recorded when the receipt was issued.

When receipts are issued

A receipt is issued when an action reaches a final state that someone signed:

Final stateReceiptHead objectOutcome storedIssued by
succeededYesRESULTsucceededGateway
failed (upstream or Gateway failure)YesRESULTfailedGateway
failed with INTERRUPTED (recovered)Yes, when the action's evidence still verifiesRESULTfailedGateway or Cloud API
rejectedYesREJECTIONfinalCloud API
cancelledYesCANCELLATIONfinalCloud API
authorized (decision only)Yes: an authorization receiptPROPOSAL, or the person's APPROVAL when one was requiredpending or grantedGateway
deniedNo———
expired (APPROVAL_EXPIRED, TASK_EXPIRED, SUPERSEDED)No———
executingNot yet———

Denied and expired actions are recorded in the dashboard and the audit log, but no receipt is issued for them. (An expired action's TASK and PROPOSAL stay stored as the signed record of its decision; nothing is signed for a denial.)

For rejections and cancellations the stored outcome is final, the state of the REJECTION or CANCELLATION in the workflow. The dashboard shows the action's status instead (for example "Rejected") in the receipts list, in the receipt page's header and in the action page's receipt card; the receipt page's Outcome field shows "Final" with the action's status beside it. Filtering the receipts list in the Cloud API with outcome=rejected finds nothing, because the stored value is final.

For a decision-only action the receipt is an authorization receipt: it proves that K-Block authorized the exact call, not that anyone executed it, and it contains no EXECUTION or RESULT. Its stored outcome is the state of its head, pending for the PROPOSAL or granted for the APPROVAL, which the dashboard shows as Authorized (decision only). The Cloud API marks every receipt with its kind: execution, authorization or closure (rejected or cancelled).

Each new receipt sends a receipt.created webhook event, with the same kind (see Webhook events). The agent gets the receipt's IDs in _meta.kblock (receipt_id, receipt_object_id) on the answer that reports the outcome; a decision-only caller gets them in the answer of POST /v1/authorize.

How long K-Block Cloud hosts a receipt

K-Block Cloud hosts a receipt's bundle for the organization's retention period, counted from the end of its action. Then it deletes the bundle and the signed objects, and keeps a record of the receipt: its IDs, outcome, issuer, and the SHA-256 and size of the bundle. Asking for the receipt, its bundle or its decision check then answers 410 with the code EVIDENCE_EXPIRED: the hosted copy is gone, the receipt is not invalid.

The bundle itself does not expire. A .kbundle you downloaded carries everything a verifier needs, so it stays independently verifiable offline for as long as you keep it, and its SHA-256 still matches the record. Download the receipts you need to keep before the retention period ends.

What the evidence bundle contains

The .kbundle file is a JSON document:

{
  "kblock_bundle": "0.1",
  "receipt": { /* the signed RECEIPT object */ },
  "objects": [ /* the signed objects the receipt covers */ ],
  "identities": [ /* identity documents of every signer, and of the issuer */ ]
  // "disclosures": optional, revealed payloads of encrypted objects
}

The covered objects are the path from the TASK to the head, the delegations the thread acts under (and their revocations, if any), and attestations about objects on the path. For example:

How the action endedPath in the bundle
Allowed and executedTASK → PROPOSAL → EXECUTION → RESULT
Approved and executedTASK → PROPOSAL → APPROVAL → EXECUTION → RESULT
RejectedTASK → PROPOSAL → REJECTION
CancelledTASK → CANCELLATION
Authorized for the caller (decision only)TASK → PROPOSAL, or TASK → PROPOSAL → APPROVAL

A cancellation receipt covers only TASK → CANCELLATION. A PROPOSAL or APPROVAL that existed before the cancellation is not on that path, so the receipt says nothing about it.

Signatures and identities

Each object in the bundle is signed by the identity that acted:

ObjectSigned by
TASK, EXECUTION, RESULTThe Gateway's service identity, on behalf of the organization principal under its delegation
PROPOSALThe Gateway's service identity
APPROVAL, REJECTIONThe approver's personal identity
CANCELLATIONThe organization principal
Delegation, revocationThe organization principal
ATTESTATIONA K-Block-aware upstream server's identity
RECEIPTThe Gateway's service identity

The bundle carries the identity documents of every signer, so a verifier needs nothing else to check the signatures. What a verifier must decide for itself is whether it trusts the issuer: a bundle can be internally consistent and still have been issued by an identity other than K-Block's Gateway. That is why the dashboard compares the issuer with the Gateway KID it trusts (its pinned Gateway KID).

When you verify a bundle yourself, compare its issuer with the Gateway KID. The dashboard shows the Gateway KID right after you create a gateway API key. If it does not show it, contact K-Block support.

Storage and download

When a receipt is issued, K-Block verifies the bundle once more and stores it before it records the receipt. With the receipt, K-Block keeps the bundle's SHA-256 and size.

You can get the bundle in two ways:

  • In the dashboard, open the receipt under Receipts and select Download .kbundle.
  • Through the Cloud API: GET /api/v1/orgs/<org-id>/receipts/<receipt-id>/bundle. Members of any role can read receipts; a management API key needs the receipts:read scope.

The Cloud API serves a bundle only after checking its bytes against the recorded SHA-256. The response carries the hash in the x-kblock-bundle-sha256 header and is named receipt-<receipt object id>.kbundle. When K-Block cannot read the stored bundle, or the stored bundle no longer matches its recorded SHA-256, the Cloud API refuses it with HTTP 500, a problem on K-Block's side. The dashboard then shows "K-Block could not complete this request because of a problem on its side." with a request ID. You cannot repair a stored bundle yourself, and nothing in the dashboard repairs it: try again later, and if it continues, contact K-Block support and give them the request ID (shown in the dashboard's error, or in the response's x-request-id header).

K-Block never deletes receipts in this version: the plan's retention period applies to operational data only, never to receipts or the signed objects they cover. See Data retention.

Verification

Verifying a bundle checks every signature, every identity chain, the workflow rules (which object may follow which, and who may sign it), and that the receipt covers exactly the objects it names. There are three places to do it:

WhereWhat it trusts
Verification by the Cloud on the receipt pageThe Cloud API re-reads the stored bundle and checks its hash on every request, and verifies it (a report is reused for the same bytes while the trust inputs are unchanged). It trusts as attesters the server KIDs currently registered in the organization, and reports invalid if a signer is marked as equivocated
Verify in this browser on the receipt pageThe K-Block verifier runs in your browser, independently of the Cloud API's verification, and compares the bundle's SHA-256 with the recorded one. The result is VALID only if the Gateway identity the dashboard trusts (its pinned Gateway KID) issued the receipt and signed its thread, and the bundle is the receipt the page shows
Offline, with @kblockai/sdk/verifyYour own code: you choose the attesters to trust, and you compare the issuer the report names with the Gateway KID you expect. See Receipt verification SDK

The Receipts page also has Verify a receipt file: choose any .kbundle file (up to 16 MiB) and it is checked entirely in your browser; nothing is uploaded.

The in-browser check reports one of these verdicts:

VerdictMeaning
VALIDEvery check passed, and the Gateway identity the dashboard trusts (its pinned Gateway KID) issued the receipt and signed its thread
CONSISTENTEvery check passed against the identities inside the bundle, but the dashboard has no trusted Gateway identity to compare the issuer with, so the issuer is not established. Compare the issuer KID with the Gateway KID that K-Block gave you, and contact K-Block support if the dashboard keeps showing this verdict
NOT THIS GATEWAYThe bundle is consistent, but another identity issued it or signed its thread
MISMATCHThe bundle verifies, but it is not the receipt this page shows
INVALIDThe receipt did not verify, or its SHA-256 differs from the recorded one

Attestations

A plain upstream MCP server knows nothing about K-Block. For it, the RESULT is the Gateway's own record of what the upstream returned.

An upstream registered with a server KID is K-Block-aware. The Gateway sends it the evidence in _meta.kblock, and the upstream must answer with an ATTESTATION signed by its own identity. The Gateway verifies the attestation before recording the result; a missing or invalid attestation makes the execution fail (ATTESTATION_MISSING, ATTESTATION_INVALID). The attestation is bound into the RESULT and included in the bundle.

An attestation counts only for a verifier that chooses to trust its attester. The Cloud trusts the server KIDs registered in your organization; an offline verifier decides for itself.

What a receipt proves, and what it does not

A valid receipt shows who signed what, in which order, and under which delegation; that the approver signed exactly this proposal (arguments, tool, server, action, price, upstream URL); and that the result's terms equal the approved terms.

It does not prove:

  • the real-world effect of a call to a plain upstream — the RESULT is the Gateway's claim;
  • that the timestamps are true;
  • the tool binding (credential version, input schema, amount mapping), which only the Gateway checks, against the tool configuration K-Block has stored; it is not in the signed evidence;
  • anything about objects outside the covered path;
  • that the Cloud API and the Gateway are separate parties: both hold the Gateway's service identity and both sign receipts.

An INTERRUPTED receipt says failed even though the upstream outcome is unknown. Details: What a receipt proves.

Next steps