Introducing the AnonRouter Confidential SDK

Published
8 min read

Every model gateway 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 other two from inside your own process.

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 anyone's good behavior, including ours.

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 infrastructure host and attests which code is running inside the enclave. It does not hide that content from our gateway, which sees plaintext in order to route and meter the request. 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.

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 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 now lives in a public, Apache-2.0 repository, separate from our product code, which holds the verification logic, the client, the reviewed measurement pins, and the test vectors. You install it, pin it, read the pins, and run it in your own process against the provider's raw evidence.

npm install @anonrouter/confidential

There are three packages. @anonrouter/confidential verifies TEE and E2EE routes and runs encrypted inference. 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.

Verify a route, then use it
import { createClient } from "@anonrouter/confidential";

const client = createClient({
  baseUrl: "https://api.anonrouter.ai",
  apiKey: process.env.ANONROUTER_API_KEY!
});

// Check the route's evidence yourself, before you send anything to it.
const result = await client.verifyAttestation({
  model: "openai/gpt-oss-120b",
  provider: "near-ai"
});
console.log(result.verdict.status, result.verdict.verification_level);

// Encrypted chat. Fresh keys and nonce per call; the relay sees ciphertext.
const reply = await client.chat({
  model: "openai/gpt-oss-120b",
  provider: "near-ai",
  messages: [{ role: "user", content: "Draft a private message." }],
  maxOutputTokens: 512
});

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.

A normalized verdict
{
  "status": "ok",
  "verification_level": "provider-attested",
  "privacy_modality": "e2ee",
  "hardware_type": "intel-tdx+nvidia-cc",
  "measurement_identities": { ... },
  "attested_encryption_key": "...",
  "supports_client_opaque_e2ee": true,
  "reason": null,
  "checks": [ ... ]
}

What it refuses to claim

The most unusual thing about this SDK is a level it will not print. Full cryptographic verification of the hardware quote all the way to the silicon vendor roots would report hardware-verified. We have not wired that chain, so the SDK never emits it. The Intel half is within reach and the NVIDIA GPU root pinning is the hard piece. Until both are done, the SDK will not print the word.

What it does emit:

  • sdk-verified for Tinfoil, checked through Tinfoil's own official verifier, which performs the vendor-root checks itself
  • provider-attested for NEAR AI, Venice, and Chutes: freshness, binding, and measurement checks passed against reviewed pins, without the in-process chain to vendor roots. A strong signal, and not proof of hardware
  • 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.

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 pins and the per-package copies are generated from it. 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. 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 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.

Keep reading

Your prompts are not our business.

Create account
Introducing the AnonRouter Confidential SDK - AnonRouter