TEE Proofs
Get a signed receipt that names the provider who answered your request and fingerprints what it sent back, and check it yourself.
Turn on TEE confidential computing proof for an API key, and its responses come with a receipt signed by VeriRelay. The receipt names the provider host that received your request, such as api.anthropic.com, and carries SHA-256 hashes of the request and response bodies. You check it on VeriRelay, so you don't have to take our word for it: VeriRelay's signing key exists only inside an AMD SEV-SNP confidential VM, and the attestation that proves this is public.
Turn it on
On the API keys page, create or edit a key and switch on TEE confidential computing proof. Your code stays the same; every request made with that key asks for a proof.
What a proven response carries
X-VeriRelay-Request-Id: 36a420474e713c4ecbe0a6d5dfd79ed0
X-VeriRelay-Store-Id: cFMrwmc4IeTQT14_R9y6gg
X-VeriRelay-Store-Key: <secret>| Header | Use it to |
|---|---|
X-VeriRelay-Request-Id | Fetch the signed receipt |
X-VeriRelay-Store-Id, X-VeriRelay-Store-Key | Fetch and decrypt the request and response bodies exactly as the provider received and sent them. Treat the Store-Key as a secret: whoever holds it can read that exchange until VeriRelay deletes it. |
A response without these headers was not proven. Proof is best effort, so a request that can't be proven is still answered, just without the headers. Requests to the chat endpoints (Chat Completions, Responses, Anthropic Messages and Gemini) can be proven; embeddings, images, audio and the other endpoints answer without a receipt, and so do some models. A stream that ends early has no receipt.
GET /v1/generation?id=<X-Request-ID> returns the same values for any proven request, plus when VeriRelay deletes each part:
"verirelay": {
"request_id": "36a420474e713c4ecbe0a6d5dfd79ed0",
"store_id": "cFMrwmc4IeTQT14_R9y6gg",
"store_key": "<secret>",
"relay": "https://relay.verirelay.com",
"receipt_expires_at": "2026-11-11T08:45:48Z",
"store_expires_at": "2026-11-12T07:45:48Z"
}Check a receipt
On the Usage page, open a request marked Verifiable and choose Verify on VeriRelay. To build the link yourself, put the values after #:
https://verirelay.com/verify#relay=https%3A%2F%2Frelay.verirelay.com&request_id=<X-VeriRelay-Request-Id>&store_id=<X-VeriRelay-Store-Id>&store_key=<X-VeriRelay-Store-Key>Browsers never send the part after # to a server, so the Store-Key stays on your machine. The page checks the signature and the attestation, then decrypts the stored bodies and compares them with the hashes. Leave out store_id and store_key to check the receipt alone.
To check in code, fetch the receipt. No key is needed:
curl https://relay.verirelay.com/v1/avr/receipts/36a420474e713c4ecbe0a6d5dfd79ed0{
"version": "avr/1",
"canonicalization": "jcs-rfc8785",
"signature_algorithm": "ed25519",
"hash_algorithm": "sha256",
"request_id": "36a420474e713c4ecbe0a6d5dfd79ed0",
"timestamp": "2026-11-11T07:45:48.036395467Z",
"method": "POST",
"upstream_authority": "api.deepseek.com",
"upstream_path": "/v1/chat/completions",
"request_body_sha256": "…",
"response_body_sha256": "…",
"response_status": 200,
"upstream_tls": { "sni": "api.deepseek.com", "spki_sha256": "…" },
"service_public_key": "…",
"key_id": "5262ba4e3931cd2a",
"signature": { "payload_sha256": "…", "value": "…" }
}upstream_authority is the host that answered, and upstream_tls.spki_sha256 fingerprints the certificate it presented. GET /v1/avr/evidence/<request_id> returns the receipt together with the SEV-SNP attestation that binds the signing key to the confidential VM. The formats are specified in avr1.md.
Keep a proof
VeriRelay keeps the signed receipt for 1 hour and the stored bodies for 24 hours, then deletes them. To keep a proof longer, save the evidence bundle within the hour. It carries its own signature and attestation, so it can still be checked after VeriRelay's copy is gone.
What the receipt covers
The receipt covers the exchange with the provider. What you receive matches it byte for byte, except for these changes:
| Change | Where you see it |
|---|---|
| Protocol conversion, when you call a model on a protocol its provider doesn't speak | The stored request is in the provider's protocol |
| The model name: the provider receives its own ID for the model | model in the stored request |
| The charge | X-Cost-Credits, and the : x-cost-credits line in streams |
| Responses IDs, when the response is stored | resp_* IDs issued by OhMyGPT |
| The output limit, when your balance can't cover the one you asked for | X-Output-Tokens-Capped gives the new limit, which matches the stored request (details) |
The stored bodies are the bytes the provider sent, so they may be gzip-compressed, and the hashes cover the compressed bytes. Decompress them before comparing with what you received.