# Evidence on request: `GET /evidence/{jti}` on the seller (v0.3 and v0.2, 2026-09-25; v0.1, 2026-09-13) The chains a purchase in AP2 v0.2 form presents (`mandate-ap2-profile.md`) live in no ledger: the ledgers carry digests, the root's `sd_hash` on the authority's `mandate.issued` and the hop's `reference` on the seller's and the buyer's entries. A verifier that wants to walk the chains themselves needs them from a party that has them, and in the AP2 model that party is the merchant (or its PSP), which retains the mandate for dispute retrieval by transaction id. This document is that retrieval, shaped for an auditor: the seller keeps what it received for a retention window and serves it to an allow-listed verifier on a signed request. Nothing is copied by hand and nothing is published. What makes serving it safe is what the ledgers already committed: a verifier holding the three ledgers checks the root `sd_hash` against the authority's entry and the hop reference against the seller's and the buyer's before believing a byte of what it was served, so the seller cannot substitute a chain. What makes it private is the request: the chains reveal the person's pseudonym (`sub`), the line item and the amount, and go to an auditor, not to a page. ## The request ``` GET /evidence/{jti}[?issuer=] Authorization: Bearer ``` `{jti}` is the pair's `jti` (`ap2_jti` on the authority's and the buyer's entries, `jti` on the seller's), 16 to 128 characters of `A-Z a-z 0-9 . _ ~ -`. `issuer` names whose `jti` it is (since v0.2). An issuer chooses its own `jti`, so the seller keeps each purchase under its issuer and its `jti`, and the same `jti` from two issuers is two purchases. Absent, the purchase is a listed authority's (`MANDATE_ISSUERS`), and a call written before v0.2 gets the answer it always got for such a purchase; naming an authority on the list is the same question. A guest's purchase over UCP (`guest-buyer.md` §7) is asked for with the origin the seller's entries name as its `mandate_issuer` (`https://` and the host of its profile URL), percent-encoded; a trailing `/` is ignored. The token below does not change: `mandate_jti` names the `jti`, the query names whose, and a verifier on the allow-list may read either. The bearer token is a compact JWS under `jws-policy.md` (ES256, `kid` = the RFC 7638 thumbprint of the signing key, no `jku`/`x5u`/`jwk`/`x5c`/`crit`), with: | Header / claim | Value | |---|---| | `typ` | `evidence-request+jwt` | | `kid` | the thumbprint of the verifier's key, published at `/.well-known/jwks.json` (the verifier's report key, the one that signs its scorecard) | | `iss` | the verifier's base URL; must be on the seller's `VERIFIERS` allow-list (a policy decision, as `MANDATE_ISSUERS` is for authorities) | | `aud` | the seller's base URL | | `iat` | now, within 60 s of the seller's clock | | `exp` | at most 120 s after `iat`, not past | | `jti` | a nonce of 8 characters or more; one use, kept in the seller's replay cache under the signer's `kid` | | `mandate_jti` | equal to `{jti}` in the path | The key set is fetched by domain and cached; an unknown `kid` triggers one refresh, as for an authority. ## The answer `200` with `cache-control: no-store` and the document, the same shape the buyer writes with `agent-buyer buy --export-mandates` plus who served it and until when it is kept: | Field | Value | |---|---| | `format` | `ap2-v0.2` | | `correlation_id`, `jti`, `issuer`, `seller`, `quote_id`, `quote_hash`, `checkout_hash` | the purchase's; `checkout_hash` is the base64url SHA-256 of the checkout the closed Checkout Mandate discloses: the quote JWS on the A2A face, the checkout session's merchant authorization over UCP (`ucp-profile.md` §4), `null` on a UCP refusal before check 7 established it (v0.3). Before v0.3 a UCP refusal named the quote JWS's, which is not what its chains close | | `presentations` | `{checkout, payment}`: the two chains exactly as the buyer presented them | | `references` | `{checkout, payment}`: the base64url SHA-256 of each closed-mandate JWT, the values the seller's ledger and receipts name | | `closed_at` | the hop's `iat`: when the buyer closed the chains, so a verifier replays them as of that moment (a mandate is good for 300 s) | | `receipts` | `{checkout_receipt_jwt, payment_receipt_jwt}` on a completed purchase; `{checkout_receipt_jwt}` with `status: Error` on a refusal; `null` when the money question stayed open | | `outcome` | `completed`, `refused` or `ambiguous`; with `reason`, `step` and `ap2_error` on a refusal | | `payment_intent_id`, `receipt_jti`, `ledger` | the intent, the seller's receipt and the entry hashes (`intent_entry_hash`, `completion_hash`; `refusal_entry_hash`; `ambiguous_entry_hash`) the verifier joins on | | `served_by`, `recorded_at`, `retain_until` | `seller`, and the window in seconds since the epoch | ## Refusals | Code | Status | When | |---|---|---| | `evidence_request_invalid` | 401 | no token, not a compact JWS of the right `typ`, a `kid` the key set does not carry, `aud`/`iat`/`exp`/`jti`/`mandate_jti` wrong, or a replayed `jti` | | `header_forbidden`, `typ_mismatch`, `signature_invalid` | 401 | the JWS policy, as everywhere | | `verifier_not_allowed` | 403 | `iss` not on `VERIFIERS`, or the list empty | | `verifier_unavailable` | 503 | the verifier's key set could not be read; failing closed | | `evidence_not_found` | 404 | no purchase in AP2 form presented that `jti` under the issuer asked for (a listed authority when `issuer` is absent), or the retention window passed | ## What is kept, and for how long A purchase is kept once the authority's signatures on its roots held: completed, refused at checks 4 to 10 (with the refusal on it: an audit of a refusal needs the same material), or left open by the rail (`ambiguous`, overwritten by the retry). A refusal is kept only when nothing is kept under that `jti` yet: a verifier's public archive carries the chains of the purchases it read, anyone can present them again, and the refusal of that replay never replaces what the purchase left. A presentation refused before any `jti` could be attributed is not kept, so a stranger cannot fill the store. A guest's purchase over UCP (`guest-buyer.md` §7), whose roots its platform signed, is kept under its origin and its `jti` when it completes or is left open, never when it is refused: anyone who publishes a profile can sign such roots, and a refusal kept for each would fill the store, or replace what is kept under the guest's own `jti` for its purchase. Nothing a guest presents is kept under an authority's `jti`, whatever `jti` it chose. A guest's purchase kept before v0.2 is under its `jti` alone, and is served when `issuer` names its origin, not when it names none. `EVIDENCE_RETENTION_DAYS` (90 unless set) is the window; the row is pruned after it and the endpoint answers 404. The verifier records what it was served in its evidence archive; the seller keeps no record of who asked beyond the replay cache. ## On the buyer, and the comparison The buyer serves the same document on the same request (`agent-buyer evidence`, behind its nginx at `https:///evidence/{jti}`; allow-list `AGENT_BUYER_VERIFIERS`, retention `AGENT_BUYER_EVIDENCE_RETENTION_DAYS`, `aud` its public base): what it wrote with `--export-mandates`, plus `served_by: buyer`, `recorded_at`, `retain_until`. The buyer is the party that presented; the seller the party that charged. A verifier asks both. When both answer, the two copies must agree byte for byte on `presentations.checkout`, `presentations.payment`, `references` and `checkout_hash`; a difference is a finding (`mandate_reference_verified` fails and names what differs), never a fallback. When one side answers and the other does not, the copy that came is walked and the other side's answer is quoted. The seller's copy is the one verified when both agree; the digests tie either copy to the three ledgers, so which one is walked changes nothing about what is proved. ## Status Implemented by the seller on 2026-09-13 (`src/evidence.mjs`, `test/evidence.test.mjs`) and by the buyer the same afternoon (`agent_buyer/evidence.py`, `tests/test_evidence.py`); read by agent-verifier's mandate dimension (`mandate_reference_verified`, `source` seller, buyer, seller+buyer or buyer-export) since the same day, with the buyer's export file as the offline path. v0.2 (2026-09-25): the seller keeps a guest's purchase under its origin and answers `issuer`; agent-verifier's guest opinion (`guest_mandate_reference_verified`) asks with it. v0.3 (the same afternoon): a refusal names the checkout its chains close, as a completion does, so over UCP the session's authorization and not the quote; the first UCP refusal (a mandate revoked at 13:40Z) named the quote's. agent-verifier takes the checkout hash of a purchase over UCP from the authorization the chain discloses, which it verifies itself.