AnyRouter

API Overview

Choose the right AnyRouter inference surface — Chat Completions, Messages, Responses, Embeddings — plus catalog and status endpoints.

AnyRouter exposes four core inference surfaces — Chat Completions, Messages, Responses, and Embeddings — plus catalog and account endpoints. Most OpenAI-compatible tooling works by overriding the base URL and API key; Anthropic-compatible tooling uses the Messages endpoint.

The full OpenAPI spec is browsable at /docs/api — every endpoint with live request/response samples and a "try it" runner.

Base URL

https://anyrouter.dev/api/v1

OpenAI-compatible clients that set the base URL to https://anyrouter.dev (and append /v1/... themselves) also work: /v1/models and /v1/chat/completions are the same API as /api/v1/....

All endpoints are versioned under /v1. Breaking changes bump the version (/v2) without removing older versions for a minimum 12-month deprecation window.

Choose an endpoint

Authentication: which key for which endpoint

AnyRouter uses two distinct API key families. Pick the right one for the endpoint you're calling.

Key familyPrefixUse forGet one at
LLM API keysk-ar-v1-Inference: /chat/completions, /messages, /responses, /embeddings, /rerank, /moderations, /decisions (/systemone is the same handler). Also reads /models, /generation/:id, /auth/key./dashboard/keys
Management API keyak_Account administration: /keys (LLM-key CRUD), /presets, /auth/byok, /credits, /management-keys./dashboard/settings#keys

The two families do not cross over. Sending an ak_ key to /chat/completions returns 401; sending a sk-ar-v1- key to /api/v1/keys returns 401 with a hint pointing you here.

flowchart LR
    App["Your app"]

    App -->|"sk-ar-…"| LLM["LLM API key"]
    App -->|"ak_…"| Mgmt["Management API key"]

    LLM --> Inference["Inference<br/>POST /chat/completions<br/>POST /messages<br/>POST /responses<br/>POST /embeddings<br/>POST /rerank<br/>POST /moderations<br/>POST /decisions"]
    LLM --> Read["Read-only<br/>GET /models<br/>GET /generation/:id<br/>GET /auth/key"]

    Mgmt --> Admin["Account admin<br/>/keys (CRUD)<br/>/management-keys<br/>/presets<br/>/auth/byok<br/>/credits"]

    classDef key fill:#fef3c7,stroke:#f59e0b,stroke-width:1px,color:#92400e
    classDef endpoint fill:#f1f5f9,stroke:#cbd5e1,color:#0f172a
    class LLM,Mgmt key
    class Inference,Read,Admin endpoint

Quick decision rule:

  • I want a model to answer a prompt → sk-ar- LLM key.
  • I want to create, rotate, or revoke keys / manage BYOK / read balances → ak_ management key.

See Management API Keys for the full scope catalog and management lifecycle.

Common headers

HeaderRequiredDescription
Authorization: Bearer <key>YesLLM key (sk-ar-…) on inference routes; management key (ak_…) on account routes.
Content-Type: application/jsonPOSTRequired on POST endpoints.
x-api-keyMessagesAnthropic-compatible auth header for /messages (accepts sk-ar-…).
anthropic-versionMessagesRequired by Anthropic-compatible Messages clients.
X-AnyRouter-ProviderNoForce routing to a specific upstream provider.
X-AnyRouter-Balance-After-Debit: trueNoAsk for this request's charge and the workspace balance after it. The response waits up to 500 ms for the charge to be applied, so leave it off unless you read the values.
X-AnyRouter-Trace-IdResponseCanonical trace id on every inference response. Paste it into the dashboard logs "Request ID" filter to open the exact call.
X-Request-IDResponseLegacy alias of X-AnyRouter-Trace-Id — same value, kept for back-compat.
X-AnyRouter-ModelResponsePublic catalog model id that served the request (including the resolved model for a virtual preset).
X-AnyRouter-Upstream-ModelResponseWire model name sent to the selected upstream.
X-AnyRouter-Provider-ChainResponseOrdered backend ids tried, joined with ->; contains route ids only, never credentials.
X-AnyRouter-Credential-SourceResponseplatform, own_byok, or pool for the winning attempt. This is a class label, not key material.
X-AnyRouter-Requested-Model / X-AnyRouter-Fallback-ReasonResponse, on model fallbackSent on /chat/completions only when a different model served the request than the one you asked for (for example through models[]). The reason is one of rate_limited, timeout, model_unavailable, credential_error, upstream_error, or not_attempted. The body carries the same facts as requested_model plus fallback: { used, reason, attempts[] }, on the non-streaming response and, for streams, on the first chunk that carries usage. A stream has no such block if the upstream sends no usage chunk, so treat the two headers as authoritative. requested_model is the resolved canonical id, not the raw @preset/... string (for a preset, the first branch's model). Requests to an anyrouter/* router never get these, because picking a model is the router's job.
X-AnyRouter-Credits-Balance-At-AdmissionResponse, when readWorkspace balance observed during paid-inference admission, before the asynchronous debit. It is omitted when the balance gate is skipped (free/BYOK-only).
X-AnyRouter-Credits-Balance-After-DebitResponse, non-streaming, on requestWorkspace balance right after this request's own charge was applied, on /chat/completions, /messages, and /responses. Sent only when the request included X-AnyRouter-Balance-After-Debit: true. Streaming responses carry the same value as anyrouter_metadata.balanceAfterDebit in the final metadata event on /chat/completions, /messages, and /responses. It is omitted for free and BYOK requests, for a retried request that was already charged, and whenever the charge has not settled by the time the response is written — use GET /credits then. Other requests running at the same time can lower the balance further.
X-AnyRouter-Credits-ChargedResponse, non-streaming, on requestCredits this request was charged, read from the billing ledger, so it already reflects the backend that served it, retries, fallbacks, and cache pricing. Sent on /chat/completions, /messages, and /responses only when the request included X-AnyRouter-Balance-After-Debit: true. 0 for free and BYOK requests. Streaming responses carry it as anyrouter_metadata.creditsCharged in the final metadata event. It is omitted for a retried request that was already charged and whenever the charge has not settled by the time the response is written.
X-AnyRouter-Credits-BalanceGET /credits responseCurrent authoritative workspace balance returned by the credits read endpoint.
X-RateLimit-* / RateLimit-*ResponseThe per-minute limit and policy for the tier the request hit (free-model is 2400 per minute per key). Go's 5-hour window also reports remaining count and reset.
Server-TimingResponseGateway-vs-upstream latency split in ms on /chat/completions, /messages and /responses: gw is AnyRouter's own work before the first upstream call (auth, routing, admission), upstream is the total time spent inside upstream attempts — repeated once per attempt with a desc naming the backend — and post is AnyRouter's work after the upstream call (response assembly, usage and cost finalization). total is the sum. For a streamed response the header leaves with the first byte, so post covers only what happened before it; the full split also arrives as anyrouter_metadata.timings in the final metadata frame.

Endpoint summary

MethodPathDescription
POST/chat/completionsChat completions (OpenAI-compatible)
POST/ocr/extractOCR — extract text as Markdown
POST/messagesAnthropic Messages passthrough
POST/responsesResponses for structured output and tool workflows
POST/embeddingsEmbeddings for search and clustering
POST/rerankRerank documents by relevance to a query
POST/documents/parseDocument parse — file to Markdown, your own Nace key
POST/moderationsModerations — classify text for harmful content
POST/decisionsDecisions TypeSafe decisions (not chat). Preferred path.
POST/systemoneSame handler as /decisions
GET/modelsList models
GET/models/:idModel metadata
GET/models/countTotal model count
GET/generation/:idPer-request stats (tokens, cost, latency)
GET/providersList upstream providers
GET/creditsRemaining credit balance
GET/auth/keyCurrent API key info
GET/healthGateway + provider status

Error shape

All errors follow a consistent JSON envelope:

{
  "error": {
    "code": "payment_required",
    "message": "Upstream provider \"cloudflare\" returned 402 — insufficient quota",
    "metadata": {
      "type": "upstream_error",
      "upstream_backend": "cloudflare",
      "upstream_status": 402,
      "upstream_message": "You exceeded your current quota, please check your plan and billing details."
    }
  }
}

error.code is a stable machine-readable slug. The canonical set:

HTTPerror.codeNotes
400bad_request / max_tokens_too_smallMalformed body or invalid parameters. max_tokens_too_small: a reasoning model spent the whole output budget on reasoning and produced no content — raise max_tokens (max_output_tokens on /responses) to 256 or more, or set stream: true.
401unauthorizedMissing or invalid API key.
402insufficient_balanceOut of credits on a paid model, or a held signup bonus still pending (signup_bonus_pending). Top up at Credits. Upstream BYOK billing failures may also surface as 402 with metadata.type: "upstream_error" — read metadata.upstream_message.
403forbiddenKey lacks permission for the resource.
404not_foundUnknown model or missing resource.
408request_timeoutUpstream did not respond in time.
413content_too_largeRequest body exceeds the configured limit.
422unprocessable_entityValidation failed semantically.
429rate_limit_exceeded / ip_rate_limit_exceededRate limit hit. Honor Retry-After. X-RateLimit-Remaining / X-RateLimit-Reset appear only on the Go 5-hour window limiter — not on the per-minute tier.
500internal_server_errorUnexpected AnyRouter failure.
502bad_gateway / upstream_exhaustedUpstream 5xx or every fallback failed; see metadata.upstream_attempts.
503service_unavailableUpstream temporarily unavailable.

When AI Gateway BYOK rejects a call, AnyRouter parses the provider's verbatim message into error.metadata.upstream_message — that is the authoritative reason, not Cloudflare's wrapping text. See Errors for retry recipes and /docs/api for the full interactive reference.

Rate limits

Rate limits are enforced per-key. Every response carries at least:

X-RateLimit-Limit:     <requests per minute>
X-RateLimit-Window:    60
X-RateLimit-Tier:      <tier name>
RateLimit-Limit:       <same as X-RateLimit-Limit>
RateLimit-Policy:      <limit>;w=60

The per-minute tier does not emit X-RateLimit-Remaining or X-RateLimit-Reset — the edge limiter only reports the cap. Go's 5-hour window does emit Remaining / Reset (and matching RateLimit-* headers). See Rate Limits.

Tracing

POST /chat/completions and POST /responses also accept an optional request-body trace object:

{
  "trace": {
    "trace_id": "4f8c2b9670b44b49a2e71e7fd5c0b1d3",
    "trace_name": "checkout-flow",
    "span_name": "summarize-order"
  }
}

AnyRouter uses this metadata to correlate internal request steps such as identity resolution, rate-limit checks, upstream selection, and upstream dispatch. If you do not send a trace object, AnyRouter still returns an X-Request-ID header you can use for support.

Versioning

Changes are categorized as:

  • Additive (new fields, new endpoints) — ship continuously, no notice.
  • Deprecation — announced with a 12-month sunset window and a Deprecation response header.
  • Breaking — land on a new /v2 path; /v1 remains available for the deprecation window.