Verify a receipt
Completed non-streaming model calls may include an X-CloakAPI-Receipt-V2 response
header when a receipt is produced — a base64-encoded (standard alphabet,
padded) JSON OpenReceipt envelope. You get it on calls that reach the gateway
with the client proof headers expected for pre-tokenised relay (SDKs, the
local proxy, or the contract in
Chat completions). The local proxy 0.3.0 does not
pass this header through to your client.
Verify online (no account)
Pass the header value verbatim:
# $RECEIPT = the entire X-CloakAPI-Receipt-V2 header valuecurl -X POST https://api.cloakapi.io/api/v1/receipts/verify \ -H 'Content-Type: application/json' \ -d "{\"receipt\": \"$RECEIPT\"}"The gateway accepts three payload shapes:
- Header value (base64 string) —
{"receipt": "<header>"}(recommended; least typing) - Decoded envelope object —
{"receipt": {"v":2,"alg":"ES256",...}} - JTI lookup —
{"jti": "<jti>"}answers401 auth_required, with or without acloak_key;GET /api/v1/receipts/<jti>accepts only a signed-in portal session
Or paste the envelope into the browser-based verifier at app.cloakapi.io/receipt-verifier.
A valid: true response means:
- the schema is intact,
- the signature was made by a key listed in the public JWKS
(
https://api.cloakapi.io/api/.well-known/cloakapi-receipt-pubkeys.jwks), - the chain
prev_hashlinks into the previous receipt for the same tenant.
Offline verification (no gateway round-trip)
The envelope is independently verifiable. Decode the base64 header,
canonicalise the JSON (sort keys, no whitespace), drop the sig and signature
fields (both carry the same signature), SHA-256 it, then ECDSA-P256 verify with the public key whose
kid matches envelope.kid in the JWKS. Read the envelope from the
x-cloakapi-receipt-v2 response header of the call that produced it.
A reference Python verifier in 30 lines (uses cryptography):
import base64, json, hashlib, urllib.requestfrom cryptography.hazmat.primitives.asymmetric import ecfrom cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signaturefrom cryptography.hazmat.primitives import hashes
def b64u(s): return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
env = json.loads(b64u(receipt_header_value))jwks = json.loads(urllib.request.urlopen( "https://api.cloakapi.io/api/.well-known/cloakapi-receipt-pubkeys.jwks").read())key = next(k for k in jwks["keys"] if k["kid"] == env["kid"])
env.pop("signature", None) # the envelope carries the signature twicesig = b64u(env.pop("sig")) # drop both before canonicalisingcanonical = json.dumps(env, sort_keys=True, separators=(",", ":")).encode()
pub = ec.EllipticCurvePublicNumbers( int.from_bytes(b64u(key["x"]), "big"), int.from_bytes(b64u(key["y"]), "big"), ec.SECP256R1(),).public_key()
pub.verify( encode_dss_signature(int.from_bytes(sig[:32], "big"), int.from_bytes(sig[32:], "big")), canonical, ec.ECDSA(hashes.SHA256()),)print("PASS")That’s it — completed replies can carry a signed receipt when one is produced. A receipt proves the record is intact and was signed by us; it does not by itself prove what was or was not detected in your text.