Every AI router on the market describes itself as private. Almost none of them give you a way to check. We split the claim into four labels, said plainly which ones are promises rather than proofs, and shipped an open-source SDK that verifies the rest from inside your own process. Since this post first ran, the SDK has grown its second half: it now verifies our own routing plane too, not just the provider on the far side.
A label without a mechanism is just a policy
Most privacy claims in this industry are statements of intent. A company says it does not retain your prompts, and that is usually true on the day it is written. Intentions are also revisable: terms change, a debugging flag gets left on, a subpoena arrives, an acquiring company has different ideas. None of that involves anyone lying to you. It just means the guarantee is organizational rather than technical, and it lasts exactly as long as the policy behind it.
Some guarantees can be made structural instead. If a request is encrypted to a specific hardware enclave and only that enclave holds the decryption key, then our willingness to read it stops being the question. That is a genuinely different kind of claim, and collapsing both kinds into one green Private badge would hide the distinction that matters most.
So the catalog carries four labels. Two describe how the request is handled. Two describe what the hardware enforces.
The four levels
Anonymous
Your identity is hidden from the provider, but the provider can read the prompt while serving it, and its retention is not guaranteed. This is the honest label for routes where the upstream terms do not promise anything better. Our own architecture still applies, so the provider sees content without knowing whose it is, but we do not claim more than the upstream will actually commit to.
Private
Prompt and response content is not persistently retained after the request completes. The inference runtime still sees plaintext while processing it, which is what makes this a policy tier: it rests on the provider's documented behavior, not on cryptography. We treat those documents as load-bearing and read them closely. A route only earns Private when the provider's own policy supports it, and the caveats travel with the label rather than getting quietly dropped.
Classification fails closed. On DeepInfra, for instance, routes flagged as partner routes stay Anonymous because certain models forward to a vendor API with its own retention exceptions, and we treat a route with a missing flag as the weaker case rather than the stronger one.
TEE
Inference runs inside an attestable hardware enclave. The enclave protects your content from the machine's own operator and proves which code is loaded, which is a real and useful guarantee against a compromised or curious host.
E2EE
Your client encrypts the request to a key bound to an attested enclave, and only that verified enclave can decrypt it. This is the tier where the guarantee no longer depends on our good behavior.
It is worth being exact about which enclave that is, because it is not always the one running the model. On our Venice E2EE routes the key your browser encrypts to belongs to an attested gateway enclave that reports no GPUs at all. It decrypts your prompt and forwards it to the machine that serves the model. So the guarantee is real, and it is narrower than it sounds: your prompt is opened inside verified hardware rather than on an ordinary server, but there is a second hop after that, and we cannot yet prove which enclave took it.
Labels attach to routes, not to models. One model can be served by several providers at different levels, and the same provider can serve one model two ways: Venice offers GLM 5.2 as a Private route with prompt caching and as an E2EE route with none. Those are two routes under one model. E2EE is a separate serving modality, so a plaintext request never lands on the E2EE route and an E2EE request never lands anywhere else. Routing enforces the labels instead of just printing them, and Auto picks the strongest callable privacy class first, then the cheapest route within that class. Fallback never silently downgrades you.
The distinction most people get wrong
TEE is not content-private from AnonRouter. A TEE protects your content from the machine's operator and attests which code is running inside the enclave. It does not hide that content from our relay, which handles plaintext in order to route and meter the request. That relay now runs inside an attested Intel TDX VM of its own and terminates TLS in-enclave, so a build changed to leak what it handles would change the measurements and stop verifying. Cheating is detectable, provided you actually verify. That is still not the same thing as us being unable to read your text.
Reach for a TEE route when you want hardware attestation of the serving stack. Reach for E2EE when the requirement is that we cannot read it even if we shipped code to try. This is why they are two tiers on the badge and not one: a single Confidential label covering both would be easier to market, and it would obscure the only difference that changes who can read your text.
Why we shipped an SDK
Here is the awkward part of verification, and the reason the SDK exists at all. We already show attestation evidence in the browser. That is useful, but it cannot be evidence against us, because we wrote and delivered the code doing the checking. A page we control, telling you that we passed our own check, is a convenience. It is not independent evidence.
Verification only counts when the verifier runs on your side of the boundary. So the checking logic lives at github.com/anonrouter/anonrouter-sdk, a public Apache-2.0 monorepo kept deliberately separate from our product code. It holds the verification logic, the client, the reviewed measurement pins, and the known-answer test vectors. You install it, pin it, read the pins, and run it in your own process against the raw evidence.
There are three packages. @anonrouter/confidential verifies TEE and E2EE routes, runs encrypted inference, and generates images and speech over the same ticketed flow. anonrouter-confidential on PyPI is its Python twin, sharing the same pins and the same vectors. @anonrouter/client is a thin, dependency-free client for the plaintext routes, and it is labeled as the plaintext client so nobody reaches for it expecting confidentiality. The confidential packages also install a terminal command, which we will get to.
Two hops, verified separately
A request passes through two parties, and verifying one tells you nothing about the other. Hop 1 is our own routing plane: is the data plane you connected to the exact reviewed build, running inside an Intel TDX confidential VM, bound to your nonce and your origin? Hop 2 is the provider route: did the model provider terminate your request inside a verified enclave running measurements you pinned? The SDK checks them with separate calls, verifyGateway and verifyAttestation, and verifyRoute establishes both. It reports them separately too, so a result can never read as though it covered a hop it skipped.
The hop 1 pins live in the repo, in gateway-policies.json, and they are never fetched from the server being verified. A server that could hand you the list of builds you accept could always name itself, so the pins ship inside the packages, and an origin without a pin fails closed rather than falling back to the server's own claim about itself. The build behind the pin is public as well: the content plane image is built in public CI from the confidential-content-plane repository, with a signed provenance attestation naming the same digest.
The verdict is deliberately legible. It reports what was checked, what the evidence binds to, and how far the verification actually went, and every check is content-free by construction, so a verdict never echoes prompts or keys.
hardware-verified, without the asterisk
When this post first ran, there was a level the SDK refused to print. Full cryptographic verification of a hardware quote, chained all the way to the silicon vendor roots, would report hardware-verified, and we had not wired that chain, so the SDK never emitted it. The Intel half is now wired. On hop 1 the SDK can chain the quote's signature to Intel's roots and check the TCB status, and the verdict says so.
The catch is deliberate: the packages bundle no verification engine. Shipping a prebuilt binary would mean asserting that a binary we did not build reproducibly is the reviewed one, and a hand-rolled reimplementation would be an unreviewed version of the one component whose failure mode is printing hardware-verified for a forged quote. So the engine's source lives in the repo, a script builds it twice from clean in a pinned container and fails unless the two outputs are byte-identical, and the release carries a checksummed build you can compare against rather than trust. Run without an engine and a policy that demands hardware verification fails closed instead of settling for less while reporting success.
What each hop can report:
- hardware-verified on hop 1, with the engine installed: the quote's signature chained to Intel's roots with an accepted TCB status
- sdk-verified for Tinfoil, checked through Tinfoil's own official verifier, which performs the vendor-root checks itself
- provider-attested for Venice, Chutes, and the enclave routes listed under Other: freshness, binding, and measurement checks passed against reviewed pins. Hop 2 still caps here, because several provider routes run GPU enclaves whose NVIDIA attestation chain is not available to verify, and chaining only the CPU quote while printing hardware-verified would claim more than was checked
- unverified when evidence exists but a required check failed, or when verification was not attempted. Treat it as untrusted
- unsupported when the route does not expose the capability at all
A required check that fails forces the whole verdict to failed. There is no partial credit and no advisory mode, since a warning you are free to ignore would give you confidence without evidence.
Verify from a terminal
Both confidential packages install anonrouter-verify. It prints one JSON document and exits nonzero unless the assurance you asked for was established, so it can gate a deploy rather than only inform one. The routing-plane check needs no credentials at all; the route check adds hop 2 and reads its API key from an environment variable, never from argv. Neither command ever prints a key, a ticket, request content, or the raw evidence body.
Two languages that cannot drift
Shipping the same security logic twice is a good way to end up with two subtly different security stories, so the two implementations are pinned to shared inputs. One canonical measurements.json holds the reviewed provider pins, gateway-policies.json holds the pins for our own plane, and the per-package copies are generated from them. A set of language-neutral known-answer vectors covers provider decryption, TDX quote parsing, and complete verifier verdicts, and both test suites load the same files.
The verdict vectors pin the status, the verification level, the failure reason, and the exact set of required checks that failed. A parity gate in CI fails the build when any per-package copy drifts from the canonical pins, and a second gate holds the two CLIs to the same answers. The practical effect is that a change landing in JavaScript but not Python cannot pass CI, and neither can a change that quietly demotes a required check into an advisory one. Weakening the guarantee has to be deliberate and visible in a diff.
Where to look
The catalog shows the label on every route, and you can filter by the level you need. The SDK, the pins, and the vectors are all at github.com/anonrouter/anonrouter-sdk. The privacy model, the four labels, and the verification flow are documented at docs.anonrouter.ai/privacy, with the SDK covered at docs.anonrouter.ai/sdk.
Check the label on the route you actually called rather than the badge on the model page. When the requirement is stronger than a policy, use E2EE and verify it with code we did not hand you at runtime.

