OpenReceipt protocol
The OpenReceipt protocol is an open spec — read the canonical version at signedreceipts.org/spec. This page summarises how CloakAPI implements it.
Envelopes in production
CloakAPI issues three envelope kinds today, depending on the way you use:
- v3 (client-signed) — on the client-side ways (browser chat, desktop app,
browser extension), the device that performed the tokenisation signs an
OpenReceipt v3 envelope (
v: "v3",alg: "ecdsa-p256-sha256") before egress, carrying thev3-attestationclaim set (what egressed, which detector categories fired, engine hashes, trust tier). When the request goes through the gateway, the gateway verifies the client signature and adds an additive countersignature witnessing provider, model, and upstream byte hashes. See the v3 specification for the normative format. - v2 (gateway-signed) — on the bare-SDK API way (
api.cloakapi.io), the gateway seals each completed response with the v2 flat envelope described below and returns it in theX-CloakAPI-Receipt-v2header (or as thereceipt-v2event at the end of a stream — a stream cut off before it completes has no trailer). Authenticated calls that fail with a 4xx/5xx get a separate signed failed-call receipt; a request with no valid API key gets none. - v1 (gateway-signed, stored) — the gateway also writes a v1 envelope
to your receipt chain for every call. This is the receipt
GET /api/v1/receiptsand the receipt verifier’s JTI lookup return. Its shape is described in v1 envelope shape below.
v2 envelope shape
The v2 envelope is a flat JSON object signed with ES256 (ECDSA P-256 over SHA-256), canonicalised per RFC 8785 (JCS), and chain-linked per tenant.
{ "v": 2, "alg": "ES256", "kid": "gw-prod-2026-q2", "iss": "https://api.cloakapi.io", "jti": "1de5882f66e6e376c7b26ce2c34bafc7", "iat": 1759420800, "timestamp": "2026-04-18T12:00:00.000Z", "request_id": "req_01HWRK9GTBF3MXQJ4VZPND", "provider": "openai", "model": "gpt-4o", "input_tokens": 184, "output_tokens": 312, "hash_upstream_request": "b3a…", "hash_upstream_response": "c91…", "chain": { "chain_id": "receipt-v2-org-42", "seq": 47, "prev_hash": "sha256:a3f8c2…" }, "signature": "BASE64URL_RAW_R_S_ECDSA_SIGNATURE", "sig": "BASE64URL_RAW_R_S_ECDSA_SIGNATURE"}| Field | Meaning |
|---|---|
v | Protocol version. Integer 2 for the gateway-signed flat envelope (client-signed receipts use string "v3"). |
alg | Signing algorithm. "ES256" (ECDSA P-256 over SHA-256). |
kid | Key id — look up the matching key in the public JWKS to get the verifying key. |
iss | Issuer URL (https://api.cloakapi.io). |
jti | Unique receipt id — 32 lowercase hex characters (128 random bits). The transparency feed surfaces the same value as receipt_id. |
iat | Unix-seconds timestamp the gateway sealed the response. |
timestamp | RFC 3339 timestamp with millisecond precision, always UTC (Z). |
provider / model | Upstream provider slug and model the call was routed to. |
routing_decision | Present when the router ran: from, to (provider slugs), chosen_model, classifier, savings_pct, strategy. There is no provider_id field. |
input_tokens / output_tokens | Token counts billed for the call. |
hash_upstream_request / hash_upstream_response | SHA-256 (bare lowercase hex), never the bytes themselves. hash_upstream_request covers the JCS-canonical (RFC 8785) form of the JSON body relayed to the provider, which is the client’s body unchanged; see Verify → Reproducing hash_upstream_request. |
chain.chain_id | Per-tenant chain identifier (receipt-v2-org-<org_id>). |
chain.seq | Monotonically increasing per-tenant sequence number. |
chain.prev_hash | Hash of the previous receipt’s canonical bytes (same chain). Lets auditors detect missing or out-of-order receipts. |
signature / sig | Both carry the same value: URL-safe base64 (unpadded) raw r‖s (64-byte) ECDSA signature over the JCS-canonical bytes of the envelope with both signature and sig removed. sig is the OpenReceipt name the SDKs read. |
v1 envelope shape
The stored receipt nests the call data under claims. Abridged from a live
receipt:
{ "v": "v1", "alg": "ecdsa-p256-sha256", "kid": "gw-prod-2026-q2", "iss": "https://api.cloakapi.io", "sub": "api_key:1055", "jti": "1de5882f66e6e376c7b26ce2c34bafc7", "iat": 1789978391, "chain": { "chain_id": "tenant-51", "seq": 1078, "prev_hash": "7a08…" }, "claims": { "capability": "chat", "provider": "anthropic", "model": "claude-haiku-4-5-20251001", "tokens_in": 287, "tokens_out": 42, "hash_upstream_request": "7ee1…", "hash_upstream_response": "3dc2…", "routing_decision": { "from": "anthropic", "to": "anthropic", "chosen_model": "claude-haiku-4-5-20251001", "strategy": "client-side", "classifier": "client-side-arbitrage" }, "tokenisation_mode": "client_pretokenised", "engine_version": "1.6.0" }, "sig": "BASE64URL_RAW_R_S_ECDSA_SIGNATURE"}- The v1 envelope is signed by the gateway (the
kidresolves in the same JWKS as v2), not by your device — it has no trusttier. - Token counts are
claims.tokens_in/claims.tokens_out; hashes are bare hex without asha256:prefix;chain.chain_idistenant-<account id>. - Signature:
sigover the JCS-canonical bytes of the envelope withsigremoved, verified with the JWKS key matchingkid.
Where to find the public key
- JWKS:
https://api.cloakapi.io/api/.well-known/cloakapi-receipt-pubkeys.jwks - PEM:
https://api.cloakapi.io/api/.well-known/cloakapi-receipt-pubkey.pem
Keys rotate quarterly. Old keys remain in the JWKS so historic receipts keep verifying indefinitely.