API reference
The public HTTP surface for AnonRouter integrations.
The production base URL for OpenAI-compatible clients is:
https://api.anonrouter.ai/v1That is the only URL ordinary tools such as OpenCode, SillyTavern, and OpenAI SDK clients need. The private ticket flow additionally uses:
https://control.anonrouter.ai/v1The control origin receives keys and content-free account or authorization requests. The confidential API origin receives inference content and attestation requests. The AnonRouter SDK keeps the two origins separate automatically.
Requests authenticate one of three ways, depending on the endpoint:
- Inference key (
ar_) inAuthorization: Bearerto issue tickets and, in compatibility mode, to call inference directly. - Management key (
arm_) inAuthorization: Bearerto read balance and usage. - Inference ticket in the
x-anonrouter-ticketheader to run a single ticketed request.
Some account and billing operations require an authenticated session and are performed from the dashboard rather than with an API key.
Inference
| Method | Endpoint | Origin | Credential | Description |
|---|---|---|---|---|
GET | /models | Control; API in compatibility mode | Inference or management key | List enabled models and capabilities |
GET | /catalog/models | Control | None | Public, cacheable catalog with privacy metadata |
GET | /models/uptime | Control | None | Passive success-rate and latency series |
POST | /inference/tickets | Control | Inference key | Issue a single-use inference ticket |
POST | /chat/completions | API | Ticket | Create a streaming or non-streaming chat completion |
POST | /embeddings | API | Ticket | Create vector embeddings |
POST | /images/generations | API | Ticket | Generate an image (when enabled) |
POST | /audio/speech | API | Ticket | Synthesize speech (when enabled) |
GET | /capabilities | Control | None | Report enabled media feature flags |
/models/uptime requires a ?model=<id> query parameter and answers 400 missing_model without one.
In compatibility mode, /chat/completions and
/embeddings accept an inference key directly instead of a ticket.
A chat completion can include the managed anonrouter:web_search tool. In the
ticket flow, issue the ticket with web_search: { "max_uses": n }. See
web search.
How the confidential origin decides which flow you are in
api.anonrouter.ai dispatches on the Authorization header. Send a ticket and
no bearer token and you are in the private ticket flow; send an ar_ bearer
token and you are in compatibility mode, which is why GET /v1/models answers
404 there without a credential and an OpenAI-shaped 401 invalid_api_key
with one. Control-plane routes are not served on that origin in either flow.
Attestation
| Method | Endpoint | Origin | Credential | Description |
|---|---|---|---|---|
GET | /gateway/attestation | API | None | Attest AnonRouter's own confidential plane (hop 1); requires ?nonce= of exactly 64 hex characters |
GET | /tee/attestation | Control | Inference key | Fetch provider enclave evidence for a model and provider (hop 2) |
POST | /tee/attestation | API | Attestation ticket | The same evidence without presenting a key to the content plane; send the ticket in x-anonrouter-ticket and the nonce in the JSON body |
GET | /tee/signature/:providerRequestId | Control | Inference key | Retrieve a per-request enclave receipt where the provider exposes one |
POST | /inference/attestation-tickets | Control | Inference key | Mint a model-bound ticket for the ticketed attestation call |
See TEE verification for what each document proves.
The routing, account, workspace, billing, and key-management endpoints below
are control-plane operations and use https://control.anonrouter.ai/v1 unless
the page says otherwise.
Routing
| Method | Endpoint | Credential | Description |
|---|---|---|---|
GET | /routing/effective | Inference key | List models the saved preferences allow |
GET | /routing/preferences | Session | Read saved routing preferences |
PUT | /routing/preferences | Session | Replace saved routing preferences |
POST | /routing/preview | Session | Preview a routing decision |
POST | /routing/eligible-models | None | List models eligible for draft preferences |
Account
| Method | Endpoint | Credential | Description |
|---|---|---|---|
GET | /balance | Management key | Read available and reserved balance |
GET | /usage | Management key | Read recent usage metadata |
Workspaces
| Method | Endpoint | Credential | Description |
|---|---|---|---|
GET | /workspaces | Session | List workspaces |
POST | /workspaces | Session | Create a workspace |
PATCH | /workspaces/:id | Session | Edit a workspace |
DELETE | /workspaces/:id | Session | Delete a workspace |
Connected Apps
Not enabled on the production deployment yet
AnonRouter Connect is operator-gated and is currently switched off in
production: the discovery document and every endpoint in this section answer
404 on the live deployment. The interface below is documented so an
integration can be written against it, not because it can be called today.
Connected Apps uses a focused OAuth 2.1 and OpenID Connect provider. Begin with
the discovery document at
https://control.anonrouter.ai/connect/.well-known/openid-configuration
instead of hard-coding the authorization, token, UserInfo, revocation, or JWKS
paths. See Connected Apps for the complete user and developer
flow.
The dashboard uses these session-authenticated management endpoints:
| Method | Endpoint | Description |
|---|---|---|
GET | /connect/grants | List the account's active and historical app grants |
DELETE | /connect/grants/:id | Disconnect one grant |
GET | /connect/apps | List enabled developer apps owned by the account |
POST | /connect/apps | Register an app and return its client secret once |
PATCH | /connect/apps/:id | Change app metadata; redirect changes revoke current grants |
POST | /connect/apps/:id/rotate-secret | Rotate the secret and revoke current grants |
DELETE | /connect/apps/:id | Disable the app and revoke current grants |
Billing
| Method | Endpoint | Credential | Description |
|---|---|---|---|
GET | /billing/catalog | None | Packages, limits, fee, payment terms, and whether card and crypto rails are currently enabled |
GET | /billing/orders | Session | List purchase orders |
GET | /billing/orders/:orderId | Session | Poll one order |
GET | /billing/receipts | Session | List receipts |
GET | /billing/receipts/:receiptNumber | Session | Fetch one receipt |
Response headers
Successful inference responses include:
x-anonrouter-selected-model: the chosenprovider/model.x-anonrouter-routing:autoorexact.x-anonrouter-provider: the provider that served the request, as a public provider id, the same value as the catalog'sproviderfield.x-anonrouter-provider-attempts: how many providers were tried.x-anonrouter-provider-fallback:truewhen a fallback provider served the request.x-anonrouter-privacy-class: the tier of the route that served the request,anonymous,private,tee, ore2ee.x-ratelimit-limit-requests,x-ratelimit-remaining-requests,x-ratelimit-reset-requests, and the matching-tokensvariants.
See provider routing and failover for how the provider is chosen and when another one is tried.
Errors
Errors use an OpenAI-compatible envelope with a machine-readable type and a
request_id:
{
"error": {
"message": "Inference ticket is invalid or expired",
"type": "invalid_ticket",
"request_id": "req_..."
}
}Common cases:
| Status | type | Meaning |
|---|---|---|
401 | ticket_required | A ticket is required and none was sent |
401 | invalid_ticket | The ticket is invalid, expired, or already used |
403 | insufficient_scope | The credential lacks the required scope |
402 | insufficient_balance | Not enough prepaid balance |
404 | model_not_found | The model is unknown or not enabled |
409 | ticket_model_mismatch | The request model differs from the ticket |
409 | ticket_reasoning_mismatch | The reasoning selection differs from the ticket |
429 | rate_limited | Rate limit exceeded; see retry-after |
503 | media_disabled | Image or speech is not enabled on this deployment |
A 429 response also sets a retry-after header. During streaming, a mid-stream
provider failure is delivered as an inline SSE error frame rather than an HTTP
status, because the response headers have already been sent.
Provider routing errors
| Status | type | Meaning |
|---|---|---|
400 | invalid_provider_policy | The provider value is malformed, for example an invalid provider id |
400 | provider_policy_conflict | Contradictory provider fields, such as a provider in both only and ignore |
400 | no_provider_route | No eligible provider route is available for the model |
403 | no_provider_route_meets_privacy | No route meets the required privacy floor |
403 | privacy_boundary_not_met | No route of the model meets the workspace's privacy boundary; refused before any provider sees the request |
400 | no_provider_route_meets_price | No route is within max_price |
409 | ticket_provider_policy_mismatch | The body's provider differs from the policy the ticket was issued for |
503 | provider_fallback_exhausted | Every permitted provider attempt failed before output after a fallback, or a later attempt got a 400 |
503 | provider_fallback_not_supported_for_e2ee | An E2EE provider failed before output; E2EE cannot switch providers |
See provider routing and failover.
Router, free-model, and context errors
Router IDs use the same Chat Completions endpoint. See choose a router
for paid router selection and errors, free models
for account consent and capacity errors, and context limits
for context refusals and cooldowns. A :free request never falls back to a paid
route. A recognized context refusal is not automatically retried on another
provider.
Or hand the whole thing to your agent
One prompt carries the entire setup. Give it to your agent, review what it configures, and you are done.