# prxy.monster receipt verification

Every successful inference call routed through prxy.monster carries a
signed receipt. This file describes how to verify one without trusting
prxy's servers.

## What's on the wire

Every `POST /v1/messages`, `POST /v1/chat/completions`, and
`POST /v1/agent/messages` response that ran cleanly returns three
headers:

- `Payment-Receipt: https://receipts.prxy.monster/r/<receipt_id>`
- `Payment-Receipt-Digest: sha-256=:<base64-digest>:` (RFC 9530)
- `Payment-Receipt-Kid: <key-id>` (matches the JWKS)

Streaming responses return a `Payment-Receipt-Provisional` triple at
stream open; the receipt is finalized when the stream closes and a
final `Payment-Receipt` triple is appended.

## Verification flow

1. **Fetch the receipt body.** `GET /v1/receipts/:id` against
   `https://api.prxy.monster` returns the JSON body. Visibility
   filtering applies: private receipts only return the body to the
   owning api_key; public receipts return to any reader.

2. **Fetch the JWKS.** `GET https://api.prxy.monster/.well-known/prxy-receipt-keys.json`.
   The JWKS is cached at the edge for 1 hour and rotated quarterly.
   Match the receipt's `signing_key_id` field to a JWK with the
   same `kid`.

3. **Canonicalize the receipt body.** Strip the `signature` field,
   normalize the remaining JSON via Unicode NFC, then RFC 8785 JCS
   canonicalize the result. The canonical-encoding version is named
   on the receipt as `canonicalization_version` (currently
   `prxy-canon-v1`).

4. **Verify the signature.** ed25519 verify the canonical bytes
   against the JWK's public key. The `signature` field is base64url
   without padding.

The same flow works for receipts older than the active key: the JWKS
retains rotated keys for 90 days under `status: rotated`. Receipts
from a revoked key remain trustworthy if they were signed before the
key's `revoked_at` timestamp.

## Receipt fields

The receipt is a JSON object. Field groups:

- **Identity.** `receipt_id`, `request_id`, `api_key_id`,
  `user_id`, `agent_id`, `tenant_id`, `project_id`,
  `environment`.
- **AWS attribution (when provider is Bedrock).**
  `aws_account_id`, `iam_principal`.
- **Outcome.** `status` (`succeeded` | `failed` | `blocked` |
  `cached` | `stream_aborted` | `refunded`), `http_status`,
  `provider_status`, `error_class`, `error_code`,
  `error_message_hash`.
- **Tokens + cost.** `input_tokens`, `output_tokens`,
  `cache_read_tokens`, `cache_write_tokens`, `tokens_source`,
  `cost_estimated_usd`, `cost_estimate_confidence`,
  `cost_final_usd`, `cost_currency`. The pricing-table provenance
  trio (`pricing_table_id`, `pricing_version`,
  `pricing_effective_at`) lets you re-verify the estimate against
  the same rate snapshot prxy used.
- **Pipeline.** `routing_decision`, `policy_decision`,
  `cache_status`, `module_chain`, `settlement_protocol`.
- **Hashes.** `client_request_hash`, `provider_request_hash`,
  `provider_response_hash`, `client_response_hash`. Default
  `payload_capture: hash_only` means no plaintext is stored
  server-side; the hashes prove the call shape.
- **Signature.** `signing_key_id`, `signing_algorithm` (currently
  `Ed25519`; HMAC-SHA256 fallback during the migration window),
  `signature`.

## CLI

`prxy-cli receipt verify <receipt-id>` performs the full flow above
locally. `prxy-cli receipt verify --header "<paste Payment-Receipt header here>"`
takes a header triple and verifies the digest before fetching the
body.

## JWKS

`https://api.prxy.monster/.well-known/prxy-receipt-keys.json` —
JSON Web Key Set. Each key carries:

- `kid` — the key id stamped on receipts.
- `status` — `active` | `rotated` | `revoked`.
- `not_before` / `not_after` — validity window.
- `rotated_at` (when `rotated`) / `revoked_at` (when
  `revoked`) / `revocation_reason`.

Rotation cadence is quarterly. Operator runbook lives in the
prxy-monster repo at `docs/runbooks/RECEIPT-KEY-ROTATION.md`.
