anonrouterdocs

Migrate from OpenRouter

Port an OpenRouter app to AnonRouter by changing the base URL and API key, plus the handful of field differences to know about.

OpenRouter and AnonRouter expose the same developer surface: the OpenAI Chat Completions API, spoken through the official OpenAI Python and TypeScript SDKs. Both use creator/model ids, and many ids are identical on the two catalogs. For most applications the port is exactly two changes: the base URL and the API key.

https://openrouter.ai/api/v1   ->   https://api.anonrouter.ai/v1
sk-or-v1-...                   ->   ar_...

Everything below was verified request-by-request with the official OpenAI SDKs (Python and TypeScript) against an AnonRouter gateway.

Before you begin

  1. Create an AnonRouter account and an inference key in the dashboard. Keys are prefixed ar_ and shown once.
  2. When you create the key, enable OpenAI-compatible access on it. This is the static-key mode OpenRouter users expect, and it is off by default because the fully private ticket flow is the default. See Compatibility mode for the tradeoff.
Create an API key

Why the extra toggle?

AnonRouter's default flow splits your identity from your prompts with single-use tickets, so the two cannot be linked. A static bearer key cannot make that split, so OpenAI-compatible access is a deliberate per-key opt-in, not the default. You can run both: static key for tools that need it, ticket flow for workloads where unlinkability matters.

The two-line port

from openai import OpenAI

client = OpenAI(
    base_url="https://api.anonrouter.ai/v1",   # was https://openrouter.ai/api/v1
    api_key=os.environ["ANONROUTER_API_KEY"],  # was OPENROUTER_API_KEY
    max_retries=0,                             # see "Disable automatic retries"
)

completion = client.chat.completions.create(
    model="anthropic/claude-sonnet-4.5",       # same id on both catalogs
    max_tokens=256,
    messages=[{"role": "user", "content": "Hello"}],
)

print(completion.choices[0].message.content)

Streaming, stream_options: {"include_usage": true}, tool calling with multi-turn tool-result loops, response_format structured outputs, image (image_url) content parts, and models.list() all work through the same two changes.

Model ids

Both routers use creator/model slugs, and the overlap is large: anthropic/claude-sonnet-4.5, openai/gpt-4o, google/gemini-3.5-flash, deepseek/deepseek-v3.2, qwen/..., moonshotai/... resolve on both. The catalogs are not identical, so map your model list once:

curl -s https://api.anonrouter.ai/v1/models \
  -H "Authorization: Bearer $ANONROUTER_API_KEY" | jq -r '.data[].id'

Differences to know:

OpenRouterAnonRouter
openrouter/auto/auto (routes within your privacy boundary)
some/model:nitroSame: :nitro sorts providers by throughput
some/model:floorSame: :floor sorts providers by price
:free, :online, :thinking, :extended suffixesNot supported, model_not_found
Unknown modelSame OpenAI error envelope, model_not_found

Each /v1/models entry also carries AnonRouter metadata the OpenAI SDK ignores: privacy_class (anonymous, private, e2ee), per-route capabilities (tools, reasoning efforts, vision), and pricing. Use capabilities the way you used OpenRouter's supported_parameters.

Request fields: what maps where

Verified field by field. "Ignored" means the request succeeds and the field is silently dropped, so you can migrate without stripping it first.

Works unchanged

FieldNotes
messages, temperature, top_p, stop, seed, penaltiesStandard passthrough
max_tokens or max_completion_tokensEither alias works. Sending both with different values is a 400, not a silent pick
stream, stream_options.include_usageSSE with data: [DONE] terminator
tools, tool_choice, parallel_tool_callsFull tool-calling transport, streaming and non-streaming
response_formatjson_object and json_schema forwarded to the provider
reasoning_effort and reasoning: {"effort": ...}Same two spellings as OpenRouter. Effort levels are per-model, read capabilities.reasoning
reasoning: {"enabled": false}Disables thinking on models that support disabling
provider.order, provider.allow_fallbacks, provider.ignore, provider.only, provider.sort, provider.require_parameters, provider.max_attemptsSame shapes as OpenRouter provider routing. See Routing
models: ["a", "b", ...]Cross-model fallback works on the OpenAI-compatible endpoint with a short candidate list. Pricing follows the model actually used
userAccepted and forwarded. AnonRouter keeps no per-end-user analytics by design

Accepted and ignored

FieldWhy
HTTP-Referer, X-Title headersNo app attribution or public leaderboards, by design. Leave them in or remove them
transforms: ["middle-out"]No server-side context compression yet
route: "fallback"Legacy OpenRouter routing hint
usage: {"include": true}Unnecessary: non-streaming responses already include usage with a cost field in USD
plugins: [...]No server-side web search or file parsing. The gateway transports tools, it never executes them
n, logprobs, top_logprobs, logit_biasAccepted for SDK compatibility, not honored
Legacy functions, function_callStripped. Migrate to tools and tool_choice

Must change

OpenRouterAnonRouter replacement
provider.data_collection: "deny"provider.minimum_privacy: "private" (or "e2ee"). This is a floor the router can never silently lower
provider.quantizations, other OpenRouter-only provider keysRemove them. The provider policy object is strict and rejects unknown keys with 400 invalid_request
reasoning.max_tokensRejected. Use effort levels (reasoning.effort). Reasoning tokens share the max_completion_tokens ceiling
reasoning.exclude: trueRejected. Use venice_parameters: {"strip_thinking_response": true} to hide the trace
OAuth PKCE key issuance ("Connect with OpenRouter")Not offered. Issue keys from the dashboard

Disable automatic retries

Set max_retries=0 (Python) or maxRetries: 0 (TypeScript). AnonRouter does not yet support client-supplied idempotency, so an SDK auto-retry after an ambiguous failure could start a second paid generation. The OpenAI SDK default is 2 retries, so this is worth changing during the port. Handle retries yourself where you need them.

Errors and limits

Errors use the OpenAI envelope, so typed SDK exceptions keep working: AuthenticationError on a bad key, NotFoundError on an unknown model, RateLimitError on 429. Your existing error handling ports as-is.

Two OpenRouter surfaces have no AnonRouter equivalent and fail with 404: the Responses API (client.responses.*) and models.retrieve(id) (use models.list()). Image and speech generation exist as separate Studio routes rather than OpenAI-style endpoints.

What you gain

  • A privacy floor instead of a preference. Every route is labeled anonymous, private, or e2ee, and provider.minimum_privacy is enforced with no silent downgrade. See Privacy.
  • Routing transparency headers on every completion: x-anonrouter-provider, x-anonrouter-routing, x-anonrouter-provider-attempts, x-anonrouter-provider-fallback, x-anonrouter-privacy-class.
  • Inline cost accounting without asking: non-streaming responses carry usage.cost in USD.
  • Embeddings through the same SDK and key (POST /v1/embeddings), which OpenRouter does not offer.
  • An upgrade path beyond drop-in. When a workload needs unlinkability, switch that call site to the ticket flow or an E2EE model. The request body stays the same, only authentication changes.

No BYOK, on purpose

OpenRouter's bring-your-own-key integrations re-identify you to the upstream provider, which defeats the point of an anonymizing router. AnonRouter does not offer BYOK.

On this page