# AnyRouter CLI Agent (deprecated) (/agent)
>
> Deprecated
>
> `@anyr/agent` is deprecated. Use `anyr` (install via `curl -fsSL https://anyrouter.dev/setup.sh | bash`) with Claude Code, Codex, or another launcher instead.
> The features described below (TUI, `--web`, `--print`) remain in source but are no longer published
> or actively maintained. Sign in with `anyr auth login` and launch coding agents through the
> recommended launchers.
>
Run a fully capable AI coding agent — backed by AnyRouter — with a single command. Your AnyRouter key fronts every provider, so you get automatic failover, unified billing, and access to the whole model catalog. Install the native CLI with `curl -fsSL https://anyrouter.dev/setup.sh | bash`.
```bash
npx @anyr/agent
```
## Quick start [#quick-start]
::::steps
:::step{title="Authenticate"}
The agent reads your key from `ANYROUTER_API_KEY`. Get one at [anyrouter.dev/dashboard/keys](https://anyrouter.dev/dashboard/keys). The free model (`anyrouter/free`, the default) works without spending credits — with no daily cap on any plan. See [Free Tier](/features/free-tier).
```bash
export ANYROUTER_API_KEY=sk-ar-v1-...
```
:::
:::step{title="Start a session"}
Launch an interactive coding session with the default model:
```bash
npx @anyr/agent
```
:::
:::step{title="Or run one-shot"}
Send a single prompt, print the reply, and exit — handy for scripting:
```bash
npx @anyr/agent --print "Explain closures in three sentences"
```
:::
::::
## Options [#options]
| Flag | Default | Description |
| ------------------ | ---------------- | ------------------------------------------------------- |
| `--model ` | `anyrouter/free` | Model to use for the session |
| `--print ` | — | One-shot mode: send a prompt, print the reply, and exit |
| `--web` | — | Open the AnyRouter dashboard in your browser |
| `--lang ` | — | Override the display language (e.g. `en`, `ja`) |
| `--version` | — | Print the version |
| `--help` | — | Show usage |
## Choosing a model [#choosing-a-model]
Any model available on AnyRouter works. Pass its id to `--model`:
```bash
npx @anyr/agent --model anyrouter/free # free, no credits
npx @anyr/agent --model z-ai/glm-4.7-flash # fast + affordable
npx @anyr/agent --model anthropic/claude-opus-4 # most capable
npx @anyr/agent --model anyrouter/coding # routes to your BYOK coding model
```
Run `npx @anyr/agent --model anyrouter/free` to explore without spending credits (no daily cap — see [Free Tier](/features/free-tier)), then browse [all available models](https://anyrouter.dev/models) for the full catalog.
## Print mode [#print-mode]
`--print` sends one prompt, prints the reply, and exits — useful for scripting or quick one-liners.
```bash
# Capture the reply in a shell script
ANSWER=$(npx @anyr/agent --print "What is the capital of Japan?")
echo "$ANSWER"
# Pipe a file in as context
npx @anyr/agent --print "$(cat myfile.ts) — add JSDoc comments"
```
## BYOK models [#byok-models]
If you have provider keys configured in your [BYOK settings](https://anyrouter.dev/dashboard/byok), reach them through the dynamic preset ids — each resolves to whichever provider key you set for that slot:
```bash
npx @anyr/agent --model anyrouter/byok # uses your default BYOK key
npx @anyr/agent --model anyrouter/coding # uses your BYOK coding model
npx @anyr/agent --model anyrouter/agent # uses your BYOK agent model
```
These are dynamic presets — they resolve at request time to the provider key you have configured, so the same command follows your BYOK setup across machines.
## Delegating to other agents [#delegating-to-other-agents]
Existing sessions include a `delegate_task` tool that bridges to the CLI's `anyr delegate` (one hop, 10-minute cap). The documented path for delegation is the CLI — see [Delegating between coding agents](/cli/delegate).
## Related [#related]
* [CLI](/cli) — `anyr` launches Claude Code, Codex, and other coding tools through the gateway
* [SDK](/sdk) — `@anyr/sdk` for building with AnyRouter in TypeScript
* [Models](https://anyrouter.dev/models) — full model catalog with pricing
---
# App submissions (/api-reference/app-submissions)
Publish a payload to an app with one request. Any valid AnyRouter API key (`sk-ar-...`) or signed-in session works. Explain is the first app: `app` is `explain`.
## Read the schema [#read-the-schema]
`GET /api/v1/apps/{app}/schema` needs no key. It returns `json_schema`, `version`, `instructions`, `example`, `limits` and `default_visibility`. Validate your payload against `json_schema` before you send it. The JSON Schema covers types and lengths only; the server also checks each app's own rules (for Explain, see [Publish an Explain figure](/guides/publish-explain-figure)).
## Check without saving [#check-without-saving]
Add `?validate_only=1` to the submit request. The server runs every check, then answers `200 {"valid": true}` and stores nothing. It does not count toward the daily limit and does not replay an `Idempotency-Key`.
## Submit [#submit]
```bash
curl https://anyrouter.dev/api/v1/apps/explain/submissions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Idempotency-Key: my-figure-1" \
-H "Content-Type: application/json" \
-d '{"visibility":"unlisted","tags":["locks"],"payload":{...}}'
```
Body fields: `payload` (required), `title`, `visibility` (`private`, `unlisted`, `public`), `tags` (up to 8, `a-z0-9-`), `source_url` (https only). Unknown fields are rejected.
The response is `{id, url, status, visibility}` with `201`. Repeating a request with the same `Idempotency-Key` returns the original with `200`.
Visibility defaults to `unlisted`: anyone with the link can open it, and it is not listed. `public` submissions have status `pending_review` until approved. `private` is visible only to you. Making a submission public again after a change of visibility puts it back in review.
## Manage [#manage]
| Route | Purpose |
| -------------------------------------------- | -------------------------------------------------- |
| `GET /api/v1/apps/{app}/submissions` | Your submissions, newest first (`limit`, `cursor`) |
| `GET /api/v1/apps/{app}/submissions/{id}` | One of yours, with its payload |
| `PATCH /api/v1/apps/{app}/submissions/{id}` | Change `visibility` or `title` |
| `DELETE /api/v1/apps/{app}/submissions/{id}` | Delete it |
Another user's id returns `404`.
## Limits and errors [#limits-and-errors]
* Body at most the app's `limits.max_bytes` (`413 payload_too_large`). An app that also takes uploaded files lists a larger `upload.max_bytes` in its schema. Explain takes a 5 MB body with up to 2 MB per uploaded file, and a JSON figure must still fit in 64 KiB (`422 invalid_payload`, code `too_large`).
* Each user may store at most 50 MB per app (`429 storage_limit_reached`).
* 20 submissions per app per day, UTC (`429 daily_limit_reached`, with `Retry-After`).
* A payload with something that looks like a credential is refused with `422 secret_detected` and the field path. Nothing is stored.
* A payload that fails the schema or the app's checks gets `422 invalid_payload`. `error.metadata.issues` lists each problem as `{path, code, message, fix}`, for example `{"path": "parts[5].on", "code": "host_missing", ...}`.
* Unknown app: `404 app_not_found`.
---
# Artifacts API (/api-reference/artifacts)
An artifact is a page you publish: one file, or a small folder with its styles, scripts and images. It gets a stable URL at `https://anyrouter.dev/a/{id}`. Publishing again makes a new numbered version at the same URL.
Artifacts are **private by default**. `visibility` takes one of three values:
| Value | Who can open it | Listed | Search engines |
| ---------- | ------------------- | ---------------------------------------- | -------------- |
| `private` | Only you | No | No |
| `unlisted` | Anyone with the URL | No | No (`noindex`) |
| `public` | Anyone with the URL | In the [public gallery](#public-gallery) | Yes |
Authenticate with an inference key (`sk-ar-v1-…`), a [management key](/api-reference/management-keys) (`ak_…`), or a signed-in session. A management key needs `read:artifacts` to read and `write:artifacts` to publish, change or delete. A key restricted to specific endpoints must allow the Artifacts endpoint.
## Publish one file [#publish-one-file]
```bash
curl -sS https://anyrouter.dev/api/v1/artifacts \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Hello","files":{"index.html":{"content":"Hi "}}}'
```
```json
{
"id": "k3Vx9TqLm2Ab7Zcd",
"version": 1,
"url": "https://anyrouter.dev/a/k3Vx9TqLm2Ab7Zcd",
"visibility": "private"
}
```
| Field | Type | Notes |
| ------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `files` | object | Path to `{ "content": "...", "encoding": "utf8" \| "base64" }`. Use `base64` for images and fonts. Required unless you send `manifest`. |
| `title` | string | Up to 120 characters. Defaults to the page's `` or first heading. |
| `description` | string | Up to 300 characters. |
| `visibility` | string | `private` (default), `unlisted` or `public`. |
| `show_author` | boolean | Show your name on the page. Defaults to `false`. See [Show your name](#show-your-name). |
| `entry_path` | string | The file to show at the URL. It must be one of your files, with an extension from the [Kinds](#kinds) table. See [How the entry is picked](#how-the-entry-is-picked). |
| `label` | string | A short note for this version, up to 60 characters. |
An inline publish carries up to 5 MB in total. For more, or for a folder, use the manifest flow.
## Publish a folder [#publish-a-folder]
List every file with its size and SHA-256. The response names the files the server still needs. Upload each one, then commit.
```bash
# 1. Open the upload (202)
curl -sS https://anyrouter.dev/api/v1/artifacts \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"title": "Report",
"manifest": [
{"path": "index.html", "size": 1204, "sha256": "<64 hex chars>"},
{"path": "app.js", "size": 5310, "sha256": "<64 hex chars>"}
]
}'
```
```json
{
"upload_id": "up_8Hq2...",
"expires_at": "2026-10-23T12:00:00Z",
"missing": [
{"path": "index.html", "sha256": "…", "size": 1204,
"put_url": "https://anyrouter.dev/api/v1/artifacts/uploads/up_8Hq2.../files/…"}
]
}
```
```bash
# 2. Upload each missing file to its put_url
curl -sS -X PUT "$PUT_URL" \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
--data-binary @index.html
# 3. Commit (201), same response as an inline publish
curl -sS -X POST https://anyrouter.dev/api/v1/artifacts/uploads/$UPLOAD_ID/commit \
-H "Authorization: Bearer $ANYROUTER_API_KEY"
```
The upload is open for one hour. Committing twice is safe: the second call returns the same version. The server checks each file's SHA-256 and rejects a mismatch.
## Publish a new version [#publish-a-new-version]
Send the version you built on as `base_version`.
```bash
curl -sS https://anyrouter.dev/api/v1/artifacts/$ID/versions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"base_version":1,"label":"fix chart","files":{"index.html":{"content":"Hi again "}}}'
```
Files you list are added or replaced. Files you leave out are kept. Set a path to `null` to remove it. The manifest flow accepts the same request plus `delete` (a list of paths to remove) and `keep_unlisted: false` (the manifest is the whole new file set).
If someone published a newer version first, you get `409 version_conflict` with the version to merge against:
```json
{
"error": {
"code": "version_conflict",
"message": "Someone published version 2 first. Read it, merge, and retry with base_version 2.",
"current_version": 2
}
}
```
Set `"force": true` instead of `base_version` to publish on top of whatever is current.
## Read, list and version history [#read-list-and-version-history]
| Request | Returns |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `GET /api/v1/artifacts` | Your live artifacts, newest first. Query `limit` (1 to 100, default 25) and `cursor`; the response carries `next_cursor`. |
| `GET /api/v1/artifacts/{id}` | Metadata, the current version and its file list. `?version=n` reads an older version (owner only). |
| `GET /api/v1/artifacts/{id}/versions` | The version history. |
| `GET /api/v1/artifacts/{id}/files/{path}` | The source of one file (owner only), as a download. |
## Change visibility or details [#change-visibility-or-details]
```bash
curl -sS -X PATCH https://anyrouter.dev/api/v1/artifacts/$ID \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"visibility":"unlisted"}'
```
You can change `title`, `description`, `icon`, `visibility` and `show_author`. Only the owner can change an artifact; anyone else gets `404`.
### Show your name [#show-your-name]
By default the page header reads "by an AnyRouter user", and no API response names you. Set `show_author` to `true` to show your profile's display name (or your username if you have no display name) and link it to your public profile:
```bash
curl -sS -X PATCH https://anyrouter.dev/api/v1/artifacts/$ID \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"show_author":true}'
```
Your email address is never shown. The setting is per artifact and stays when you publish a new version. You can also turn it on or off from the Artifacts page in the dashboard.
## Public gallery [#public-gallery]
Public artifacts appear on [anyrouter.dev/artifact](https://anyrouter.dev/artifact), newest first, with their title, the owner's name if they chose to show it, and when they changed. Private and unlisted artifacts never appear there. Anyone can read the list:
```bash
curl -sS "https://anyrouter.dev/api/v1/artifacts/gallery?limit=24"
```
The response has `data` (each item has `id`, `title`, `description`, `author`, `url`, `updated_at` and `views`; `author` is `{ name, username, profile_url, image_url }` when the owner turned on `show_author`, otherwise `null`) and `next_cursor`. Pass `cursor` to get the next page. Add `sort=popular` to list the most viewed first.
To keep the gallery useful:
* An account can make artifacts `public` 24 hours after sign-up.
* An account can make up to 5 artifacts `public` per day.
* Every artifact page has a **Report** button. AnyRouter can remove an artifact from the gallery or take it down.
## Views [#views]
AnyRouter counts each view of an artifact page by someone other than you, per day. "Visitors" counts each viewer once a day. It is approximate, and no IP address or user identity is stored. Only you can read your numbers:
```bash
curl -sS "https://anyrouter.dev/api/v1/artifacts/$ID/stats?days=30" \
-H "Authorization: Bearer $ANYROUTER_API_KEY"
```
The response has `days` (UTC dates, oldest first), `views` and `visitors` (one number per day) and `total` (all-time views). `GET /api/v1/artifacts/stats?days=14` returns the same series for all your artifacts at once, keyed by id. `days` goes up to 90.
## Delete [#delete]
```bash
curl -sS -X DELETE https://anyrouter.dev/api/v1/artifacts/$ID \
-H "Authorization: Bearer $ANYROUTER_API_KEY"
```
```json
{ "id": "k3Vx9TqLm2Ab7Zcd", "deleted": true }
```
The URL stops working for everyone else at once.
## Kinds [#kinds]
The entry is the file shown at the URL. Its extension decides how viewers see it.
| Extension | Kind | What viewers see |
| -------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------- |
| `.html` | Page | The page itself. |
| `.md` | Markdown | Formatted text. |
| `.svg` | Image | The SVG as an image. Scripts in it do not run. |
| `.mmd`, `.mermaid` | Diagram | A [Mermaid](https://mermaid.js.org) diagram, drawn in the viewer's browser. |
| `.py` `.ts` `.sh` `.bash` `.sql` `.go` `.rs` `.rb` `.java` `.kt` `.swift` `.c` `.h` `.cpp` `.hpp` `.cs` `.php` `.lua` `.yaml` `.yml` `.toml` | Source code | The file as text, with syntax highlighting. |
`.js`, `.mjs`, `.css`, `.json`, `.csv` and `.txt` are never an entry. They are files a page loads, so `index.html` plus `app.js` still opens `index.html`.
Only the entry is wrapped. The same file at another path is a plain download.
### How the entry is picked [#how-the-entry-is-picked]
1. An `entry_path` always wins. It must be in `files` and have an extension from the table above, or you get `400 invalid_entry`.
2. Without `entry_path`, a folder with `.html` or `.md` files opens the only one. With several, `index.html` wins, then `index.md`. Otherwise you get `400 entry_ambiguous`.
3. A folder with no `.html` or `.md` file opens its only `.svg`, `.mmd`, `.mermaid` or source file. With several, `index.svg` wins, then `index.mmd`. Otherwise you get `400 entry_ambiguous`.
4. A folder with no possible entry gets `400 no_entry`.
Adding a `.py` or `.svg` next to a page never changes which file opens. Set `entry_path` to open it.
### Publish a Mermaid diagram [#publish-a-mermaid-diagram]
```bash
curl -sS https://anyrouter.dev/api/v1/artifacts \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"title":"Flow","files":{"flow.mmd":{"content":"graph TD\n A[Request] --> B{Key valid?}\n B -->|yes| C[Route]\n B -->|no| D[401]"}}}'
```
### Limits per kind [#limits-per-kind]
* **SVG:** the file must start with `
Authenticate with an **LLM API key** (`sk-ar-v1-…`) from [/dashboard/keys](https://anyrouter.dev/dashboard/keys). Management keys (`ak_…`) are not accepted on inference routes.
## Request [#request]
| Field | Type | Required | Description |
| ----------------- | ------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model` | string | yes | `provider/model` id (e.g. `openai/gpt-5.4-mini`). See [List models](/api-reference/models). |
| `messages` | array | yes | Ordered conversation. Each message has `role` (`system` / `user` / `assistant` / `tool`) and `content`. |
| `temperature` | number | no | 0–2. Lower is more deterministic. Default: 1. |
| `top_p` | number | no | 0–1. Nucleus sampling. Default: 1. |
| `max_tokens` | integer | no | Upper bound on output tokens. |
| `stream` | boolean | no | If `true`, returns a [streaming SSE response](/guides/streaming). |
| `stop` | string \| string\[] | no | Stop sequences. |
| `tools` | array | no | Function-calling / tool-use spec. |
| `tool_choice` | string \| object | no | Force a specific tool or `auto`. |
| `response_format` | object | no | `{ "type": "json_object" }` for guaranteed JSON. |
| `seed` | integer | no | Deterministic sampling seed (provider-dependent). |
| `user` | string | no | Stable end-user identifier for abuse monitoring. |
| `session_id` | string | no | Sticky-session id (≤256 chars) that groups related requests in the [Request Logs](/features/request-logs#sessions) dashboard. Also settable via the `x-session-id` header; the body field wins if both are sent. |
| `provider` | object | no | Request-level routing preferences such as `only`, `ignore`, `order`, `sort`, `allow_fallbacks`, and `max_price`. See [Smart Routing](/features/routing). |
| `trace` | object | no | Optional tracing metadata. Supports `trace_id`, `trace_name`, `span_name`, `generation_name`, and `parent_span_id`. |
You can also force a provider for a single request with `X-AnyRouter-Provider: openai`, or a comma-separated fallback list such as `X-AnyRouter-Provider: groq,openai`. Every response returns `X-Request-ID` for support correlation and `X-AnyRouter-Provider` for the upstream that served it.
### Routing preferences [#routing-preferences]
`provider` biases or constrains upstream selection without changing the model id:
```json
{
"provider": {
"only": ["OpenAI", "Groq"],
"order": ["Groq", "OpenAI"],
"sort": "ttft",
"allow_fallbacks": true,
"max_price": {
"prompt": "1.00",
"completion": "2.00"
}
}
}
```
Use it when you want a lower-latency provider, a strict price ceiling, or a reproducible preferred backend order. AnyRouter tracks live TTFT and TPS per upstream from real traffic and re-ranks every request.
| `provider.sort` | Sorts by | Direction | When to use |
| --------------- | ------------------------------------- | -------------------- | -------------------------------- |
| `cost` | Listed input price per million tokens | Lowest price first | High-volume, cost-sensitive work |
| `ttft` | Median time-to-first-token (ms) | Lowest latency first | Latency-sensitive workloads |
| `tps` | Median tokens-per-second throughput | Highest first | Long-output generation |
Shortcut suffixes on the model id pick the sort for you: `model:floor` → `cost`, `model:fast` → `ttft`, `model:nitro` → `tps`. Legacy names `price`, `latency`, and `throughput` are accepted aliases for `cost`, `ttft`, and `tps`. Supported routing fields are `only`, `ignore`, `order`, `sort`, `allow_fallbacks`, `max_price.prompt`, `max_price.completion`, `preferred_max_latency`, and `preferred_min_throughput`; other fields are accepted for client compatibility but do not affect local routing.
## Response [#response]
```json
{
"id": "chatcmpl-8yWq4JqLfEjJ9L",
"object": "chat.completion",
"created": 1760000000,
"model": "anthropic/claude-sonnet-4-6",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "NAT maps many private IP addresses to one public IP so devices behind a router can share a single internet connection."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 28,
"completion_tokens": 30,
"total_tokens": 58,
"cost": 0.000342
}
}
```
| `finish_reason` | Meaning |
| ---------------- | ----------------------------------------------------------------- |
| `stop` | Model completed naturally or hit a stop sequence. |
| `length` | Hit `max_tokens`. |
| `tool_calls` | Model invoked one or more tools; caller must run them and resume. |
| `content_filter` | Upstream safety filter triggered. |
| `error` | Upstream provider error; see `error` on the response. |
### Cost of a request [#cost-of-a-request]
`usage.cost` is the USD amount charged to your credits for the request. It is `0` when nothing was charged, for example a free model or a request made with your own provider key ([BYOK](/features/byok)).
| Field | Type | Description |
| -------------------------------------------- | ------ | --------------------------------------------------------------------------------------------- |
| `usage.cost` | number | USD charged for this request. `0` for free models and BYOK. |
| `usage.cost_details.upstream_inference_cost` | number | USD the serving provider reported for the call, when it reports one. Present only when known. |
Where it appears:
* **Non-streaming:** on every response that includes `usage`.
* **Streaming:** with `stream_options.include_usage: true`, on the one usage chunk (an empty `choices` array) that follows the finish chunk, next to `anyrouter_metadata`. Without it, no chunk carries `usage`, as in OpenAI's API. The charge is then `anyrouter_metadata.cost.amount` on the finish chunk, which is sent once the provider ends the stream. `anyrouter_metadata.cost.amount` always equals `usage.cost`, on streaming and non-streaming responses alike. Either way each choice sends a non-null `finish_reason` exactly once, and nothing but `data: [DONE]` follows the last chunk.
* **`/responses`:** `usage.cost` is the charged amount on non-streaming responses and on the terminal `response.completed` event of a stream. The input/output split in `usage.cost_details` is not returned, because it never added up to the charge for cached tokens or non-list prices. `/messages` reports the charged amount as `usage.cost` too: on the response, or on the final `message_delta` of a stream ([Messages](/api-reference/messages#cost-of-a-request)).
The value is the amount debited from your credits, so it matches your usage history.
Once a chunk shows `usage.cost`, that amount is charged even if your client disconnects, or the provider drops the connection, before the stream ends.
## Examples [#examples]
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"messages": [
{ "role": "system", "content": "You are a concise assistant." },
{ "role": "user", "content": "Explain NAT in one sentence." }
],
"max_tokens": 200
}'
```
```python
from openai import OpenAI
client = OpenAI(
base_url="https://anyrouter.dev/api/v1",
api_key="sk-ar-your-key",
)
response = client.chat.completions.create(
model="anthropic/claude-sonnet-4-6",
messages=[
{"role": "system", "content": "You are a concise assistant."},
{"role": "user", "content": "Explain NAT in one sentence."},
],
max_tokens=200,
)
print(response.choices[0].message.content)
```
```typescript
import OpenAI from "openai"
const openai = new OpenAI({
baseURL: "https://anyrouter.dev/api/v1",
apiKey: "sk-ar-your-key",
})
const completion = await openai.chat.completions.create({
model: "anthropic/claude-sonnet-4-6",
messages: [
{ role: "system", content: "You are a concise assistant." },
{ role: "user", content: "Explain NAT in one sentence." },
],
max_tokens: 200,
})
console.log(completion.choices[0].message.content)
```
### Tool use [#tool-use]
Pass a `tools` array with function schemas, then handle any returned `tool_calls`:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-mini",
"messages": [
{ "role": "user", "content": "What is the weather in Tokyo?" }
],
"tools": [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a city",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
}
]
}'
```
```typescript
const response = await client.chat.completions.create({
model: "openai/gpt-5.4-mini",
messages: [{ role: "user", content: "What is the weather in Tokyo?" }],
tools: [
{
type: "function",
function: {
name: "get_weather",
description: "Get the current weather for a city",
parameters: {
type: "object",
properties: { city: { type: "string" } },
required: ["city"],
},
},
},
],
})
```
See [Tool use](/features/tool-use) for a deeper walkthrough.
## Errors [#errors]
All errors follow the standard AnyRouter JSON envelope with a stable `error.code`.
| Status | Meaning | Fix |
| ------ | ------------------------------------------------ | -------------------------------------------------------- |
| 400 | Malformed body or invalid parameters. | Check the request against the field table above. |
| 401 | Missing or invalid API key. | Send a valid `sk-ar-…` key as `Authorization: Bearer`. |
| 402 | Out of credits or upstream BYOK billing failure. | Top up credits, or read `metadata.upstream_message`. |
| 404 | Unknown model. | Verify the id with [List models](/api-reference/models). |
| 429 | Rate limit hit. | Back off; inspect `X-RateLimit-*` response headers. |
| 502 | Upstream 5xx or every fallback failed. | Retry; see `metadata.upstream_attempts`. |
See [Errors](/guides/errors) for the full status table and retry recipes.
---
# Credits API (/api-reference/credits)
Return the credit balance for the workspace tied to your credential. Use it to surface budget state in dashboards, run low-balance alerts, or pre-flight a paid request before you send it.
::endpoint{method="GET" path="/api/v1/credits"}
Authenticate with the same **LLM key** (`sk-ar-v1-…`) you already use for inference — no second credential needed. A **Management key** (`ak_…`) or a signed-in dashboard session also works. Each returns the balance for that credential's workspace.
The one restriction is per-key endpoint scoping: a key whose `allowed_endpoints` allow-list is non-empty and omits `/api/v1/credits` gets `403 insufficient_scope`. Keys created in the dashboard start with the inference endpoints enabled and management endpoints — including `/api/v1/credits` — turned off; enable **Management permissions** when you create the key, or edit it later. Keys created through the API with no allow-list have full access.
## Request [#request]
`GET /api/v1/credits` takes no request body and no query parameters. The balance is scoped to the credential in the `Authorization` header.
```http
GET /api/v1/credits HTTP/1.1
Authorization: Bearer sk-ar-v1-your-key
```
## Response [#response]
```json
{
"balance": 9.734035,
"monthly_balance": 7.5,
"topup_balance": 2.234035,
"used": 0.265965,
"today_cost": 0.042,
"currency": "usd"
}
```
| Field | Type | Description |
| ----------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `balance` | number | Total remaining credit (`monthly_balance + topup_balance`). When this hits zero, paid inference requests return `402` with `error.code: "insufficient_balance"`. |
| `monthly_balance` | number | AnyRouter-issued credits (plan monthly credit, referral/admin grants). **Spent before** top-up credits. New signups do not receive a signup bonus. |
| `topup_balance` | number | Credits you purchased. Spent after monthly credits run out; never expire. |
| `used` | number | Cumulative lifetime spend across every successful inference. |
| `today_cost` | number | Spend recorded since the start of the current UTC day. |
| `currency` | string | Always `"usd"`. |
All values are in **USD**, accurate to six decimal places. One unit of credit equals one US dollar. Both managed and BYOK pricing are pass-through — you're charged the upstream provider's list price with no AnyRouter markup, whether the request is billed against your credit balance or served on your own key. (Card top-ups do carry a separate 5% + 50¢ Polar processing fee — see [Credits & Billing](/features/credits#topping-up) — but that's a payment-provider fee, not a per-request charge.)
If you only use catalog models tagged `:free`, credits are not consumed — `used` stays at zero. The free tier has its own rate limits.
## Examples [#examples]
```bash
curl https://anyrouter.dev/api/v1/credits \
-H "Authorization: Bearer sk-ar-v1-your-key"
```
```python
import httpx
resp = httpx.get(
"https://anyrouter.dev/api/v1/credits",
headers={"Authorization": "Bearer sk-ar-v1-your-key"},
)
balance = resp.json()["balance"]
if balance < 1.0:
print("Low balance — top up before your next big request")
```
```typescript
const resp = await fetch("https://anyrouter.dev/api/v1/credits", {
headers: { Authorization: "Bearer sk-ar-v1-your-key" },
})
const { balance } = await resp.json()
if (balance < 1.0) console.warn("Low balance")
```
## Errors [#errors]
| Status | Meaning | Fix |
| ------ | --------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| 401 | Missing or invalid bearer token (`missing_api_key` / `invalid_api_key`) | Send a valid LLM key, Management key, or dashboard session. |
| 403 | Key scoped to an endpoint allow-list that omits this route (`insufficient_scope`) | Allow `/api/v1/credits` on the key, or use a key with no allow-list. |
| 429 | Rate limited (`rate_limit_exceeded` or `ip_rate_limit_exceeded`) | Back off and honor `Retry-After`. |
| 500 | Unexpected AnyRouter failure (`internal_server_error`) | Retry; contact support if it persists. |
See [API Overview](/api-reference/overview) for the full error envelope.
## Related [#related]
* [Pricing](/features/pricing) — how credits are charged
* [Rate Limits](/features/rate-limits) — free-tier and per-key limits
* [Management API Keys](/api-reference/management-keys) — the `ak_…` keys used here
---
# Document Parse API (/api-reference/documents-parse)
Turn a file into Markdown, plain text, layout blocks and optional chunks for retrieval. OCR runs when the file needs it.
::endpoint{method="POST" path="/api/v1/documents/parse"}
Document parsing is **bring-your-own-key for now**. It runs on your own [Nace](https://console.nace.ai) API key (`nace_sk_…`) and your own Nace credit, and AnyRouter charges nothing for it. Add the key under Provider keys in the dashboard first. Without one the call returns `400` with code `byok_required`.
Authenticate with an **LLM API key** (`sk-ar-v1-…`) from [/dashboard/keys](https://anyrouter.dev/dashboard/keys), sent as `Authorization: Bearer …`.
## Request [#request]
The body is JSON and is sent to Nace unchanged, so every parse option in the [Nace Parse reference](https://console.nace.ai/docs/perception/parse) works here.
| Field | Type | Required | Description |
| ------------------- | ----------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `source` | object | yes | The file. `{"type":"url","url":"https://…","file_name":"report.pdf"}`, a `workspace_file`, or a finished `parse_result`. A `url` must start with `https://` and `file_name` needs an extension. |
| `page_ranges` | `{start,end}[]` | no | One-based, inclusive page ranges. Defaults to all pages. |
| `parse_mode` | `low` \| `medium` \| `high` | no | Effort. Defaults to `low`. |
| `output.formats` | string\[] | no | Any of `markdown`, `text`, `blocks`. Defaults to `markdown` and `blocks`. |
| `chunking.strategy` | `none` \| `page` \| `section` | no | Returns retrieval-sized `chunks`. Defaults to `none`. |
The request body can be up to 256 KiB. Send the file by `https://` URL, or upload it to Nace and name it as a `workspace_file`.
Query parameter `wait_seconds` (integer, 0 to 60) holds the call open until the parse finishes. An optional `Idempotency-Key` header makes a retry return the same job instead of a second one.
```bash
curl "https://anyrouter.dev/api/v1/documents/parse?wait_seconds=60" \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: report-2026-10-01" \
-d '{
"source": {
"type": "url",
"url": "https://example.com/report.pdf",
"file_name": "report.pdf"
},
"page_ranges": [{ "start": 1, "end": 3 }],
"output": { "formats": ["markdown"] }
}'
```
## Response [#response]
**200** when the parse finished within `wait_seconds`:
```json
{
"result_type": "parse",
"file_name": "report.pdf",
"lane": "pdf",
"document": { "page_count": 3, "markdown": "# Annual Report\n\n...", "text": null, "chunks": [] },
"ocr_applied": false,
"units": 3,
"disclosures": []
}
```
**202** when it is still running (or no `wait_seconds` was sent). The body is the job, for example `{"job_id":"…","status":"queued"}`. Read it from Nace with the same key, or send the same request again with the same `Idempotency-Key` and a `wait_seconds` to pick it up.
The answer is relayed as Nace sends it. `X-AnyRouter-Documents-Pages` carries the page count when it is known.
A parse result larger than 8 MiB is refused with `502` and code `upstream_response_too_large`. Narrow `page_ranges`, or drop `blocks`, `chunks` and OCR data from `output`.
## Errors [#errors]
| Status | Code | Meaning |
| ----------- | -------------------------------------------- | -------------------------------------------------------------------------------------- |
| 400 | `byok_required` | No Nace key on your account. Add one, then retry. |
| 400 | `missing_required_fields`, `invalid_request` | Missing or invalid `source`, or `wait_seconds` outside 0 to 60. |
| 413 | `request_too_large` | Body over 256 KiB. |
| 422, 429, … | Nace's own | Nace's error, with its message, for a bad file type, a rate limit or an empty balance. |
| 502 | `upstream_response_too_large` | Result over 8 MiB. |
| 504 | `upstream_timeout` | No answer in time. Retry with the same `Idempotency-Key`. |
## Pricing [#pricing]
Nace bills pages to your own Nace account at its rate (parse is one credit per page). AnyRouter adds no charge; the request shows in [Request Logs](/features/request-logs) with no cost.
---
# Embeddings API (/api-reference/embeddings)
Generate embedding vectors for one or more texts. Drop-in compatible with OpenAI's `POST https://api.openai.com/v1/embeddings` — swap the base URL and key.
::endpoint{method="POST" path="/api/v1/embeddings"}
Authenticate with an **LLM API key** (`sk-ar-v1-…`) from [/dashboard/keys](https://anyrouter.dev/dashboard/keys), sent as `Authorization: Bearer …`. Management keys (`ak_…`) are not accepted.
## Request [#request]
| Field | Type | Required | Description |
| ----------------- | ----------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model` | string | yes | Model id to use for embeddings. See [Models](#models). |
| `input` | string \| string\[] | yes | Text or array of texts to embed. |
| `encoding_format` | `"float"` \| `"base64"` | no | Format for returned embeddings. Defaults to `float`. |
| `dimensions` | integer (≥ 1) | no | Reduce the output to this many dimensions, when the upstream model supports truncation. |
| `user` | string | no | Stable end-user identifier for abuse tracking. Passed through to the upstream. |
| `session_id` | string | no | Sticky-session id (≤256 chars) that groups related requests in [Request Logs](/features/request-logs#sessions). Also settable via the `x-session-id` header; the body field wins. |
Unrecognized fields are forwarded to the upstream — unsupported ones may be ignored or rejected depending on the provider.
### Routing controls [#routing-controls]
AnyRouter routes each request through a fallback chain. Override the chain with these headers (no request body changes):
| Header | Description |
| ---------------------- | ------------------------------------------------------------------------------------------------ |
| `X-AnyRouter-Provider` | Force a specific backend (e.g. `cloudflare`, `deepinfra`). Comma-separated to restrict to a set. |
| `X-Routing-Strategy` | Routing strategy override. Common values: `cheapest`, `fastest`, `most-available`. |
### Caching [#caching]
Embeddings are deterministic for a given `(model, input, dimensions, encoding_format)` tuple. Opt into Cloudflare AI Gateway caching by setting either header on the request:
| Header | Description |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `cf-aig-cache-ttl` | Cache the upstream response for N seconds. Identical subsequent requests are served from the gateway without billing the upstream. |
| `cf-aig-skip-cache` | Set to `true` to bypass the cache for this request. |
When a response comes from the cache, AnyRouter echoes a `X-AnyRouter-Cache-Status: HIT` response header.
## Response [#response]
```json
{
"id": "embd_req_01HXYZ...",
"object": "list",
"data": [
{
"object": "embedding",
"index": 0,
"embedding": [0.0023064255, -0.009327292, 0.015797347]
}
],
"model": "qwen/qwen3-embedding-8b",
"usage": {
"prompt_tokens": 9,
"total_tokens": 9,
"cost": 0.00000011
}
}
```
| Field | Type | Description |
| ----------------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | Unique identifier for the response. Mirrors `X-Request-ID`. |
| `object` | `"list"` | Always `list`. |
| `data` | object\[] | One entry per input string, in request order. |
| `data[].object` | `"embedding"` | Always `embedding`. |
| `data[].index` | integer | Zero-based index of the input that produced this vector. |
| `data[].embedding` | number\[] \| string | Float vector, or base64-encoded bytes when `encoding_format=base64`. |
| `model` | string | The model that served the request. |
| `usage.prompt_tokens` | integer | Token count of the input. |
| `usage.total_tokens` | integer | Same as `prompt_tokens` for embeddings. |
| `usage.cost` | number | USD cost of the request. Present for paid models; omitted when free or pricing is unknown. |
| `usage.prompt_tokens_details` | object | Per-modality token breakdown. Only present when the input contains 2+ modalities and the upstream returns modality-level counts. Sub-fields: `text_tokens`, `image_tokens`, `audio_tokens`, `file_tokens`, `video_tokens`. |
### Response headers [#response-headers]
| Header | Description |
| -------------------------- | ---------------------------------------------------------------------- |
| `X-Request-ID` | Stable request identifier — quote this when filing a support issue. |
| `X-AnyRouter-Provider` | Which backend actually served the request after fallback. |
| `X-AnyRouter-Cache-Status` | Present when AI Gateway caching is enabled. `HIT` / `MISS` / `BYPASS`. |
### Encoding format [#encoding-format]
Request `encoding_format: "base64"` to reduce JSON payload size for large batches:
```json
// float (default)
{ "embedding": [0.0023, -0.0235, 0.0456] }
// base64
{ "embedding": "AAAACgkJMzdG7y..." }
```
### Dimensions [#dimensions]
When the upstream supports it, request a truncated vector. Unsupported `dimensions` values are passed through and may be rejected upstream:
```json
{
"model": "qwen/qwen3-embedding-8b",
"input": "Your text here",
"dimensions": 512
}
```
## Models [#models]
| Model | Dimensions | Context | Notes |
| ------------------------- | ---------- | ------------- | ------------------------------- |
| `qwen/qwen3-embedding-8b` | 4096 | 32,768 tokens | Higher quality, longer context. |
Pricing and live availability are listed on the [Models page](https://anyrouter.dev/models).
## Examples [#examples]
```bash
curl https://anyrouter.dev/api/v1/embeddings \
-H "Authorization: Bearer $ANYROUTER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen/qwen3-embedding-8b",
"input": "The quick brown fox jumps over the lazy dog"
}'
```
```json
{
"model": "qwen/qwen3-embedding-8b",
"input": [
"First text to embed",
"Second text to embed",
"Third text to embed"
]
}
```
The response contains one `data[]` entry per input, ordered by `index`.
```typescript
const embed = (text: string | string[]) =>
fetch("https://anyrouter.dev/api/v1/embeddings", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ANYROUTER_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ model: "qwen/qwen3-embedding-8b", input: text }),
}).then((r) => r.json())
const docs = await embed(documents) // batch
const query = await embed(userQuery) // single
const similarity = cosineSimilarity(query.data[0].embedding, docs.data[0].embedding)
```
Batch inputs — one request with N strings is cheaper and faster than N requests. Embeddings are deterministic per `(model, input)`, so cache by content hash. Normalize vectors before cosine similarity, and pick the smallest model that meets your recall needs.
## Errors [#errors]
| Status | Code | When it happens |
| --------------- | -------------------------------------- | --------------------------------------------------------------------------------- |
| 400 | `missing_required_fields` | `model` or `input` missing. |
| 400 | `invalid_request_error` | Malformed JSON or unsupported field value. |
| 401 | `invalid_api_key` | Missing or invalid bearer token. |
| 402 | `insufficient_balance` | Account balance below the request cost (`metadata.type: "insufficient_credits"`). |
| 404 | `model_unavailable` | No upstream serves the requested model. |
| 429 | `rate_limit_exceeded` | Per-key rate limit hit. |
| 500 / 502 / 503 | `upstream_error`, `upstream_exhausted` | All upstream providers failed. |
Error responses follow the standard [error contract](/guides/errors).
## Related [#related]
* [Models](/api-reference/models) — list and inspect every supported model
* [Errors](/guides/errors) — status codes and fixes
* [Smart Routing](/features/routing) — provider preferences and fallback
---
# Health API (/api-reference/health)
Return a public snapshot of AnyRouter's runtime health: overall `status`, when it was checked, and a `components` breakdown. No API key required. Wire it into status pages or probes.
::endpoint{method="GET" path="/api/v1/health"}
This endpoint is unauthenticated. A default `curl` User-Agent may receive `403` HTML from zone bot protection on the apex — use `https://api.anyrouter.dev/api/v1/health`, or send a descriptive User-Agent such as `-A "my-status-probe/1.0"`.
## Request [#request]
No parameters and no auth header. Recommended polling interval: **≥ 60 s** (see [Caching](#caching)).
```http
GET /api/v1/health HTTP/1.1
```
Prefer the dedicated API host if a client (including default `curl`) receives HTML instead of JSON:
```bash
curl https://api.anyrouter.dev/api/v1/health
```
`https://anyrouter.dev/api/v1/health` is the same Worker.
## Response [#response]
The handler always returns **HTTP 200** on success. Read `status` in the JSON body — do not treat the HTTP code as the health signal.
Each component has its own `status`. They are independent:
* `components.api` is last-hour generation error rate (can be `down` during a traffic spike). BYOK requests (`generations.is_byok = 1`) are excluded entirely — a user's own upstream key is not a platform signal. A failed request only counts as an error when its final attempt was a real platform outage (404/410, any 5xx, or `failure_class` `health`/`capability`) — a key-permission failure (401/403, `credential`) never counts.
* `components.providers` lists **routing backend ids** that served traffic in the last hour (`cloudflare`, not `openrouter-byok`) — not catalog model owners (`openai`, `z-ai`), and not `-byok`/`-pool` backends (a user's or donor's own key is their problem, never a platform incident). Idle backends are omitted. Last-hour generations whose `provider_id` is not a public backend appear as `unattributed` so this list and `components.api.data.requests_1h` share one window. Quarantine, budget-exhausted, and smoke-exclusion overlay the traffic-derived status so a skipped backend cannot read `healthy`. Error counting follows the same real-outage-only rule as `components.api`.
* `components.routing.summary.healthy` is how many **upstreams** are currently routable — it subtracts quarantined and budget-exhausted backends, so it will not read `N/N` while those suppressions are active.
* `components.smoke_tests.last_run` is the **daily** catalog smoke (00:00 UTC). A run from earlier today is expected; `data.stale` is true only when `last_run` is older than two days (2× the daily cadence).
* `events` is a chronological list of upstream status-*change* events (not every probe tick), newest first, capped at 50. `-byok`/`-pool` upstreams never appear here.
* `components.postgres` and `components.clickhouse` are **optional archives**. They report probe status from the homelab latency check. They never move overall `status` — inference, API keys, and billing stay on D1, Analytics Engine, and R2.
```json
{
"status": "healthy",
"description": "All systems operational.",
"version": "1.4.0",
"checked_at": "2026-08-18T12:00:00.000Z",
"components": {
"api": {
"status": "healthy",
"description": "Processing requests normally. 12847 requests in the last hour with 2.1% error rate.",
"note": "Derived from the last 1 hour of traffic.",
"data": {
"requests_1h": 12847,
"error_rate": 0.021,
"latency_ms": { "avg": 340 },
"cache_hit_rate": 0.35
}
},
"providers": {
"status": "healthy",
"description": "All 1 providers that served traffic are operational.",
"note": "Each row is a routing backend id (`generations.provider_id`), not a catalog model owner. Status is the last-hour error rate, overlaid with quarantine / budget-exhausted / smoke-exclusion.",
"data": {
"providers": [
{
"name": "cloudflare",
"status": "healthy",
"description": "Operating normally (5421 requests, 1.2% errors).",
"latency_ms": 120,
"requests_1h": 5421,
"error_rate": 0.012
}
]
}
},
"smoke_tests": {
"status": "healthy",
"description": "All 80 tested models passed.",
"note": "The daily smoke-test workflow (00:00 UTC) sends a real one-token request through chat/completions, responses, and messages.",
"data": {
"total": 80,
"passed": 80,
"failed": 0,
"skipped": 0,
"last_run": "2026-08-18T00:00:00.000Z",
"stale": false,
"cadence": "0 0 * * *"
}
},
"workflows": {
"status": "healthy",
"description": "All 8 scheduled workflows are running on cadence.",
"note": "Each scheduled workflow that has recorded a run is checked against its last run. Jobs that have never run are omitted.",
"data": {
"healthy": 8,
"stale": 0,
"failing": 0,
"never_ran": 0,
"workflows": [
{
"kind": "gateway-log-sync",
"label": "Gateway log sync",
"state": "healthy",
"last_status": "success",
"last_run_at": "2026-08-18T11:45:00.000Z",
"age_hours": 0.1,
"detail": "Last run success 0.1h ago."
}
]
}
},
"routing": {
"status": "healthy",
"description": "All 84 routing upstreams are healthy.",
"note": "Live routing health from the smoke-cooldown store, plus active quarantine and monthly-budget suppression.",
"data": {
"summary": {
"total": 84,
"healthy": 84,
"deprioritized": 0,
"excluded": 0,
"quarantined": 0,
"budget_exhausted": 0
},
"degraded": []
}
}
},
"events": [
{
"at": "2026-08-18T11:10:00.000Z",
"upstream_id": "cerebras",
"from": "healthy",
"to": "degraded",
"message": "HTTP 503: Service Unavailable"
}
]
}
```
| Field | Type | Description |
| ------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `status` | `"healthy" \| "degraded" \| "partial" \| "down" \| "no_signal"` | Overall state. Same enum is used on every component. `partial` means some components are degraded while routing still works. `no_signal` means there is not enough recent traffic to assess. |
| `description` | string | Human-readable summary of the overall state. |
| `version` | string | Release tag of the currently-deployed Worker. |
| `checked_at` | string | ISO 8601 timestamp of this snapshot. |
| `components` | object | Breakdown for `api`, `providers`, `smoke_tests`, `workflows`, `routing`, `postgres`, and `clickhouse`. `smoke_tests`, `workflows`, and `routing` may be `null`. Postgres and ClickHouse are optional archives and never move overall `status`. |
| `events` | array | Chronological upstream status-change events, newest first (capped at 50). Never every probe tick — only rows where the status changed from the previous check. |
Every component object has `status` (same enum), `description`, `note` (how the check is derived), and `data`.
| Nested field | Type | Description |
| --------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `components.api.data.requests_1h` | integer | Requests in the last hour. |
| `components.api.data.error_rate` | number | Fraction of those requests that errored (0–1). |
| `components.api.data.latency_ms.avg` | number | Average latency in milliseconds. |
| `components.api.data.cache_hit_rate` | number | Fraction of requests that hit the prompt cache (0–1). |
| `components.providers.data.providers[]` | array | Per-**backend** rows (`name` is a routing backend id such as `cloudflare`, not a catalog owner). Includes `status`, `description`, optional `latency_ms` / `error_rate`, and `requests_1h`. Only backends with last-hour traffic appear. Not a top-level `providers` array. |
| `components.smoke_tests.data` | object | `total`, `passed`, `failed`, `skipped`, `last_run` (ISO 8601 or `null`), `stale` (true only after 2× the daily cadence), and `cadence` (`0 0 * * *`). |
| `components.workflows.data` | object | Counts `healthy`, `stale`, `failing`, `never_ran`, plus `workflows[]` (`kind`, `label`, `state`, `last_status`, `last_run_at`, `age_hours`, `detail`). Public health omits jobs that have never run, so `never_ran` is always `0` and `workflows[]` has no `never_ran` rows. Jobs whose schedule is intentionally paused are omitted for the same reason — a paused job is not failing, and it has not proven it runs on cadence either. |
| `components.routing.data.summary` | object | `total`, `healthy`, `deprioritized`, `excluded`, `quarantined`, `budget_exhausted`. `healthy` subtracts quarantined / budget-exhausted backends that still look smoke-healthy. |
| `components.routing.data.degraded[]` | array | Degraded upstreams with `id` (routing backend id, e.g. `google-agent-platform`), `name` (display label — not unique), `logo`, and `state` (`deprioritized`, `excluded`, `quarantined`, or `budget_exhausted`). A backend degraded in more than one way (for example both quarantined and out of budget) appears once per state. |
| `events[]` | array | Each entry has `at` (ISO 8601), `upstream_id`, `from`/`to` (`healthy`, `degraded`, `down`, or `unknown`), and an optional `message` from the probe. |
A binary probe can treat any top-level `status` other than `healthy` as not fully operational. Pair provider rows with [`/api/v1/providers`](/api-reference/providers) for logos and descriptions.
Responses also include `X-RateLimit-Limit`, `X-RateLimit-Window`, and `X-RateLimit-Tier` (anonymous callers use the IP tier). A 429 honors `Retry-After`. See [Rate Limits](/features/rate-limits).
## Examples [#examples]
```bash
curl https://api.anyrouter.dev/api/v1/health
```
```python
import httpx
health = httpx.get("https://api.anyrouter.dev/api/v1/health").json()
if health["status"] != "healthy":
print(health["description"], health["components"]["providers"]["status"])
```
```typescript
const res = await fetch("https://api.anyrouter.dev/api/v1/health")
const health = await res.json()
if (health.status !== "healthy") {
console.warn(health.description, health.components)
}
```
## Status codes [#status-codes]
| HTTP | Body `status` | Meaning |
| ---- | --------------------------------------------- | ----------------------------------------------------------------------------------------------- |
| 200 | `healthy` | All scored components are healthy. |
| 200 | `partial` / `degraded` / `no_signal` / `down` | One or more components are unhealthy or lack traffic. Inference may still succeed via fallback. |
| 429 | — | Anonymous IP cap (300 req / 60s). Honor `Retry-After`. |
This endpoint does not use HTTP 503 for a `down` component — read `status` and `components.*.status`.
## Caching [#caching]
Anonymous responses are cacheable. The Worker stamps:
```http
Cache-Control: public, max-age=60, s-maxage=300, stale-while-revalidate=600
```
Browsers may reuse the body for 60 seconds; the shared edge may keep it for 5 minutes and serve a stale copy for up to 10 minutes while one request refreshes it. Poll at **≥ 60 s**.
## Related [#related]
* [Providers](/api-reference/providers) — catalog of upstream backends
* [Models](/api-reference/models) — the catalog
* [Rate Limits](/features/rate-limits) — `X-RateLimit-*` headers
* [API Overview](/api-reference/overview) — every endpoint at a glance
---
# Images API (/api-reference/images)
AnyRouter does not offer image or video generation. It is a **text and embeddings** gateway.
`POST /api/v1/images/generations` returns HTTP **410** with `error.code` `endpoint_gone`. There is no replacement media product.
Use these inference surfaces instead:
* [Chat Completions](/api-reference/chat-completions)
* [Messages](/api-reference/messages)
* [Responses](/api-reference/responses)
* [Embeddings](/api-reference/embeddings)
Vision **input** on chat models (sending an image as part of a prompt and getting text back) is unchanged. That is not image generation.
## Related [#related]
* [API Overview](/api-reference/overview)
* [Embeddings](/api-reference/embeddings)
* [OCR](/api-reference/ocr) — extract text from images
---
# API Keys (/api-reference/keys)
Manage the **LLM API keys** (prefix `sk-ar-`) your workspace uses to call inference endpoints. List, create, inspect, update, and delete keys from a script or CI pipeline.
::endpoint{method="POST" path="/api/v1/keys"}
The CRUD routes (`GET`/`POST /api/v1/keys`, `PATCH`/`DELETE /api/v1/keys/{hash}`) authenticate with a **Management key** (`ak_…`) carrying `read:llm-keys` / `write:llm-keys` scope, or a signed-in dashboard session. An **LLM key** (`sk-ar-…`) is **not** accepted on these routes — it is only used on `GET /api/v1/auth/key` to inspect itself.
## Request [#request]
The primary write route is `POST /api/v1/keys`, which mints a new LLM key. Send a JSON body:
| Field | Type | Required | Description |
| ----------------------- | ---------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name` | string | Yes | Human-readable key name. |
| `expires_at` | string \| null | No | ISO 8601 expiration timestamp. |
| `limit` | number \| null | No | Credit cap for the key. |
| `limit_reset` | `daily` \| `weekly` \| `monthly` \| null | No | Rolling window `limit` is measured over. `null` is measured over 30 days, not for the key's lifetime. |
| `include_byok_in_limit` | boolean | No | Whether BYOK usage counts toward the key limit. |
| `rate_limit` | number | No | Requests per minute for this key. |
| `disabled` | boolean | No | Create the key in a disabled state. |
| `allowed_models` | string\[] \| null | No | Restrict the key to specific model ids. Ids are matched without regard to case. `anyrouter/auto` and `anyrouter/free` only route to listed models that are in their own pool; to use any other listed model, call it directly. |
| `allowed_endpoints` | string\[] \| null | No | Restrict the key to specific API paths. |
| `tpm` | number \| null | No | Tokens-per-minute ceiling. |
### Other routes [#other-routes]
| Method & path | Purpose |
| --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GET /api/v1/auth/key` | Return metadata about the **LLM key** sent as the Bearer token. |
| `GET /api/v1/key` | OpenRouter-compatible current-key info. With an LLM key (`sk-ar-…`, including sign-in app keys) it returns only that key: `data.label`, `limit`, `limit_remaining`, `usage`, `is_free_tier`, `rate_limit`. With a dashboard session or Management key it returns the workspace's newest active key. |
| `GET /api/v1/keys` | List workspace keys. Pass `?include_disabled=true` to include disabled keys. |
| `GET /api/v1/keys/{hash}` | Return one workspace key by its stored hash. |
| `PATCH /api/v1/keys/{hash}` | Update `name`, `disabled`, `include_byok_in_limit`, `limit`, `limit_reset`, `rate_limit`, or `expires_at`. |
| `DELETE /api/v1/keys/{hash}` | Soft-delete a key by hash. |
| `POST /api/v1/keys/{hash}/rotate` | Issue a new secret for the same key. The old secret stops working immediately; the key's `id`, limits, restrictions and usage history are unchanged. |
## Response [#response]
`POST /api/v1/keys` returns the plaintext secret **once** alongside the key record:
```json
{
"key": "sk-ar-v1-actual-secret-only-shown-once",
"data": {
"id": "key_123",
"hash": "f3b8...",
"name": "ci-smoketest",
"label": "sk-ar-v1",
"created_at": "2026-04-24T00:00:00.000Z",
"updated_at": null,
"expires_at": null,
"disabled": false,
"real_usage": 0,
"real_usage_daily": 0,
"real_usage_weekly": 0,
"real_usage_monthly": 0,
"real_usage_monthly_requests": 0,
"limit": null,
"limit_remaining": null,
"limit_reset": null,
"include_byok_in_limit": false,
"rate_limit": 30,
"is_free_tier": true,
"is_management_key": false,
"creator_user_id": "user_123",
"last_used_at": null,
"allowed_models": ["openai/gpt-5.4-mini"],
"allowed_endpoints": ["/api/v1/chat/completions"],
"tpm": null
}
}
```
`limit_remaining` is `limit` minus the key's spend **in that window** — a `daily`
key's is measured over the last 24h, a `weekly` one over 7 days, and a `monthly`
(or `null`) one over 30 days — so it can rise again as the window rolls.
`POST /api/v1/keys/{hash}/rotate` returns the same envelope with a **new**
plaintext secret and the key's **new** `hash`. Everything else is carried over
unchanged — `id`, `name`, `limit`, `allowed_*`, spend totals — so any
`/key?id=` or `api_key_id` reference keeps resolving. The old secret stops
authenticating the moment the call returns, so update every deployment holding
it before you rotate. The request takes no body; two concurrent rotates of the
same key are rejected with `409` rather than silently discarding one secret.
`GET /api/v1/keys` returns a `{ "object": "list", "data": [...] }` envelope of key records (same shape, without the plaintext `key`). `GET /api/v1/auth/key` returns a compact record:
```json
{
"key": {
"prefix": "sk-ar-v1",
"name": "production-backend",
"created_at": "2026-04-24T00:00:00.000Z",
"rate_limit": 60,
"usage": 128,
"limit": null,
"is_free_tier": true
}
}
```
`DELETE /api/v1/keys/{hash}` performs a soft-delete and returns:
```json
{
"deleted": true,
"id": "key_123"
}
```
## Examples [#examples]
```bash
curl https://anyrouter.dev/api/v1/keys \
-X POST \
-H "Authorization: Bearer ak_your-management-key" \
-H "Content-Type: application/json" \
-d '{
"name": "ci-smoketest",
"rate_limit": 30,
"allowed_models": ["openai/gpt-5.4-mini"],
"allowed_endpoints": ["/api/v1/chat/completions"]
}'
```
```python
import httpx
resp = httpx.post(
"https://anyrouter.dev/api/v1/keys",
headers={"Authorization": "Bearer ak_your-management-key"},
json={
"name": "ci-smoketest",
"rate_limit": 30,
"allowed_models": ["openai/gpt-5.4-mini"],
"allowed_endpoints": ["/api/v1/chat/completions"],
},
)
print(resp.json()["key"]) # shown only once
```
```typescript
const resp = await fetch("https://anyrouter.dev/api/v1/keys", {
method: "POST",
headers: {
Authorization: "Bearer ak_your-management-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
name: "ci-smoketest",
rate_limit: 30,
allowed_models: ["openai/gpt-5.4-mini"],
allowed_endpoints: ["/api/v1/chat/completions"],
}),
})
const { key } = await resp.json() // shown only once
```
The plaintext `key` is returned **once** on creation and never again. Store it in a secret manager immediately.
## Errors [#errors]
All key endpoints return the standard error envelope:
```json
{
"error": {
"code": "authentication_required",
"message": "Authentication required",
"metadata": {
"type": "authentication_error"
}
}
}
```
| Status | Meaning | Fix |
| ------ | ----------------------------- | ---------------------------------------------------------- |
| 401 | Missing or invalid credential | Send a valid Management key (`ak_…`) or dashboard session. |
| 403 | Key lacks the required scope | Grant `read:llm-keys` / `write:llm-keys`. |
| 404 | No key with that hash | Check the `hash` from a `GET /api/v1/keys` listing. |
---
# Management API Keys (/api-reference/management-keys)
Management keys (`ak_…`) let you administer your AnyRouter account from scripts, CI pipelines, or other tools — without your dashboard password. They authenticate the management plane: LLM key CRUD, presets, BYOK providers, credits, and your own management keys.
::endpoint{method="POST" path="/api/v1/management-keys"}
A management key is authenticated by sending it as a **Bearer token** (`Authorization: Bearer ak_live_…`). Management keys are **not** for LLM inference — to call `/api/v1/chat/completions` or `/api/v1/messages`, use an **LLM key** (`sk-ar-…`) from [/dashboard/keys](https://anyrouter.dev/dashboard/keys).
## Two kinds of keys [#two-kinds-of-keys]
| Key type | Prefix | Purpose | Where to manage |
| ----------------------- | -------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| **LLM API keys** | `sk-ar-` | Send chat, messages, embeddings, image, and responses requests | [/dashboard/keys](https://anyrouter.dev/dashboard/keys) |
| **Management API keys** | `ak_` | Programmatically manage your account: list/create/revoke LLM keys, configure BYOK, read credits | [/dashboard/settings#keys](https://anyrouter.dev/dashboard/settings#keys) |
## Request [#request]
Create a key from the dashboard (**Create key** → name it → select scopes → copy the secret shown **once**), or programmatically from a session-authed request:
| Field | Type | Required | Description |
| ------------- | --------- | -------- | ---------------------------------------- |
| `name` | string | Yes | Human-readable key name. |
| `description` | string | No | Free-text note (e.g. what uses the key). |
| `scopes` | string\[] | Yes | The scopes this key grants (see below). |
| `expires_at` | string | No | ISO 8601 expiration timestamp. |
### Scopes [#scopes]
Scopes follow the `action:resource` pattern. `write:X` implicitly grants `read:X`. Grant the smallest set a key needs — a leaked `read:credits`-only key cannot create LLM keys or change BYOK.
| Scope | Grants access to |
| ----------------------- | ------------------------------------------------------------------------------------------------------------- |
| `read:llm-keys` | `GET /api/v1/keys`, `GET /api/v1/key` |
| `write:llm-keys` | `POST /api/v1/keys`, `PATCH /api/v1/keys/{hash}`, `DELETE /api/v1/keys/{hash}` |
| `read:presets` | `GET /api/v1/presets`, `GET /api/v1/presets/{slug}` |
| `write:presets` | `POST /api/v1/presets`, `PATCH /api/v1/presets/{slug}`, `DELETE /api/v1/presets/{slug}` |
| `read:byok` | `GET /api/v1/auth/byok/...` |
| `write:byok` | `POST/PATCH/DELETE /api/v1/auth/byok/...` |
| `read:credits` | `GET /api/v1/credits` |
| `read:analytics` | `GET /api/v1/analytics/*` (read-only; there is no write scope) |
| `read:management-keys` | `GET /api/v1/management-keys` |
| `write:management-keys` | `POST /api/v1/management-keys`, `DELETE /api/v1/management-keys/{id}` |
| `read:artifacts` | `GET /api/v1/artifacts`, `GET /api/v1/artifacts/{id}` and its versions |
| `write:artifacts` | `POST /api/v1/artifacts`, `POST /api/v1/artifacts/{id}/versions`, `PATCH` and `DELETE /api/v1/artifacts/{id}` |
| `read:security` | `GET /api/v1/security/keys`, `GET /api/v1/security/ip-allowlist` |
| `write:security` | `POST /api/v1/security/ip-allowlist`, `DELETE /api/v1/security/ip-allowlist/{id}` |
### Other routes [#other-routes]
| Method & path | Purpose |
| ------------------------------------- | ---------------------------------------------------------------------------------------- |
| `GET /api/v1/management-keys` | List your management keys. Pass `?include_invalid=true` to include revoked/expired keys. |
| `DELETE /api/v1/management-keys/{id}` | Revoke a key immediately (optional `revocation_reason` body). |
## Response [#response]
`POST /api/v1/management-keys` returns the plaintext `secret` **once** — store it immediately. Revocation is immediate: the next request with a revoked key returns `401`.
## Examples [#examples]
```bash
curl -X POST https://anyrouter.dev/api/v1/management-keys \
-H "Content-Type: application/json" \
--cookie "__session=" \
-d '{
"name": "CI deploy bot",
"description": "Used by the GitHub Actions deploy workflow",
"scopes": ["read:credits", "write:llm-keys"],
"expires_at": "2027-01-01T00:00:00Z"
}'
```
```bash
curl https://anyrouter.dev/api/v1/credits \
-H "Authorization: Bearer ak_live_..."
```
```bash
curl https://anyrouter.dev/api/v1/management-keys \
-H "Authorization: Bearer ak_live_..."
```
```bash
curl -X DELETE https://anyrouter.dev/api/v1/management-keys/ \
-H "Authorization: Bearer ak_live_..." \
-H "Content-Type: application/json" \
-d '{"revocation_reason": "Rotated for quarterly audit"}'
```
## BYOK keys [#byok-keys]
A management key with `read:byok` lists your provider keys; `write:byok` adds, rotates, donates, and removes them — the same operations as the BYOK page in the dashboard.
| Method & path | Scope | Purpose |
| -------------------------------------- | ------------ | -------------------------------------------------------------------------- |
| `GET /api/v1/auth/byok` | `read:byok` | List your keys, grouped by provider. |
| `POST /api/v1/auth/byok` | `write:byok` | Add a key. |
| `PATCH /api/v1/auth/byok/keys/{keyid}` | `write:byok` | Edit or rotate one key in place, keeping its id and its pool donation. |
| `POST /api/v1/byok/donated` | `write:byok` | Donate a key to the shared pool (requires explicit consent in the body). |
| `DELETE /api/v1/byok/donated/{id}` | `write:byok` | Withdraw a donation — the key goes back to serving only your own requests. |
Every key object carries two fields beyond the id, provider, label, and quota:
| Field | Values | Meaning |
| -------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `kind` | `byok`, `donated` | `donated` while the key is shared with the community [shared pool](/features/shared-pool), including while it is temporarily paused; `byok` when it serves only your own requests. Read-only — it changes when you donate or withdraw, not by editing the key. |
| `source` | `cli`, `dashboard`, `api` | Which app added the key. `api` is the default when you don't pass one, and for keys added before the field existed. Set once, at creation. |
Pass `source` when adding a key so you can tell one script's keys from another's later:
```bash
curl -X POST https://anyrouter.dev/api/v1/auth/byok \
-H "Authorization: Bearer ak_live_..." \
-H "Content-Type: application/json" \
-d '{"provider_id": "openai", "api_key": "sk-proj-…", "label": "laptop", "source": "cli"}'
```
`source` accepts `cli`, `dashboard`, or `api`; any other value is rejected with `400 source must be 'cli', 'dashboard', or 'api'`. The dashboard sends `dashboard` for keys you add there. The installed `anyr` binary doesn't send `cli` yet, so keys it adds report `api` until a new release ships — set it yourself to tell your scripts' keys apart.
```json
{
"id": "byok_01JQZ…",
"provider_id": "openai",
"label": "laptop",
"key_preview": "sk-p****7c2",
"enabled": true,
"route_coverage": "all",
"kind": "byok",
"source": "cli"
}
```
`key_preview` is masked — the first four characters, `****`, and the last three. The key itself is never returned, and keys are listed per workspace.
## Organization-scoped keys [#organization-scoped-keys]
If your account belongs to an organization, keys you create while the org is active are **owned by the org** — any member with access can use them and revocation is org-wide. Personal keys are owned by your user and only authenticate you.
Store secrets in a secret manager (1Password, Vault, GitHub Actions Secrets), never in source control. Use the narrowest scope set, set an `expires_at` where possible, rotate on a schedule, and revoke any key you suspect is compromised.
## Errors [#errors]
| Status | Meaning | Fix |
| ------ | ------------------------------------- | --------------------------------------------------- |
| 401 | Key invalid, revoked, or expired | Create a fresh key from the dashboard. |
| 403 | Key valid but lacks the route's scope | Add the required scope, or use a higher-scoped key. |
## Related [#related]
* [API Keys](/api-reference/keys) — the `sk-ar-…` LLM keys these credentials manage
* [Credits API](/api-reference/credits) — read balance with `read:credits`
* [Presets API](/api-reference/presets) — manage presets with `write:presets`
* [BYOK](/features/byok) — configure provider keys with `write:byok`
---
# MCP API Reference (/api-reference/mcp)
AnyRouter runs a [Model Context Protocol](https://modelcontextprotocol.io) server that exposes your workspace — models, keys, presets, credits, and status — as JSON-RPC tools. This page is the protocol reference: tools, arguments, scopes, errors, and the manual auth path for clients that can't run OAuth.
::endpoint{method="POST" path="/api/v1/mcp"}
Authenticate with an OAuth 2.1 bearer token, an **LLM key** (`sk-ar-v1-…`, grants the unscoped tools such as `list_models` and `recommend_model`), or a **Management key** (`ak_…`) carrying the scopes the tools you call require. See [Manual authentication](#manual-authentication).
For step-by-step client setup (Claude Desktop, Claude Code, Cursor, OpenCode, …), see [MCP Server](/mcp). The MCP server does **not** expose a chat tool — clients call the [Chat Completions](/api-reference/chat-completions), [Messages](/api-reference/messages), or [Responses](/api-reference/responses) endpoints directly.
## Request [#request]
* **Endpoint:** `POST https://anyrouter.dev/api/v1/mcp`
* **Protocol:** JSON-RPC 2.0 over HTTPS (Streamable HTTP transport)
* **Content type:** `application/json`
* **Authentication:** OAuth 2.1 bearer token, or a static API key (see below)
### Tools [#tools]
| Tool | Purpose | Arguments | Scope |
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
| `list_models` | Browse models your key can call. | `provider`, `capability` (catalog tag), `task` (`translation`, `coding`, `reasoning`, `vision`, `cheap`), `free_only`, `min_context`, `sort` (`price`, `context`), `include_unavailable`, `limit` (default 100) | None |
| `recommend_model` | Rank models this caller can dispatch for one job. | `task` (required), `free_only`, `max_input_per_1m`, `min_context`, `limit` (default 5) | None |
| `get_usage` | Request, token, and dollar totals for a recent window, plus the top models. | `days` (default 7, max 90) | `read:analytics` |
| `list_byok_keys` | Provider-key status (enabled, validation). Never returns the secret. | `enabled_only` (optional) | `read:byok` |
| `list_okf_concepts` | Browse the AnyRouter knowledge base (Open Knowledge Format). | `type`, `search`, `limit` (default 200) | None |
| `get_okf_concept` | Read one knowledge-base concept in full. | `id` | None |
| `get_credits` | Workspace balance and lifetime usage totals. | None | `read:credits` |
| `list_keys` | List workspace LLM API keys. | `include_disabled` (default `false`) | `read:llm-keys` |
| `create_key` | Create a new LLM API key. | `name`, `label` (optional), `rate_limit` (optional) | `write:llm-keys` |
| `revoke_key` | Disable an LLM API key. | `key_id` | `write:llm-keys` |
| `list_presets` | List workspace presets. | None | `read:presets` |
| `list_conversations` | Search workspace conversations. | `starred`, `limit` (default 50), `search` | `read:llm-keys` |
| `publish_prompt` | Publish a prompt to the community prompt library (starts as pending review). | `title`, `description`, `body`, `category`, plus optional `tags`, `platforms`, `recommended_agents`, `recommended_model` | `write:hub` |
| `star_prompt` | Save a community prompt to your personal collection. | `prompt_id` | `write:hub` |
| `fork_prompt` | Fork a community prompt into your own copy (private by default). | `prompt_id`, plus optional `title`, `body`, `visibility` | `write:hub` |
| `publish_skill` | Sync a single `SKILL.md` to a hub so your machines and agents can install it. | `body`, plus optional `name`, `slug`, `tags`, `hub` | `write:hub` |
| `list_hubs` | List the Skills & Knowledge hubs your workspace owns. | None | `read:hub` |
| `get_hub` | Get one hub (by slug) with its items. | `slug` | `read:hub` |
| `search_public_hubs` | Search public hubs you can subscribe to. | `search` (optional), `limit` (default 20) | `read:hub` |
| `create_hub` | Create a new hub. | `name`, plus optional `slug`, `description`, `visibility` (default `workspace`) | `write:hub` |
| `hub_add_item` | Add an item (skill, prompt, knowledge doc, or plugin) to a hub. | `hub`, `body`, plus optional `kind` (default `kb`), `name`, `slug`, `tags` | `write:hub` |
| `hub_import_skill` | Import a skill from skills.sh / GitHub into a hub. | `hub`, `source_ref` | `write:hub` |
| `publish_artifact` | Publish a page, diagram, image or source file (text files, up to 1 MB) and get its URL. The entry can be `.html`, `.md`, `.svg`, `.mmd` or a source file. Private by default; pass `id` and `base_version` for a new version. | `files`, plus optional `title`, `visibility`, `id`, `base_version`, `label`, `entry_path` | `write:artifacts` |
| `list_artifacts` | List your artifacts, newest first. | `limit` (default 20) | `read:artifacts` |
| `get_artifact` | Read one artifact's metadata and file list. | `id` | `read:artifacts` |
| `delete_artifact` | Delete an artifact. Its URL stops working at once. | `id` | `write:artifacts` |
| `set_artifact_visibility` | Make an artifact `private`, `unlisted` or `public`. | `id`, `visibility` | `write:artifacts` |
| `get_system_status` | AnyRouter health snapshot: overall status plus per-component breakdowns (API, providers, smoke tests). | None | None |
The `list_hubs` … `hub_import_skill` tools manage the [Skills & Knowledge Hub](/hub) from inside your agent. The artifact tools publish and manage pages; see the [Artifacts API](/api-reference/artifacts).
### Admin tools [#admin-tools]
Accounts registered as AnyRouter administrators see additional tools (hidden from `tools/list` for everyone else). They work with either an LLM key (`sk-ar-v1-*`) or a management key (`ak_*`).
| Tool | Purpose | Arguments |
| ----------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------- |
| `admin_list_endpoints` | List every admin API endpoint callable via `admin_request`. | `method` (optional), `search` (optional) |
| `admin_request` | Call any admin API endpoint. | `method`, `path`, `query` (optional), `body` (optional), `confirm` |
| `admin_overview` | Platform overview: users, requests, cost, revenue, DAU/MAU, top models. | None |
| `admin_usage_summary` | Cost / revenue / token / request totals by provider and model. | `days` (default 30) |
| `admin_north_star` | North-star growth metrics: activation rate and weekly-active CLI users. | `days` (default 30) |
| `admin_list_users` | List users with balance, plan, and activity. | `limit`, `offset`, `include_stale` |
| `admin_get_user` | Rich detail for one user. | `user_id` |
| `admin_errors_overview` | Error overview and timeline. | `days` (default 1) |
| `admin_smoke_test` | Latest smoke-test snapshot. | None |
| `admin_upstream_health` | Live per-upstream health (healthy / deprioritized / excluded). | None |
| `admin_grant_credits` | Grant credits to a user. | `user_id`, `amount`, `description` (optional), `confirm` (required) |
`admin_request` is the general gateway; `admin_list_endpoints` returns the full live catalog. Endpoints flagged **dangerous** (destructive or costly) require `confirm: true`.
### Scopes and consent bundles [#scopes-and-consent-bundles]
When a client requests authorization, the user picks one bundle on the AnyRouter consent screen. Users can downgrade from what a client requests; a tool needing a scope outside the granted bundle returns `permission denied`.
| Bundle | Includes | Use when |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| **Read-only** (default) | `read:llm-keys`, `read:presets`, `read:credits`, `read:analytics`, `read:byok`, `read:hub`, `read:connections`, `inference` | The client browses models, credits, recent usage, and provider-key status without making changes. |
| **Standard** | Read-only + `write:llm-keys`, `write:presets`, `write:hub`, `write:connections` | The client creates/revokes keys, edits presets, or manages hub items. |
`read:hub` / `write:hub` gate the [Skills & Knowledge Hub](/hub) tools. `read:credits` gates `get_credits`, `read:analytics` gates `get_usage`, and `read:byok` gates `list_byok_keys`. `list_models`, `recommend_model`, `get_system_status`, and the knowledge-base tools need no scope. An LLM key (`sk-ar-v1-*`) can call those unscoped tools and nothing else.
## Manual authentication [#manual-authentication]
Clients that cannot run OAuth authenticate with a static bearer token: an **LLM API key** (`sk-ar-v1-*`, grants the unscoped tools) or a **Management API key** (`ak_*`) with the scopes you need.
```bash
curl https://anyrouter.dev/api/v1/mcp \
-H "Authorization: Bearer ak_your_management_key" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}'
```
## Response [#response]
A tool call returns a normal JSON-RPC success envelope. The tool's output lives in `result.content` — an array of content blocks. AnyRouter tools return a single `text` block whose text is a JSON payload:
```json
{
"jsonrpc": "2.0",
"result": {
"content": [{ "type": "text", "text": "{ \"total\": 3, \"data\": [ ... ] }" }]
},
"id": 1
}
```
### Tool failures [#tool-failures]
When a tool fails (an invalid argument, an unknown id, an upstream error), the call **still returns a successful JSON-RPC envelope**. Per the MCP spec, the failure is reported *in-band* on the result with `isError: true` and the message in `result.content[0].text` — there is no top-level `error` object:
```json
{
"jsonrpc": "2.0",
"result": {
"content": [{ "type": "text", "text": "Key key_123 not found in this workspace." }],
"isError": true
},
"id": 1
}
```
Detect a failed tool call by checking `result.isError` — **not** `response.error`. A top-level `error` object is reserved for protocol/transport failures, not tool failures. For example, an unauthenticated request returns HTTP `401` with:
```json
{
"jsonrpc": "2.0",
"error": { "code": -32001, "message": "Unauthorized" },
"id": null
}
```
## Errors [#errors]
| Case | Meaning | Fix |
| ------------------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| No workspace bound | The credential isn't associated with a workspace. | Create a key from the dashboard first. |
| Unauthorized | The credential is invalid, revoked, or expired. | Check [/dashboard/keys](https://anyrouter.dev/dashboard/keys) or [/dashboard/settings#keys](https://anyrouter.dev/dashboard/settings#keys). |
| Permission denied | The credential lacks the tool's scope. | Pick a higher consent bundle or upgrade the management key's scopes. |
## Rate limits [#rate-limits]
The MCP endpoint is rate limited to protect your workspace and keep the service responsive:
* **Per credential** — up to 120 tool calls per minute for each API key or token.
* **Per IP** — up to 300 requests per minute from a single IP address (applied before authentication, so it also covers failed sign-ins).
Crossing a limit returns HTTP `429` with a `Retry-After` header and a JSON-RPC error body:
```json
{
"jsonrpc": "2.0",
"error": { "code": -32000, "message": "Rate limit exceeded — retry after 60s." },
"id": null
}
```
These limits are on the management tier and are separate from the [inference rate limits](/features/rate-limits) that apply to Chat Completions, Messages, and Responses.
## Auto-connect config [#auto-connect-config]
The anyr CLI reads one workspace setting to decide whether to connect this gateway in the coding agents it launches (`anyr claude`, `anyr codex`, `anyr opencode`, `anyr grok`).
```bash
curl https://anyrouter.dev/api/v1/mcp/config \
-H "Authorization: Bearer $ANYROUTER_API_KEY"
```
```json
{
"workspace_id": "ws_123",
"enabled": true,
"setting": null,
"default": true,
"url": "https://anyrouter.dev/api/v1/mcp",
"auth": { "header": "Authorization", "scheme": "Bearer", "credential": "caller_key" },
"can_edit": true
}
```
| Field | Meaning |
| ---------- | ----------------------------------------------------------------------------------------------------------- |
| `enabled` | The value in effect: `setting` when it is set, otherwise `default`. |
| `setting` | Your saved choice, or `null` to follow the default. |
| `default` | `true` when the workspace has at least one live (not paused) MCP connection, otherwise `false`. |
| `url` | The gateway URL to connect. |
| `auth` | How to authenticate: send the caller's own key as a bearer token. The response never contains a credential. |
| `can_edit` | Whether the caller may change the setting. |
`GET` accepts an inference key (`sk-ar-v1-…`) or a signed-in session. A management key (`ak_…`) is not accepted here. A key whose endpoint allow-list leaves out MCP gets `403`.
To change the setting, send `PATCH` from a signed-in dashboard session as the workspace owner. Send `{ "enabled": true }` or `{ "enabled": false }`, or `{ "enabled": null }` to go back to the default. The response has the same shape as `GET`.
To opt out on one machine instead, pass `--no-mcp` on a launch or run `anyr mcp auto off`.
## Revoking access [#revoking-access]
To revoke an MCP client's access, open [/dashboard/mcp](https://anyrouter.dev/dashboard/mcp), find the client under **Connected clients**, and click **Revoke**. The token is deleted immediately; subsequent tool calls return `401 Unauthorized`. Revoking does not uninstall the client config — the next tool invocation simply re-runs OAuth.
Access tokens are bearer tokens — treat them like passwords. All connections use HTTPS; the endpoint refuses unencrypted HTTP. If you lose a device or suspect a leak, revoke the client on [/dashboard/mcp](https://anyrouter.dev/dashboard/mcp) immediately. Starting a new consent flow mints a new token; old tokens stay valid until explicitly revoked.
## Related [#related]
* [MCP Server](/mcp) — step-by-step client setup
* [Management API Keys](/api-reference/management-keys) — the `ak_…` keys and scopes used here
* [Credits API](/api-reference/credits) — what `get_credits` returns
---
# Messages API (/api-reference/messages)
The Messages API is an Anthropic-compatible endpoint. Any tool that speaks the Anthropic Messages protocol — including Claude Code and the official `@anthropic-ai/sdk` — works against AnyRouter by changing the base URL.
::endpoint{method="POST" path="/api/v1/messages"}
Authenticate with an **LLM API key** (`sk-ar-v1-…`) from [/dashboard/keys](https://anyrouter.dev/dashboard/keys), passed via either `Authorization: Bearer …` or Anthropic's `x-api-key` header. Management keys (`ak_…`) are not accepted.
## Request [#request]
Unlike the OpenAI-compatible endpoints, the Messages API expects Anthropic's auth headers.
| Header | Required | Description |
| ------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------- |
| `x-api-key` | yes | Your AnyRouter API key (prefixed `sk-ar-`). `Authorization: Bearer sk-ar-…` is also accepted. |
| `anthropic-version` | recommended | Anthropic API version. Dated values (`YYYY-MM-DD`, e.g. `2023-06-01`) are accepted; a missing header is allowed. |
| `Content-Type` | yes | `application/json`. |
Request body fields:
| Field | Type | Required | Description |
| ---------------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model` | string | yes | Any catalog model id (e.g. `anthropic/claude-sonnet-4-6`, `openai/gpt-4o`, `anyrouter/coding`). |
| `messages` | array | yes | `[{role, content}]` — `content` can be a string or rich blocks. |
| `max_tokens` | integer | yes | Required by the Messages API. |
| `system` | string | no | System prompt (Anthropic splits this out of `messages`). |
| `temperature` | number | no | 0–1. |
| `stream` | boolean | no | SSE streaming. |
| `stop_sequences` | string\[] | no | Stop sequences. |
| `tools` | array | no | Tool definitions in Anthropic's schema. |
| `session_id` | string | no | Sticky-session id (≤256 chars) that groups related requests in [Request Logs](/features/request-logs#sessions). Also settable via the `x-session-id` header; the body field wins. |
## Response [#response]
```json
{
"id": "msg_01XY...",
"type": "message",
"role": "assistant",
"model": "anthropic/claude-sonnet-4-6",
"content": [{ "type": "text", "text": "Hello there." }],
"stop_reason": "end_turn",
"usage": { "input_tokens": 10, "output_tokens": 4, "cost": 0.000342 }
}
```
### Cost of a request [#cost-of-a-request]
`usage.cost` is the USD amount charged to your credits for the request. It is `0` when nothing was charged, for example a free model or a request made with your own provider key ([BYOK](/features/byok)). It matches your usage history.
* **Non-streaming:** `usage.cost` is on the response.
* **Streaming:** `usage.cost` is on the `usage` of the final `message_delta` event, sent when the model has finished. No event is added or reordered.
It is an extra key beside `input_tokens` and `output_tokens`. Anthropic's SDKs ignore keys they do not know, so existing clients such as Claude Code keep working unchanged. The same field is on [chat completions](/api-reference/chat-completions#cost-of-a-request).
### Supported models [#supported-models]
The Messages endpoint accepts **any model in the catalog**, not just Anthropic ones. `anthropic/*` models are sent to Anthropic-native upstreams as a passthrough; every other model — including OpenAI-compatible providers and AnyRouter virtual models such as `anyrouter/coding` — is transparently translated to and from the Chat Completions dialect, so the request and response you see stay in Anthropic Messages format.
This means Anthropic-native clients (e.g. Claude Code) can target the full catalog through `/messages` without switching to [chat completions](/api-reference/chat-completions).
## Examples [#examples]
```bash
curl https://anyrouter.dev/api/v1/messages \
-H "x-api-key: sk-ar-your-key" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-haiku-4-5",
"max_tokens": 1024,
"messages": [
{ "role": "user", "content": "Say hi in 5 words" }
]
}'
```
```typescript
import Anthropic from "@anthropic-ai/sdk"
const client = new Anthropic({
baseURL: "https://anyrouter.dev/api",
apiKey: "sk-ar-your-key",
})
const response = await client.messages.create({
model: "anthropic/claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello!" }],
})
```
For the Anthropic SDK the base URL is `https://anyrouter.dev/api` — the SDK appends `/v1/messages` itself.
## Errors [#errors]
Errors use the standard AnyRouter envelope. An unknown provider or an unrouteable model returns `unknown_provider` or `no_upstream`:
```json
{
"error": {
"code": "no_upstream",
"message": "No upstream is available for \"\".",
"metadata": { "type": "upstream_error" }
}
}
```
| Status | Meaning | Fix |
| ------ | ---------------------------------------- | -------------------------------------------------------------- |
| 400 | Malformed body, or `max_tokens` missing. | `max_tokens` is required by the Messages API. |
| 401 | Missing or invalid key. | Send a valid `sk-ar-…` key via `x-api-key` or `Authorization`. |
| 404 | Unknown model or no upstream. | Verify the id with [List models](/api-reference/models). |
See [Errors](/guides/errors) for the full status table and retry guidance.
---
# Models API (/api-reference/models)
AnyRouter exposes the model catalog via REST. Every model is uniquely identified by a `provider/model` slug — the same format used in the chat completions `model` field.
The JSON list is `GET /api/v1/models` (or `https://api.anyrouter.dev/api/v1/models`). Use that path when you need JSON — some hosts serve HTML on other `/models` URLs.
::endpoint{method="GET" path="/api/v1/models"}
::endpoint{method="GET" path="/api/v1/models/{owner}/{model}"}
::endpoint{method="GET" path="/api/v1/models/{owner}/{model}/routing-preview"}
::endpoint{method="GET" path="/api/v1/models/count"}
::endpoint{method="GET" path="/api/v1/providers"}
The list endpoint works anonymously, but authenticate with an **LLM API key** (`sk-ar-v1-…`) from [/dashboard/keys](https://anyrouter.dev/dashboard/keys) to also receive your saved presets in the response.
## Request [#request]
### List models [#list-models]
Returns models the current caller can reach — platform routes, matching BYOK keys, and (when you send an LLM API key) that key's allow/deny lists. Anonymous requests omit BYOK-only rows such as Anthropic Sonnet. Pass `all=1` or `include=unavailable` for the full catalog, including models you cannot call yet.
Accepts these query parameters:
| Param | Type | Description |
| ------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `all` | `1` / `true` / `yes` | Include models the current caller cannot reach. |
| `include` | `unavailable` / `all` | Same as `all=1`. |
| `provider` | string | Filter by provider id (`openai`, `anthropic`, `google`, …). |
| `category` | string | `text` / `image` / `audio` / `embedding`. |
| `min_context` | integer | Minimum context window in tokens. |
| `capability` | string | Repeatable. E.g. `?capability=vision&capability=function_calling`. |
| `supported` | string | Legacy comma-separated capability filter kept for compatibility. |
| `privacy` | `0` / `false` | Skip the authenticated caller's `blocked_providers` filter. Route health and BYOK still apply unless `all=1`. |
| `presets` | `0` | Omit your saved presets from an authenticated response (included by default when authenticated). |
| `sort` | string | `added` (aliases `newest`, `new`, `created`; newest first), `usage` (aliases `top`, `top-usage`, `popular`; 7-day request volume), or `name` (alias `id`). |
### Get one model [#get-one-model]
`GET /api/v1/models/{owner}/{model}` — use the catalog slug as two path segments. For `anthropic/claude-sonnet-4-6`, request `/api/v1/models/anthropic/claude-sonnet-4-6`. Nested routes on the same id (`/routing-preview`, `/metrics`, `/privacy`, `/uptime`) accept that slash form too. The single-segment encoded form (`anthropic%2Fclaude-sonnet-4-6`) still works.
Platform auto-routing presets (`anyrouter/auto`, `anyrouter/free`, and the other first-party `anyrouter/*` ids) expose their live failover chain at `GET /api/v1/models/anyrouter/auto/routing-preview` (no auth for platform presets). The unique member list is the same order inference will try.
### Count [#count]
`GET /api/v1/models/count` returns `{ "count": N }`, the number of rows the list endpoint returns for the same query. It accepts the same filters (`all`, `include`, `provider`, `category`, ...), so the default count is the reachable catalog and `?all=1` is the full catalog. Saved presets are not counted.
## Response [#response]
```json
{
"object": "list",
"data": [
{
"id": "openai/gpt-5.4-mini",
"object": "model",
"provider": "OpenAI",
"providers": ["OpenAI", "Groq"],
"category": "text",
"context_length": 128000,
"pricing": {
"prompt": "10",
"completion": "30",
"input_per_1m": 10,
"output_per_1m": 30
},
"capabilities": ["chat", "function-calling", "vision"],
"links": {
"details": "/api/v1/models/openai/gpt-4-turbo",
"metrics": "/api/v1/models/openai%2Fgpt-4-turbo/metrics"
}
}
]
}
```
`provider` is the organization that owns the model. `providers` lists the public upstream providers that can serve it.
### Who pays: `pricing.billing` [#who-pays-pricingbilling]
Every model carries `pricing.billing`, one field that says how a request is paid for:
* `"credits"`: a paid AnyRouter route serves the model and your credits pay for it.
* `"free"`: a public $0 route serves it on the free tier.
* `"byok"`: every route needs your own provider key. AnyRouter charges $0 and your provider bills you.
The default list can include `"byok"` models, so read the field instead of assuming the list is the credits list. For `"byok"` models, `pricing.list_pricing` (`input_per_1m`, `output_per_1m`) holds the provider's published rate when it is known. Each `per_upstream` entry priced at 0 only because it is BYOK also carries `billing: "byok"`, plus `list_pricing` when published. Treat `0` on those routes as "billed by your provider", not free.
### Reasoning and output limits [#reasoning-and-output-limits]
The model response exposes only facts present in the catalog:
* `reasoning.levels` and `reasoning.default` are the exact model-level effort enum when the model declares one. The field is omitted when no enum is declared; clients must not invent a level list.
* `per_upstream[].reasoning` is a route-specific effort override. If it is omitted, the route inherits the model-level enum. `per_upstream[].features.reasoning` is a capability flag, not an effort list.
* `supported_parameters` remains authoritative. For a selected route, use `per_upstream[].supported_parameters`; the top-level field is a model-level/legacy projection and is not a promise shared by every fallback route. The API does not add a reasoning parameter because a model advertises reasoning.
* `top_provider.max_completion_tokens` is the strictest completion cap declared by any serving upstream, or `null` when no upstream declares one. It is not a promise that an undeclared route is unlimited. Per-route caps are available at `per_upstream[].context.max_completion_tokens`.
* AnyRouter does not infer universal toggle, token-budget, or interleaved-thinking support from a model name or description. Toggle and budget controls remain endpoint-specific (`reasoning.enabled` / `reasoning.max_tokens` on Responses, `thinking` on Messages). Anthropic interleaved thinking is a provider beta request (`anthropic-beta: interleaved-thinking-2025-05-14`), not a model or preset capability flag; whether the selected provider honors it is provider-specific.
### Your presets in the response [#your-presets-in-the-response]
When the request is authenticated, the response also includes a `presets` array of your account's saved [presets](/api-reference/presets) alongside `data`. This lets a model picker show your presets next to raw models in a single call:
```json
{
"object": "list",
"data": [ /* … models … */ ],
"presets": [
{
"id": "preset_abc123",
"object": "preset",
"slug": "code-reviewer",
"name": "Code Reviewer",
"description": "Strict senior-engineer review.",
"config": { "model": "anthropic/claude-sonnet-4-6", "temperature": 0.2 },
"created_at": "2026-03-01T12:00:00Z",
"updated_at": "2026-03-01T12:00:00Z"
}
]
}
```
The field is omitted entirely for unauthenticated requests. Pass `?presets=0` to skip it on an authenticated request. Reference a preset in any inference call with the `@preset/` model syntax.
### List providers [#list-providers]
`GET /api/v1/providers` lists the upstream backends AnyRouter dispatches to:
```json
{
"object": "list",
"data": [
{
"id": "openai",
"name": "OpenAI",
"description": "GPT models",
"website": "https://openai.com",
"status": "operational",
"model_count": 4,
"regions": ["us", "eu"],
"model_prefix": "openai/",
"endpoints": ["/api/v1/chat/completions", "/api/v1/responses", "/api/v1/models"]
}
]
}
```
## Examples [#examples]
```bash
# Models you can call without a key (anonymous). Use the api. host if apex returns HTML.
curl https://api.anyrouter.dev/api/v1/models
# Full catalog, including BYOK-only rows
curl "https://api.anyrouter.dev/api/v1/models?all=1"
# Models reachable with this LLM API key
curl https://anyrouter.dev/api/v1/models \
-H "Authorization: Bearer sk-ar-your-key"
# Filter + newest first
curl "https://anyrouter.dev/api/v1/models?provider=anthropic&min_context=200000&sort=added" \
-H "Authorization: Bearer sk-ar-your-key"
# Top usage this week
curl "https://anyrouter.dev/api/v1/models?sort=usage"
# One model (slash path)
curl https://anyrouter.dev/api/v1/models/anthropic/claude-sonnet-4-6 \
-H "Authorization: Bearer sk-ar-your-key"
# Live failover chain for a platform preset (slash path; %2F also works)
curl https://anyrouter.dev/api/v1/models/anyrouter/auto/routing-preview
# List providers (no auth required)
curl https://anyrouter.dev/api/v1/providers
```
```python
import requests
resp = requests.get(
"https://anyrouter.dev/api/v1/models",
params={"provider": "anthropic", "min_context": 200000},
headers={"Authorization": "Bearer sk-ar-your-key"},
)
for model in resp.json()["data"]:
print(model["id"], model["context_length"])
```
```typescript
const res = await fetch(
"https://anyrouter.dev/api/v1/models?provider=anthropic&min_context=200000",
{ headers: { Authorization: "Bearer sk-ar-your-key" } },
)
const { data } = await res.json()
data.forEach((m: { id: string; context_length: number }) =>
console.log(m.id, m.context_length),
)
```
## Errors [#errors]
| Status | Meaning | Fix |
| ------ | -------------------------------------------- | --------------------------------------------------------------------------------------- |
| 401 | Invalid API key on an authenticated request. | Send a valid `sk-ar-…` key, or drop the header to list anonymously. |
| 404 | Unknown model id. | Use `/api/v1/models/{owner}/{model}` and confirm the slug appears in the list response. |
See [Errors](/guides/errors) for the full status table.
---
# Moderations API (/api-reference/moderations)
Check whether text is harmful before you send it to a model or show it to a user. The request and response follow OpenAI's `POST /v1/moderations`, so the OpenAI SDKs work by changing the base URL and key. Guard models such as NVIDIA Nemotron Content Safety and Llama Guard do the classification.
::endpoint{method="POST" path="/api/v1/moderations"}
Authenticate with an **LLM API key** (`sk-ar-v1-…`) from [/dashboard/keys](https://anyrouter.dev/dashboard/keys), sent as `Authorization: Bearer …`.
## Request [#request]
| Field | Type | Required | Description |
| ------------ | ----------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `input` | string \| string\[] \| `{type:"text", text}`\[] | yes | Up to 32 texts, each up to 32,000 characters. Images are not supported. |
| `model` | string | no | A moderation model id. Defaults to `nvidia/nemotron-3.5-content-safety`. OpenAI ids such as `omni-moderation-latest` use the default too. |
| `session_id` | string | no | Groups related requests in [Request Logs](/features/request-logs#sessions). |
```bash
curl https://anyrouter.dev/api/v1/moderations \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input": ["What is a good pasta recipe?", "How do I buy a gun with no background check?"]}'
```
## Response [#response]
```json
{
"id": "modr-req_01HXYZ",
"model": "nvidia/nemotron-3.5-content-safety",
"results": [
{
"flagged": false,
"categories": { "harassment": false, "illicit/violent": false, "violence": false },
"category_scores": { "harassment": 0, "illicit/violent": 0, "violence": 0 },
"guard_categories": []
},
{
"flagged": true,
"categories": { "harassment": false, "illicit/violent": true, "violence": false },
"category_scores": { "harassment": 0, "illicit/violent": 1, "violence": 0 },
"guard_categories": ["guns_and_illegal_weapons"]
}
],
"usage": { "prompt_tokens": 955, "completion_tokens": 24, "total_tokens": 979, "cost": 0 }
}
```
The example is shortened. Every result lists all 13 OpenAI categories: `harassment`, `harassment/threatening`, `hate`, `hate/threatening`, `illicit`, `illicit/violent`, `self-harm`, `self-harm/intent`, `self-harm/instructions`, `sexual`, `sexual/minors`, `violence`, `violence/graphic`.
| Field | Description |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `results[]` | One result per input, in input order. |
| `flagged` | `true` when the guard model judged the text unsafe. |
| `categories` | OpenAI categories the guard's verdict maps to. |
| `category_scores` | `1` for a flagged category, else `0`. Guard models return a verdict, not a probability. |
| `guard_categories` | The guard model's own labels. Some (privacy, defamation, elections, …) have no OpenAI category: the text is still `flagged`, but no OpenAI category is set. |
| `usage` | Tokens summed over every input. Priced at the model's per-1M token rates. |
## Models [#models]
Moderation models appear under the **Moderation** tab at [anyrouter.dev/models](https://anyrouter.dev/models):
| Model | Notes |
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| `nvidia/nemotron-3.5-content-safety` | Default. Returns categories. |
| `meta/llama-guard-4-12b` | MLCommons hazard codes S1–S14. Requires your own DeepInfra or OpenRouter key ([BYOK](/features/byok)). |
| `openai/gpt-oss-safeguard-20b` | Policy-following reasoning classifier. Requires your own key (Groq, OpenRouter, Vercel, Hugging Face, or Nous). |
## Errors [#errors]
| Status | Code | When |
| ------ | --------------------------------- | -------------------------------------------------------------------- |
| 400 | `invalid_request` | `input` is missing, empty, contains an image, or exceeds the limits. |
| 400 | `model_not_moderation_compatible` | The model is not a moderation model. |
| 502 | `upstream_invalid_response` | The guard model replied without a verdict. |
| 502 | `upstream_error` | Every provider for the model failed. |
---
# Network Stats API (/api-reference/network-stats)
Return aggregate statistics about token consumption across the AnyRouter network: total tokens processed, the top models by consumption, and 24h/7d/30d activity trends. Every value is network-wide, never per-user.
::endpoint{method="GET" path="/api/v1/analytics/network"}
This endpoint is **public** — no authentication is required.
## Request [#request]
`GET /api/v1/analytics/network` takes no request parameters and no auth headers.
The snapshot includes:
* **Total tokens processed** — input, output, cached, and reasoning tokens over the last 30 days.
* **Token burns** — the top 15 models ranked by total token consumption (30-day window).
* **Activity trends** — hourly token volumes for the last 24 hours, daily volumes for 7 and 30 days.
All data is **purely aggregate**. No per-user, per-workspace, or per-key data is ever returned. Model identifiers (`model_id`) are public catalog ids visible on the models page.
## Response [#response]
```json
{
"generated_at": "2026-06-26T12:00:00.000Z",
"totals": {
"tokens": 1234567890,
"requests": 50000,
"prompt_tokens": 800000000,
"completion_tokens": 400000000,
"cached_tokens": 30000000,
"reasoning_tokens": 4567890
},
"top_models": [
{
"model_id": "openai/gpt-4o",
"model_name": "GPT-4o",
"provider": "OpenAI",
"rank": 1,
"tokens": 500000000,
"requests": 25000,
"share": 0.4051
}
],
"trend": {
"24h": [
{ "bucket": "2026-06-26 11:00", "tokens": 5000000, "requests": 200 }
],
"7d": [
{ "bucket": "2026-06-25", "tokens": 80000000, "requests": 3200 }
],
"30d": [
{ "bucket": "2026-06-01", "tokens": 40000000, "requests": 1600 }
]
}
}
```
| Field | Type | Description |
| -------------------------- | -------------- | ---------------------------------------------------------------------------------- |
| `generated_at` | string \| null | ISO 8601 timestamp of when the snapshot was computed. `null` before the first run. |
| `totals.tokens` | number | Total tokens processed in the last 30 days. |
| `totals.requests` | number | Total inference requests in the last 30 days. |
| `totals.prompt_tokens` | number | Input tokens. |
| `totals.completion_tokens` | number | Output tokens. |
| `totals.cached_tokens` | number | Cached (prompt cache hit) tokens. |
| `totals.reasoning_tokens` | number | Reasoning (chain-of-thought) tokens. |
| `top_models[].model_id` | string | Public catalog model identifier. |
| `top_models[].model_name` | string | Human-readable model name. |
| `top_models[].share` | number | Fraction of total tokens consumed (0–1, 4 dp). |
| `trend["24h"]` | array | Hourly buckets for the last 24 hours (`"YYYY-MM-DD HH:00"`). |
| `trend["7d"]` | array | Daily buckets for the last 7 days (`"YYYY-MM-DD"`). |
| `trend["30d"]` | array | Daily buckets for the last 30 days (`"YYYY-MM-DD"`). |
## Examples [#examples]
```bash
curl https://anyrouter.dev/api/v1/analytics/network
```
```python
import httpx
stats = httpx.get("https://anyrouter.dev/api/v1/analytics/network").json()
print("Tokens (30d):", stats["totals"]["tokens"])
for m in stats["top_models"][:5]:
print(m["rank"], m["model_name"], m["tokens"])
```
```typescript
const stats = await (
await fetch("https://anyrouter.dev/api/v1/analytics/network")
).json()
console.log("Tokens (30d):", stats.totals.tokens)
```
## Caching [#caching]
The snapshot is computed every 10 minutes by a background workflow and served with:
```http
Cache-Control: public, max-age=600, s-maxage=600
```
The `generated_at` field shows exactly when the data was last refreshed.
A visual representation of this data is live at [anyrouter.dev/network](https://anyrouter.dev/network) — a dashboard with the total-token counter, token-burn bar chart, and trend sparklines.
## Related [#related]
* [Models API](/api-reference/models) — the individual model-id catalog
* [Providers API](/api-reference/providers) — the upstream-backend catalog
---
# OAuth (/api-reference/oauth)
Building the OpenRouter-style flow where a user approves your app on `https://anyrouter.dev/auth` and your app receives their API key? That flow is OpenRouter-compatible and needs no existing key. See the [OAuth PKCE guide](/guides/oauth-pkce). Its code exchange is a public `POST /api/v1/auth/keys` with `{ code, code_verifier, code_challenge_method }`, returning `{ key, user_id }`. The rest of this page covers minting a key from a key you already hold, and device authorization.
This page covers two flows that both **require an AnyRouter account you already control**: minting an additional key via PKCE, and the device authorization grant for browser-less apps. Neither is the right fit for "let anonymous users sign in with their own AnyRouter account and bill their own credits" — that's [Sign in with AnyRouter](/guides/sign-in-with-anyrouter), a separate open-registration OAuth 2.1 flow with no bearer key required to start.
## Mint a key via PKCE [#mint-a-key-via-pkce]
A lightweight flow so a trusted local tool (already holding one of your keys, e.g. a CLI or a dev server on `localhost:3000`) can generate a *new* scoped key without you copying it out of the dashboard by hand. Two endpoints drive it: create an authorization code, then exchange it for a new key. **This is not an anonymous sign-in flow** — creating the code authenticates with an existing `sk-ar-…` key, so an app with no key cannot start it. Building "sign in with AnyRouter" for third-party users? Use the [Sign in with AnyRouter guide](/guides/sign-in-with-anyrouter) instead.
::endpoint{method="POST" path="/api/v1/auth/keys/code"}
Creating the code authenticates with an **LLM API key** (`sk-ar-…`) sent as the Bearer token. The exchange needs no key: the `code_verifier` proves it is you. The new key is created in your personal workspace.
## The flow [#the-flow]
::::steps
:::step{title="Create an authorization code"}
`POST /api/v1/auth/keys/code` with the PKCE challenge and callback details.
| Field | Type | Required | Description |
| ----------------------- | -------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
| `callback_url` | string | Yes | Any `https` URL, or `http` on `localhost`, `127.0.0.1` or `[::1]` on any port. |
| `code_challenge` | string | Yes | PKCE challenge. |
| `code_challenge_method` | `S256` \| `plain` | No | Defaults to `S256`. `plain` is accepted. |
| `expires_at` | string \| null | No | Expiration of the **new key** (future ISO 8601 timestamp). The authorization code itself always expires after 10 minutes. |
| `key_label` | string | No | Label for the eventual generated key. |
| `limit` | number | No | Credit cap for the key. |
| `usage_limit_type` | `daily` \| `weekly` \| `monthly` | No | Reset interval for `limit`. |
The response returns the authorization code id:
```json
{
"data": {
"id": "authcode_123",
"app_id": 1,
"created_at": "2026-04-24T00:00:00.000Z"
}
}
```
:::
:::step{title="Exchange the code for a key"}
`POST /api/v1/auth/keys` with the code and the PKCE verifier. No `Authorization` header is needed.
| Field | Type | Required | Description |
| ----------------------- | ----------------- | -------- | ---------------------------------- |
| `code` | string | Yes | Authorization code id from step 1. |
| `code_verifier` | string | Yes | PKCE verifier (43-128 characters). |
| `code_challenge_method` | `S256` \| `plain` | No | Must match step 1 when sent. |
The response returns the plaintext key **once**:
```json
{
"key": "sk-ar-v1-actual-secret-only-shown-once",
"user_id": "user_123"
}
```
:::
::::
## Examples [#examples]
```bash
curl https://anyrouter.dev/api/v1/auth/keys/code \
-X POST \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"callback_url": "https://example.com:443/callback",
"code_challenge": "pkce_challenge_value",
"code_challenge_method": "S256",
"key_label": "oauth-app",
"limit": 25,
"usage_limit_type": "monthly"
}'
```
```bash
curl https://anyrouter.dev/api/v1/auth/keys \
-X POST \
-H "Content-Type: application/json" \
-d '{
"code": "authcode_123",
"code_verifier": "pkce_verifier_value",
"code_challenge_method": "S256"
}'
```
`callback_url` must be an `https://` URL, or `http://` on `localhost`, `127.0.0.1` or `[::1]` (any port). Authorization codes expire after 10 minutes and are single-use, so exchange them promptly.
## Device authorization [#device-authorization]
For apps that can't open a browser — a CLI, a terminal tool, a headless box — use the device authorization grant ([RFC 8628](https://datatracker.ietf.org/doc/html/rfc8628)). Your app shows the user a short code; the user approves it on any device with a browser; your app polls until the credential is ready.
Unlike the flow above, this one needs no existing API key: the user's approval in the browser *is* the authentication.
`client_id` is **optional**, but if you send one it must already exist. Unregistered apps should either omit `client_id` entirely (and pass `client_name` instead — see the table below) or register first via [`POST /api/v1/mcp/oauth/register`](/guides/sign-in-with-anyrouter#quickstart) to get a real `client_id`. Sending a made-up `client_id` fails with `400 invalid_client`, even though no key was required to get this far:
```json
{ "error": "invalid_client", "error_description": "Unknown client_id" }
```
::::steps
:::step{title="Request a device code"}
`POST /api/v1/oauth/device/code`. Accepts form-encoded or JSON; every field is optional.
| Field | Type | Description |
| ------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client_name` | string | Name shown to the user on the approval screen. Ignored if `client_id` is set and resolves — the registered name is shown instead. |
| `client_id` | string | Optional. **Must be a `client_id` returned by `POST /api/v1/mcp/oauth/register`** — omit it entirely for an unregistered app; don't invent one. An unrecognized `client_id` returns `400 invalid_client`. |
| `scope` | string | Space-separated permissions to request, e.g. `inference read:credits write:llm-keys`. Unrecognized scope strings are dropped; if none survive, the response is `400 invalid_scope`. Omit to request the full device-flow set; the user can narrow it further at approval. |
| `key_label` | string | Suggested name for the key that gets created. |
```json
{
"device_code": "8f2a…",
"user_code": "K4RT-9WPZ",
"verification_uri": "https://anyrouter.dev/cli/device",
"verification_uri_complete": "https://anyrouter.dev/cli/device?code=K4RT-9WPZ",
"expires_in": 600,
"interval": 5
}
```
Show the user `user_code` and `verification_uri`. If you can open a link for them, use `verification_uri_complete` — it pre-fills the code.
:::
:::step{title="Poll for the token"}
`POST /api/v1/oauth/token` with `grant_type=urn:ietf:params:oauth:grant-type:device_code`, no more often than `interval` seconds.
While the user hasn't finished, you get a `400` with one of:
| `error` | Meaning |
| ----------------------- | ------------------------------------------------------ |
| `authorization_pending` | Not approved yet. Keep polling. |
| `slow_down` | You polled too fast. Wait at least `interval` seconds. |
| `access_denied` | The user declined. Stop polling. |
| `expired_token` | The code expired or was already used. Start over. |
On approval you get the credential:
```json
{
"access_token": "sk-ar-v1-actual-secret-only-shown-once",
"token_type": "Bearer",
"scope": "inference read:credits",
"user_id": "user_123"
}
```
:::
::::
```bash
curl https://anyrouter.dev/api/v1/oauth/device/code \
-X POST \
-H "Content-Type: application/json" \
-d '{"client_name": "My CLI", "scope": "inference read:credits"}'
```
```bash
curl https://anyrouter.dev/api/v1/oauth/token \
-X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=urn:ietf:params:oauth:grant-type:device_code" \
-d "device_code=8f2a..."
```
`scope` is a request, not a guarantee. The approval screen lets the user hand over less than you asked for, so read the `scope` field on the response to see what you actually received. Users can revoke access at any time from their dashboard.
## Errors [#errors]
Errors use the standard envelope:
```json
{
"error": {
"code": "bad_request",
"message": "callback_url is required",
"metadata": {
"type": "invalid_request_error"
}
}
}
```
| Status | Meaning | Fix |
| ------ | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| 400 | Missing/invalid field (e.g. `callback_url`) | Send a valid `https` (or loopback `http`) `callback_url` and a PKCE challenge. |
| 400 | `invalid_client` — device-flow `client_id` isn't a registered app | Omit `client_id` (use `client_name` instead), or register first via `POST /api/v1/mcp/oauth/register`. |
| 400 | `invalid_scope` — none of the requested device-flow scopes are recognized | Request valid scopes, e.g. `inference read:credits`, or omit `scope` for the default set. |
| 401 | Invalid or expired bearer key | Send a valid `sk-ar-…` key. |
---
# OCR API (/api-reference/ocr)
Extract text from an image or document and return the content as Markdown. Powered by Cloudflare Workers AI's native document-to-markdown conversion.
::endpoint{method="POST" path="/api/v1/ocr/extract"}
Authenticate with an **LLM API key** (`sk-ar-v1-…`) from [/dashboard/keys](https://anyrouter.dev/dashboard/keys), sent as `Authorization: Bearer …`. Management keys (`ak_…`) are not accepted.
## Request [#request]
The endpoint accepts two content types: a JSON body with a base64 image, or a multipart file upload.
| Field | Type | Required | Description |
| ---------- | ------ | --------------- | ------------------------------------------------------------------------------------------------------- |
| `image` | string | yes (JSON only) | Base64-encoded image bytes, or a data URL (`data:;base64,`). |
| `filename` | string | no (JSON only) | Optional filename hint. Helps the extractor detect the file type. Defaults to `image.png` when not set. |
For **multipart/form-data**, include the file in a field named `image`. The filename is taken from the `Content-Disposition` header.
### Supported formats [#supported-formats]
The endpoint accepts any image or document format that Cloudflare Workers AI's `toMarkdown` conversion supports, including PNG, JPEG, GIF, WebP, BMP, TIFF, and PDF.
The maximum accepted file size is **5 MiB**. Requests that exceed this limit receive a `413` response.
## Response [#response]
```json
{
"text": "# Invoice #1042\n\n**Date:** 2026-06-15 \n**Amount:** $120.00\n\n## Line items\n\n| Description | Price |\n|---|---|\n| API credits | $120.00 |",
"format": "markdown",
"model": "cloudflare/ai-to-markdown",
"tokens": 68
}
```
| Field | Type | Description |
| -------- | ------ | --------------------------------------- |
| `text` | string | Extracted content in Markdown format. |
| `format` | string | Always `"markdown"`. |
| `model` | string | Always `"cloudflare/ai-to-markdown"`. |
| `tokens` | number | Token count consumed by the extraction. |
## Examples [#examples]
```bash
# JSON — base64 data URL
curl https://anyrouter.dev/api/v1/ocr/extract \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d "{\"image\": \"$(base64 -i receipt.png | tr -d '\n')\"}"
# Multipart — raw file upload
curl https://anyrouter.dev/api/v1/ocr/extract \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-F "image=@receipt.png"
```
```typescript
// JSON path — base64 encode the image first
async function extractText(imageBuffer: ArrayBuffer): Promise {
const base64 = btoa(String.fromCharCode(...new Uint8Array(imageBuffer)))
const response = await fetch("https://anyrouter.dev/api/v1/ocr/extract", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ANYROUTER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
image: `data:image/png;base64,${base64}`,
filename: "document.png",
}),
})
const result = await response.json()
return result.text
}
// Multipart path — stream the file directly
async function extractTextFromFile(filePath: string): Promise {
const { createReadStream } = await import("node:fs")
const form = new FormData()
form.append("image", new Blob([createReadStream(filePath)]), "document.png")
const response = await fetch("https://anyrouter.dev/api/v1/ocr/extract", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.ANYROUTER_API_KEY}` },
body: form,
})
const result = await response.json()
return result.text
}
```
## Errors [#errors]
| Status | Code | Description |
| ------ | ---------------------------- | --------------------------------------------------------------------------------------------- |
| 400 | `missing_required_fields` | The `image` field is absent or empty. |
| 400 | `invalid_request_body` | The request body is not valid JSON or is not parseable multipart. |
| 400 | `invalid_image_encoding` | The `image` value is not valid base64 or a valid data URL. |
| 400 | `invalid_image_field` | The multipart `image` field is a string, not a file. |
| 401 | `invalid_api_key` | The API key is missing, expired, or invalid. |
| 413 | `image_too_large` | The image exceeds the 5 MiB limit. |
| 500 | `missing_workers_ai_binding` | The Workers AI binding is not configured (server misconfiguration). |
| 502 | `ocr_upstream_failed` | The extraction call failed. Retry the request. |
| 502 | `ocr_extraction_error` | Workers AI returned an error for this file (for example: unsupported format or corrupt data). |
See [Errors](/guides/errors) for retry guidance.
## Related [#related]
* [Embeddings](/api-reference/embeddings) — vectors for search and RAG
* [Models](/api-reference/models) — list and inspect every supported model
---
# API Overview (/api-reference/overview)
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](https://anyrouter.dev/docs/api) — every endpoint with live request/response samples and a "try it" runner.
## Base URL [#base-url]
```text
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 [#choose-an-endpoint]
**Chat Completions** — OpenAI-compatible chat, streaming, tool calls, and broad cross-provider routing.
**Messages** — Anthropic-compatible clients such as Claude Code or the Anthropic SDK.
**Responses** — modern agent and tool workflows that need structured output items.
**Embeddings** — semantic search, clustering, and classification.
**Rerank** — sort documents by relevance to a query for search and RAG.
**Document Parse** — turn a PDF, Word file or image into Markdown with your own Nace key.
**Moderations** — classify text for harmful content.
**Decisions** — TypeSafe typed decisions (noul / choice / score), not chat.
**OCR** — extract text from images and documents as Markdown.
**Models** — discovery, context windows, capabilities, pricing, and provider routes.
**Health** — live `status`, `checked_at`, and per-component availability.
## Authentication: which key for which 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 family | Prefix | Use for | Get one at |
| ---------------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| **LLM API key** | `sk-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](https://anyrouter.dev/dashboard/keys) |
| **Management API key** | `ak_` | Account administration: `/keys` (LLM-key CRUD), `/presets`, `/auth/byok`, `/credits`, `/management-keys`. | [/dashboard/settings#keys](https://anyrouter.dev/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.
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](/api-reference/management-keys) for the full scope catalog and management lifecycle.
## Common headers [#common-headers]
| Header | Required | Description |
| ------------------------------------------------------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Authorization: Bearer ` | Yes | LLM key (`sk-ar-…`) on inference routes; management key (`ak_…`) on account routes. |
| `Content-Type: application/json` | POST | Required on POST endpoints. |
| `x-api-key` | Messages | Anthropic-compatible auth header for `/messages` (accepts `sk-ar-…`). |
| `anthropic-version` | Messages | Required by Anthropic-compatible Messages clients. |
| `X-AnyRouter-Provider` | No | Force routing to a specific upstream provider. |
| `X-AnyRouter-Balance-After-Debit: true` | No | Ask 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-Id` | Response | Canonical trace id on every inference response. Paste it into the dashboard logs "Request ID" filter to open the exact call. |
| `X-Request-ID` | Response | Legacy alias of `X-AnyRouter-Trace-Id` — same value, kept for back-compat. |
| `X-AnyRouter-Model` | Response | Public catalog model id that served the request (including the resolved model for a virtual preset). |
| `X-AnyRouter-Upstream-Model` | Response | Wire model name sent to the selected upstream. |
| `X-AnyRouter-Provider-Chain` | Response | Ordered backend ids tried, joined with `->`; contains route ids only, never credentials. |
| `X-AnyRouter-Credential-Source` | Response | `platform`, `own_byok`, or `pool` for the winning attempt. This is a class label, not key material. |
| `X-AnyRouter-Requested-Model` / `X-AnyRouter-Fallback-Reason` | Response, on model fallback | Sent 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 last chunk, next to `anyrouter_metadata` (and `usage`, when you set `stream_options.include_usage`). 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-Admission` | Response, when read | Workspace 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-Debit` | Response, non-streaming, on request | Workspace 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-Charged` | Response, non-streaming, on request | Credits 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-Balance` | `GET /credits` response | Current authoritative workspace balance returned by the credits read endpoint. |
| `X-RateLimit-*` / `RateLimit-*` | Response | The 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-Timing` | Response | Gateway-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 [#endpoint-summary]
| Method | Path | Description |
| ------ | ------------------- | -------------------------------------------------------------------------------------- |
| POST | `/chat/completions` | [Chat completions](/api-reference/chat-completions) (OpenAI-compatible) |
| POST | `/ocr/extract` | [OCR](/api-reference/ocr) — extract text as Markdown |
| POST | `/messages` | [Anthropic Messages](/api-reference/messages) passthrough |
| POST | `/responses` | [Responses](/api-reference/responses) for structured output and tool workflows |
| POST | `/embeddings` | [Embeddings](/api-reference/embeddings) for search and clustering |
| POST | `/rerank` | [Rerank](/api-reference/rerank) documents by relevance to a query |
| POST | `/documents/parse` | [Document parse](/api-reference/documents-parse) — file to Markdown, your own Nace key |
| POST | `/moderations` | [Moderations](/api-reference/moderations) — classify text for harmful content |
| POST | `/decisions` | [Decisions](/api-reference/systemone) TypeSafe decisions (not chat). Preferred path. |
| POST | `/systemone` | Same handler as `/decisions` |
| GET | `/models` | [List models](/api-reference/models) |
| GET | `/models/:id` | Model metadata |
| GET | `/models/count` | Total model count |
| GET | `/generation/:id` | Per-request stats (tokens, cost, latency) |
| GET | `/providers` | List upstream providers |
| GET | `/credits` | Remaining credit balance |
| GET | `/auth/key` | Current API key info |
| GET | `/health` | [Gateway + provider status](/api-reference/health) |
## Error shape [#error-shape]
All errors follow a consistent JSON envelope:
```json
{
"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:
| HTTP | `error.code` | Notes |
| ---- | ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| 400 | `bad_request` / `max_tokens_too_small` | Malformed 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`. |
| 401 | `unauthorized` | Missing or invalid API key. |
| 402 | `insufficient_balance` | Out of credits on a paid model, or a held signup bonus still pending (`signup_bonus_pending`). Top up at [Credits](https://anyrouter.dev/dashboard/credits). Upstream BYOK billing failures may also surface as `402` with `metadata.type: "upstream_error"` — read `metadata.upstream_message`. |
| 403 | `forbidden` | Key lacks permission for the resource. |
| 404 | `not_found` | Unknown model or missing resource. |
| 408 | `request_timeout` | Upstream did not respond in time. |
| 413 | `content_too_large` | Request body exceeds the configured limit. |
| 422 | `unprocessable_entity` | Validation failed semantically. |
| 429 | `rate_limit_exceeded` / `ip_rate_limit_exceeded` | Rate 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. |
| 500 | `internal_server_error` | Unexpected AnyRouter failure. |
| 502 | `bad_gateway` / `upstream_exhausted` | Upstream 5xx or every fallback failed; see `metadata.upstream_attempts`. |
| 503 | `service_unavailable` | Upstream 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](/guides/errors) for retry recipes and [`/docs/api`](https://anyrouter.dev/docs/api) for the full interactive reference.
## Rate limits [#rate-limits]
Rate limits are enforced per-key. Every response carries at least:
```text
X-RateLimit-Limit:
X-RateLimit-Window: 60
X-RateLimit-Tier:
RateLimit-Limit:
RateLimit-Policy: ;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](/features/rate-limits#response-headers).
## Tracing [#tracing]
`POST /chat/completions` and `POST /responses` also accept an optional request-body `trace` object:
```json
{
"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 [#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.
## Related [#related]
* [Migrate from OpenAI](/guides/migrate-from-openai) — swap base URL and key
* [Migrate from Anthropic](/guides/migrate-from-anthropic) — point Anthropic clients at Messages
* [Migrate to Responses](/guides/migrate-to-responses) — adopt the structured lifecycle
* [Errors](/guides/errors) — status codes and retry guidance
---
# Presets API (/api-reference/presets)
Presets are reusable configuration bundles that store a default model, system prompt, sampling parameters, and provider routing rules. Reference a preset in any inference request with the `@preset/` model syntax.
::endpoint{method="POST" path="/api/v1/presets"}
Authenticate with a **Management key** (`ak_…`) carrying `read:presets` (read routes) or `write:presets` (write routes), or a signed-in dashboard session. An **LLM key** (`sk-ar-…`) is not accepted. See [Management API Keys](/api-reference/management-keys).
## Request [#request]
`POST /api/v1/presets` creates a preset in the authenticated workspace:
| Field | Type | Required | Description |
| ------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------- |
| `slug` | string | Yes | Unique identifier. Lowercase alphanumeric and hyphens (`/^[a-z0-9][a-z0-9-]*$/`). Unique per workspace. |
| `name` | string | Yes | Display name. Must not be empty. |
| `description` | string \| null | No | Optional description. |
| `config` | object | Yes | Preset configuration. See [PresetConfig fields](#presetconfig-fields). |
### Other routes [#other-routes]
| Method & path | Purpose |
| ------------------------------- | ------------------------------------------------ |
| `GET /api/v1/presets` | List all presets in the workspace, newest first. |
| `GET /api/v1/presets/{slug}` | Return a single preset by slug (`404` if none). |
| `PATCH /api/v1/presets/{slug}` | Partial update — only included fields change. |
| `DELETE /api/v1/presets/{slug}` | Delete a preset by slug (`404` if none). |
`PATCH` update behavior: `name` is updated if provided and non-empty; `description` is tri-state (omit to keep, `null` to clear, string to set); `config` is shallow-merged so existing keys not in the patch are preserved.
### PresetConfig fields [#presetconfig-fields]
All fields are optional. When a preset is referenced in a request, these values serve as defaults the request can override.
| Field | Type | Description |
| ----------------------- | ------------------- | ----------------------------------------------------------------------------------- |
| `model` | string | Default model (e.g. `"z-ai/glm-4.7-flash"`) |
| `models` | string\[] | Fallback model list, tried in order if the primary is unavailable. |
| `system` | string | System prompt, prepended as a `{role: "system"}` message when the request has none. |
| `temperature` | number | Sampling temperature (0–2) |
| `max_tokens` | number | Maximum tokens in the response |
| `max_completion_tokens` | number | Maximum completion tokens (newer OpenAI parameter) |
| `top_p` | number | Nucleus sampling threshold |
| `top_k` | number | Top-K sampling |
| `frequency_penalty` | number | Frequency penalty (-2 to 2) |
| `presence_penalty` | number | Presence penalty (-2 to 2) |
| `repetition_penalty` | number | Repetition penalty |
| `stop` | string \| string\[] | Stop sequences |
| `seed` | number | Reproducibility seed |
| `response_format` | object | Response format (e.g. `{ "type": "json_object" }`) |
| `tools` | array | Tool definitions |
| `tool_choice` | string \| object | Tool selection strategy |
| `provider` | object | Provider routing rules (`sort`, `only`, `ignore`, `allow_fallbacks`, etc.) |
| `reasoning` | object | Reasoning config (`effort`, `max_tokens`, `exclude`) |
| `stream_options` | object | Streaming options (e.g. `{ "include_usage": true }`) |
### Reasoning on `anyrouter/*` routes [#reasoning-on-anyrouter-routes]
The built-in `anyrouter/auto`, `anyrouter/free`, `anyrouter/cowork`, `anyrouter/hermes`, and `anyrouter/latest` ids are routing presets, not fixed models. They do not add a synthetic reasoning level list. The rules below describe explicit request controls on these built-in routes; a custom preset's stored `reasoning` object is not a promise that every endpoint will merge it:
* Chat-style requests use flat `reasoning_effort`. Responses uses the nested `reasoning.effort` form, with `reasoning.enabled: false` as the endpoint-specific disable form. For flat Chat and converted Messages, `none` is a disable sentinel, not an effort tier.
* Messages requests use `thinking.type` (`enabled` / `disabled`); native Anthropic routes receive the body unchanged. When Messages is converted to a non-native upstream, `enabled` maps to flat `high` and `disabled` maps to `none`; `thinking.budget_tokens` is not carried into that flat field.
* For flat `reasoning_effort` paths (including the converted Messages form), before each upstream attempt AnyRouter clamps the requested effort against the selected concrete model's declared levels, using a route-specific level override when one exists. It prefers the nearest lower supported level and falls back to the model default for an unknown tier. An omitted effort stays omitted; the declared `default` is a clamp fallback, not automatic injection. A branch's levels never leak into the next fallback branch. `none` remains a disable sentinel and is never clamped on these flat paths.
* If a resolved branch has no declared effort enum, AnyRouter does not invent one and forwards the caller's flat value unchanged. For a selected route, its per-upstream `supported_parameters` is the authority; the top-level model list is a model-level/legacy projection, not a promise shared by every fallback.
* Preset-level `require_params` is forwarded as a hard candidate constraint on paths that support preset candidate selection. Preset, key, and request provider lists are unioned at the preference layer, but provider/key requirements are not a universal hard filter on every concrete dispatch. Built-in virtual resolution uses tool-related entries in the list (and request tools) to require tool-capable members; it does not turn every arbitrary parameter into a capability guarantee.
* `provider.require_parameters: true` is a separate strict model-level check over the request body. It also has no effect when the model has no declared `supported_parameters` list. Ordinary body-inferred parameters such as `tools`, `tool_choice`, and `response_format` use the softer candidate-reorder/strip path. If a route has no declared support list, the current router treats support as unknown/permissive rather than claiming universal support.
* Routing floors merge deliberately: `min_context` takes the stricter (higher) value, key `ignore` is unioned, and key/request `only` lists intersect. The top-level preset `require_params` is the legacy alias for `provider.require_params`; `min_context` is likewise accepted at the top level.
* A normal fallback chain may still try a different resolved model after an upstream failure. Reasoning clamping is recalculated for that attempt; it is not a promise that every model in the chain has identical effort levels.
These rules describe the current router. They do not claim that every provider supports a toggle, a reasoning token budget, or interleaved thinking. Interleaved thinking is a provider beta request (`anthropic-beta: interleaved-thinking-2025-05-14`), not a preset/model guarantee; use the model response's declared fields and the endpoint-specific parameters instead.
## Response [#response]
`GET /api/v1/presets` returns a list envelope; a single preset record looks like:
```json
{
"id": "preset_abc123",
"object": "preset",
"slug": "code-reviewer",
"name": "Code Reviewer",
"description": "Strict senior-engineer review focused on bugs, security, and clarity.",
"config": {
"model": "anthropic/claude-sonnet-4",
"system": "You are a senior code reviewer...",
"temperature": 0.2,
"max_tokens": 4096
},
"created_at": "2026-03-01T12:00:00.000Z",
"updated_at": "2026-03-01T12:00:00.000Z"
}
```
`DELETE /api/v1/presets/{slug}` returns:
```json
{
"deleted": true,
"slug": "code-reviewer"
}
```
## Examples [#examples]
```bash
curl -X POST https://anyrouter.dev/api/v1/presets \
-H "Authorization: Bearer ak_your-management-key" \
-H "Content-Type: application/json" \
-d '{
"slug": "code-reviewer",
"name": "Code Reviewer",
"description": "Strict senior-engineer review focused on bugs, security, and clarity.",
"config": {
"model": "anthropic/claude-sonnet-4",
"system": "You are a senior code reviewer. Focus on bugs, security issues, and clarity.",
"temperature": 0.2,
"max_tokens": 4096
}
}'
```
```python
import httpx
httpx.post(
"https://anyrouter.dev/api/v1/presets",
headers={"Authorization": "Bearer ak_your-management-key"},
json={
"slug": "code-reviewer",
"name": "Code Reviewer",
"config": {
"model": "anthropic/claude-sonnet-4",
"system": "You are a senior code reviewer.",
"temperature": 0.2,
"max_tokens": 4096,
},
},
)
```
```typescript
await fetch("https://anyrouter.dev/api/v1/presets", {
method: "POST",
headers: {
Authorization: "Bearer ak_your-management-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
slug: "code-reviewer",
name: "Code Reviewer",
config: {
model: "anthropic/claude-sonnet-4",
system: "You are a senior code reviewer.",
temperature: 0.2,
max_tokens: 4096,
},
}),
})
```
Once created, use it in any inference request by setting `model` to `@preset/code-reviewer`.
## Errors [#errors]
| Status | Code | Meaning |
| ------ | ----------------- | -------------------------------------------------------------- |
| 400 | `invalid_slug` | Slug doesn't match the required pattern. |
| 400 | `name_required` | Name is empty or missing. |
| 400 | `config_required` | Config is missing or not an object. |
| 401 | — | Missing or invalid credential. |
| 403 | — | Key lacks the required `read:presets` / `write:presets` scope. |
| 409 | `duplicate_slug` | A preset with this slug already exists in the workspace. |
## Related [#related]
* [Management API Keys](/api-reference/management-keys) — the `ak_…` keys and scopes used here
* [Presets](/features/presets) — the feature overview and dashboard flow
* [Provider Routing](/guides/provider-routing) — the `provider` config block explained
---
# Providers API (/api-reference/providers)
Return the catalog of upstream backends AnyRouter routes through — Cloudflare AI Gateway, partner-hosted Workers AI models, BYOK provider routes, DeepInfra, and other direct providers. Each entry carries a logo and short description suitable for rendering in a UI.
::endpoint{method="GET" path="/api/v1/providers"}
This endpoint is **public** — no authentication is required.
The list is provider-organization-grained — separate from the *model* catalog at [`/api/v1/models`](/api-reference/models), which lists individual model ids.
## Request [#request]
`GET /api/v1/providers` takes no parameters and requires no auth headers. The full list is always returned; to find which providers serve a *specific* model, fetch `/api/v1/models/:id` and read its `upstreams[]` array.
## Response [#response]
```json
{
"object": "list",
"data": [
{
"id": "anthropic",
"name": "Anthropic",
"description": "Anthropic's Claude model family.",
"logo": "/providers/anthropic.svg"
},
{
"id": "openai",
"name": "OpenAI",
"description": "OpenAI-hosted GPT and embedding models.",
"logo": "/providers/openai.svg"
},
{
"id": "cloudflare",
"name": "Cloudflare AI Gateway",
"description": "Cloudflare's Workers AI catalog (first-party + partner-hosted).",
"logo": "/providers/cloudflare.svg"
}
]
}
```
| Field | Type | Description |
| ------------- | ------ | ------------------------------------------------------------------------------------ |
| `id` | string | Stable backend identifier. Matches the `provider` field on `/api/v1/models` entries. |
| `name` | string | Display name. |
| `description` | string | Short summary (≤ 200 chars), safe to render in tooltips. |
| `logo` | string | Absolute or root-relative path to an SVG asset. |
Provider availability is **not** returned here — that lives on [`/api/v1/health`](/api-reference/health) so the static catalog can be cached aggressively. Combine the two for a "providers + status" view.
## Examples [#examples]
```bash
curl https://anyrouter.dev/api/v1/providers
```
```python
import httpx
providers = httpx.get("https://anyrouter.dev/api/v1/providers").json()["data"]
for p in providers:
print(p["id"], "-", p["name"])
```
```typescript
const { data } = await (
await fetch("https://anyrouter.dev/api/v1/providers")
).json()
data.forEach((p) => console.log(p.id, "-", p.name))
```
## Errors [#errors]
| Status | Code | Meaning |
| ------ | ----------------------- | ------------------------------------------------------------ |
| 429 | `too_many_requests` | Rate limited. |
| 500 | `internal_server_error` | Unexpected AnyRouter failure. |
| 502 | `bad_gateway` | Catalog generation failed (rare; falls back to cached copy). |
| 503 | `service_unavailable` | Service is down. |
---
# Rerank API (/api-reference/rerank)
Score how relevant each document is to a query, then sort them. Use it as a second stage after vector or keyword search: fetch 50–100 candidates, rerank, and keep the top few. The body is compatible with Cohere's Rerank API.
::endpoint{method="POST" path="/api/v1/rerank"}
Authenticate with an **LLM API key** (`sk-ar-v1-…`) from [/dashboard/keys](https://anyrouter.dev/dashboard/keys), sent as `Authorization: Bearer …`. Management keys (`ak_…`) are not accepted.
## Request [#request]
| Field | Type | Required | Description |
| -------------------- | ------------------------------- | -------- | --------------------------------------------------------------------------- |
| `model` | string | yes | A rerank model id, e.g. `cohere/rerank-v3.5`. See [Models](#models). |
| `query` | string | yes | The search query. |
| `documents` | (string \| `{text: string}`)\[] | yes | Up to 1,000 documents to score. |
| `top_n` | integer (≥ 1) | no | Return only the best `top_n` results. Defaults to all documents. |
| `return_documents` | boolean | no | When `true`, each result includes `document.text`. Defaults to `false`. |
| `max_tokens_per_doc` | integer (≥ 1) | no | Truncate each document to this many tokens before scoring. |
| `session_id` | string | no | Groups related requests in [Request Logs](/features/request-logs#sessions). |
```bash
curl https://anyrouter.dev/api/v1/rerank \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "cohere/rerank-v3.5",
"query": "What is the capital of France?",
"documents": [
"Paris is the capital and largest city of France.",
"Berlin is the capital of Germany.",
"Bananas are rich in potassium."
],
"top_n": 2
}'
```
## Response [#response]
```json
{
"id": "162b4aa9-d997-4942-896a-ef65644ff544",
"model": "cohere/rerank-v3.5",
"results": [
{ "index": 0, "relevance_score": 0.892 },
{ "index": 1, "relevance_score": 0.073 }
],
"usage": {
"prompt_tokens": 1000,
"total_tokens": 1000,
"search_units": 1,
"cost": 0.002
}
}
```
| Field | Type | Description |
| --------------------------- | -------- | -------------------------------------------------------------------------------- |
| `id` | string | Response id. |
| `model` | string | The model id you requested. |
| `results[]` | array | Sorted by `relevance_score`, highest first. |
| `results[].index` | integer | Position of the document in your `documents` array. |
| `results[].relevance_score` | number | Relevance between 0 and 1. Compare scores within one request, not across models. |
| `results[].document` | `{text}` | Present only when `return_documents` is `true`. |
| `usage.prompt_tokens` | integer | Billed tokens (see Pricing). |
| `usage.search_units` | integer | Provider search units, when the provider reports them. |
| `usage.cost` | number | USD charged for this request. |
## Pricing [#pricing]
Rerank models are priced per 1M input tokens, like the rest of the catalog. AnyRouter counts tokens over the query plus every document (about 4 characters per token), because rerank providers do not return token counts. Each provider search unit (one query with up to 100 documents) bills at least 1,000 tokens, so a very small request costs the same as one search.
Requests with your own Cohere key ([BYOK](/features/byok)) are not charged by AnyRouter.
## Models [#models]
Rerank models appear under the **Rerank** tab at [anyrouter.dev/models](https://anyrouter.dev/models). Sending a chat or embedding model to this endpoint returns `400 model_not_rerank_compatible`.
## Errors [#errors]
| Status | Code | When |
| ------ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| 400 | `missing_required_fields` | `model`, `query`, or `documents` is missing. |
| 400 | `invalid_request` | A document is not a string or `{text}`, `top_n` is not a positive integer, or there are more than 1,000 documents. |
| 400 | `model_not_rerank_compatible` | The model is not a rerank model. |
| 404 | `model_unavailable` | No route serves the model for your key. |
| 502 | `upstream_error` | Every provider for the model failed. |
---
# Responses API (/api-reference/responses)
`/api/v1/responses` implements the OpenAI Responses API lifecycle. Unlike Chat Completions, the response is a self-contained envelope with typed output items — text blocks, tool calls, and reasoning summaries — rather than a flat `choices` array.
::endpoint{method="POST" path="/api/v1/responses"}
::endpoint{method="GET" path="/api/v1/responses/{response_id}"}
::endpoint{method="DELETE" path="/api/v1/responses/{response_id}"}
::endpoint{method="GET" path="/api/v1/responses/{response_id}/input_items"}
::endpoint{method="POST" path="/api/v1/responses/input_tokens"}
::endpoint{method="POST" path="/api/v1/responses/{response_id}/cancel"}
::endpoint{method="POST" path="/api/v1/responses/compact"}
Authenticate with an **LLM API key** (`sk-ar-v1-…`) from [/dashboard/keys](https://anyrouter.dev/dashboard/keys). Management keys (`ak_…`) are not accepted on inference routes.
## Request [#request]
| Field | Type | Required | Description |
| ---------------------- | ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `model` | string | yes | `provider/model` id. See [List models](/api-reference/models). |
| `input` | string \| array | yes | User input. String or array of input items (text, image, etc.). |
| `instructions` | string | no | System-level instruction passed before the input. |
| `max_output_tokens` | integer | no | Token budget for the response. |
| `temperature` | number | no | 0–2. Sampling temperature. |
| `top_p` | number | no | 0–1. Nucleus sampling. |
| `stream` | boolean | no | If `true`, returns an SSE stream of Responses events. |
| `store` | boolean | no | Defaults to `true`. Stored responses can be retrieved, deleted, continued with `previous_response_id`, and inspected with `/input_items`. |
| `previous_response_id` | string | no | Continue from a stored response owned by the same workspace. |
| `background` | boolean | no | If `true`, creates a queued stored response that can be retrieved or cancelled. |
| `tools` | array | no | Function definitions the model can call. Each tool has `type`, `name`, `description`, `parameters`. |
| `tool_choice` | string \| object | no | `"auto"`, `"none"`, `"required"`, or `{"type":"function","name":"…"}`. |
| `reasoning` | object | no | `{"effort": "low"\|"medium"\|"high", "summary": "auto"\|"detailed"}`. |
| `modalities` | string\[] | no | Output modalities requested (e.g. `["text"]`). |
| `metadata` | object | no | Arbitrary key-value pairs attached to the response. |
| `session_id` | string | no | Sticky-session id (≤256 chars) that groups related requests in [Request Logs](/features/request-logs#sessions). Also settable via the `x-session-id` header; the body field wins. |
| `provider` | object | no | Provider routing preferences. Supports `only`, `ignore`, `order`, `sort`, `allow_fallbacks`, `max_price`, `preferred_max_latency`, and `preferred_min_throughput`. |
| `trace` | object | no | Optional tracing metadata. Supports `trace_id`, `trace_name`, `span_name`, `generation_name`, and `parent_span_id`. |
For the full field list see [`/docs/api`](https://anyrouter.dev/docs/api).
### Stored response lifecycle [#stored-response-lifecycle]
Responses are stored by default for later retrieval. Pass `store: false` when you do not need lifecycle operations. Stored responses are scoped to the API key workspace; another workspace cannot retrieve, delete, continue, or cancel them.
```bash
# Retrieve a stored response
curl https://anyrouter.dev/api/v1/responses/resp_abc123 \
-H "Authorization: Bearer sk-ar-your-key"
# List its input items
curl https://anyrouter.dev/api/v1/responses/resp_abc123/input_items \
-H "Authorization: Bearer sk-ar-your-key"
```
## Response [#response]
```json
{
"id": "resp_…",
"object": "response",
"created_at": 1714000000,
"completed_at": 1714000001,
"model": "z-ai/glm-4.7-flash",
"status": "completed",
"output": [ /* typed output items */ ],
"output_text": "Paris.",
"usage": {
"input_tokens": 14,
"output_tokens": 3,
"total_tokens": 17
},
"incomplete_details": null,
"error": null
}
```
`status` is one of `"in_progress"`, `"completed"`, `"incomplete"`, or `"failed"`. When `status` is `"incomplete"`, `incomplete_details.reason` is `"max_output_tokens"` or `"content_filter"`.
### Output item types [#output-item-types]
| Type | Description |
| --------------- | ----------------------------------------------------------------------------------- |
| `message` | Assistant turn with a `content` array of `output_text` blocks. |
| `function_call` | Tool invocation with `call_id`, `name`, `arguments`. |
| `reasoning` | Reasoning summary (provider-dependent; present when reasoning tokens are surfaced). |
Responses-native upstreams can also pass through server tool items such as `web_search_call`, `file_search_call`, `code_interpreter_call`, and `computer_call`. Chat Completions and Anthropic Messages upstreams currently normalize only text, reasoning, and function-call output items.
### Streaming events [#streaming-events]
Set `stream: true` to receive server-sent events. The event sequence is:
```
event: response.created
data: {"type":"response.created","response":{...}}
event: response.in_progress
data: {"type":"response.in_progress","response":{...}}
event: response.output_item.added
data: {"type":"response.output_item.added","output_index":0,"item":{...}}
event: response.output_text.delta
data: {"type":"response.output_text.delta","item_id":"msg_xyz","output_index":0,"content_index":0,"delta":"Par"}
event: response.output_text.done
data: {"type":"response.output_text.done","item_id":"msg_xyz","output_index":0,"content_index":0,"text":"Paris."}
event: response.output_item.done
data: {"type":"response.output_item.done","output_index":0,"item":{...}}
event: response.completed
data: {"type":"response.completed","response":{...}}
event: response.anyrouter.metadata
data: {"type":"response.anyrouter.metadata","anyrouter_metadata":{...}}
```
The `response.anyrouter.metadata` frame is emitted after `response.completed` and carries billing and upstream info. Standard Responses API clients safely ignore unknown event types. When the request sent `X-AnyRouter-Balance-After-Debit: true` and this request's charge applied, the frame also carries `anyrouter_metadata.creditsCharged` and `anyrouter_metadata.balanceAfterDebit` — a stream's headers are already gone by then, so this final frame is where the charge and the post-charge balance appear.
## Examples [#examples]
```bash
curl https://anyrouter.dev/api/v1/responses \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "z-ai/glm-4.7-flash",
"input": "What is the capital of France?"
}'
```
```bash
curl https://anyrouter.dev/api/v1/responses \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "z-ai/glm-4.7-flash",
"input": "What is the weather in Paris?",
"tools": [
{
"type": "function",
"name": "get_weather",
"description": "Get current weather for a location",
"parameters": {
"type": "object",
"properties": { "location": { "type": "string" } },
"required": ["location"]
}
}
],
"tool_choice": "auto"
}'
```
```bash
curl https://anyrouter.dev/api/v1/responses \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/o1-mini",
"input": "Solve: x^2 - 5x + 6 = 0",
"reasoning": { "effort": "medium", "summary": "auto" }
}'
```
```bash
curl https://anyrouter.dev/api/v1/responses \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-mini",
"input": "Summarize the deployment notes",
"provider": {
"only": ["OpenAI", "Groq"],
"sort": "latency",
"allow_fallbacks": true,
"preferred_max_latency": 250
},
"trace": {
"trace_id": "4f8c2b9670b44b49a2e71e7fd5c0b1d3",
"trace_name": "release-pipeline"
}
}'
```
The minimal request above returns a stored response envelope:
```json
{
"id": "resp_abc123",
"object": "response",
"status": "completed",
"model": "z-ai/glm-4.7-flash",
"output": [
{
"type": "message",
"id": "msg_xyz",
"role": "assistant",
"content": [{ "type": "output_text", "text": "Paris." }],
"status": "completed"
}
],
"usage": {
"input_tokens": 14,
"output_tokens": 3,
"total_tokens": 17,
"cost": 0.0000021,
"is_byok": false
},
"anyrouter_metadata": {
"requestId": "resp_abc123",
"model": "z-ai/glm-4.7-flash",
"upstream": { "provider": "openai_direct", "backend": "openai_direct", "latencyMs": 412 }
}
}
```
`usage.cost` is the USD charged to your credits for the request, `0` for free models and [BYOK](/features/byok). It is the same amount on non-streaming responses and on the terminal `response.completed` (or `response.incomplete`) event of a stream, and it matches your usage history. See [Cost of a request](/api-reference/chat-completions#cost-of-a-request).
When you send a `reasoning` request, the `output` array contains a `reasoning` item before the `message` item, provided the model exposes reasoning tokens.
### Provider routing behavior [#provider-routing-behavior]
The `provider` object is applied before AnyRouter dispatches upstream requests:
* `only` and `ignore` shape the candidate set.
* `order` pins the preferred backend order.
* `sort` reorders candidates by `price`, `latency`, or `throughput`.
* `max_price.prompt` and `max_price.completion` remove candidates above your budget.
* `preferred_max_latency` and `preferred_min_throughput` apply performance cutoffs.
* `allow_fallbacks: false` means AnyRouter tries only the top candidate.
See [Smart Routing](/features/routing) for the full behavior.
## Errors [#errors]
All errors follow the standard AnyRouter JSON envelope with a stable `error.code`.
| Status | Meaning | Fix |
| ------ | ------------------------------------------- | --------------------------------------------------------------------------- |
| 400 | Malformed body or invalid parameters. | Check the request against the field table. |
| 401 | Missing or invalid API key. | Send a valid `sk-ar-…` key as `Authorization: Bearer`. |
| 402 | Out of credits or upstream billing failure. | Top up credits, or read `metadata.upstream_message`. |
| 404 | Unknown model or stored response. | Verify the model id, or confirm the response id is owned by your workspace. |
| 429 | Rate limit hit. | Back off; inspect `X-RateLimit-*` response headers. |
See [Errors](/guides/errors) for the full status table and retry recipes.
---
# Decisions API (/api-reference/systemone)
Evaluate application **state** against typed **questions** and get structured **answers** (probabilities, not generated chat text). This is TypeSafe's System One contract — **not** OpenAI Chat Completions.
::endpoint{method="POST" path="/api/v1/decisions"}
`POST /api/v1/systemone` is the same handler, auth, and body. Prefer `/api/v1/decisions`.
Authenticate with an **LLM API key** (`sk-ar-v1-…`) from [/dashboard/keys](https://anyrouter.dev/dashboard/keys), sent as `Authorization: Bearer …`.
## Free on every plan: `anyrouter/decision` [#free-on-every-plan-anyrouterdecision]
Send `"model": "anyrouter/decision"` and AnyRouter runs your request on its own keys. Decisions through `anyrouter/decision` are free on every plan, Free included:
* **No credits.** Every request is charged `$0`, and your balance can be zero.
* **No BYOK key.** AnyRouter pays the upstream provider.
* **No plan quota.** There is no daily or monthly cap. A per-key fair-use burst cap of **60 requests per minute** stops runaway loops; see [Rate Limits](/features/rate-limits#decisions-free-no-plan-quota-fair-use-burst-cap).
`anyrouter/decision` picks a healthy decision model from the set AnyRouter serves free and falls back to the next one if a model fails. The response's `model` field names the model that answered. See the current list at [/model/anyrouter/decision](https://anyrouter.dev/model/anyrouter/decision).
To pin one model instead, send its catalog id, for example `typesafe/jev`. A pinned model runs on its `$0` platform route when one exists, or on a BYOK key you saved under [Dashboard → BYOK](https://anyrouter.dev/dashboard/byok) for a provider that serves it: [TypeSafe](https://console.typesafe.ai/settings/keys), [LLM Gateway](https://llmgateway.io), [Vercel AI Gateway](https://vercel.com/docs/ai-gateway/authentication-and-byok/api-keys), [AIHubMix](https://aihubmix.com/token), or [AutoJev](https://autojev.ai/settings/apikeys).
Do not send decision requests to `/chat/completions`. Jev is a decision model (`capability: systemone`) and is rejected on chat-shaped endpoints.
## Request [#request]
| Field | Type | Required | Description |
| ----------- | ------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `state` | string \| object \| array | yes | Content to evaluate: plain text or structured application state. |
| `model` | string | yes | `anyrouter/decision` (free, routed), or a catalog id such as `typesafe/jev`. Aliases: `typesafe/jev-latest`, `typesafe/jev-preview`, `typesafe/jev-1.13.0`. |
| `questions` | object | yes | Non-empty map of question id → `{ type, instructions, criteria? }`; do not send an array. Types: `noul`, `choice`, `score`. |
### Question types [#question-types]
| `type` | Meaning | Answer |
| -------- | ------------------------------------------------------------------------------ | ------------------------------------------------ |
| `noul` | Yes/no; optional `criteria` is an object with `true` and `false` descriptions | `noul` probability in `[0, 1]` |
| `choice` | One option; `criteria` must be an object map of option → description or `null` | `choice`, `probabilities`, `confidence` |
| `score` | Ordered `criteria` array with 2–10 level descriptions | `score`, `legend`, `probabilities`, `confidence` |
## Response [#response]
```json
{
"model": "jev-1.13.0",
"answers": {
"is_urgent": { "type": "noul", "noul": 0.92 }
},
"usage": { "input_tokens": 312, "output_tokens": 48, "cost": 0 }
}
```
`usage.cost` is `0` for `anyrouter/decision`. On a BYOK route your provider bills you directly (TypeSafe bills input tokens only), and AnyRouter charges no credits.
## Example [#example]
```bash
curl -sS https://anyrouter.dev/api/v1/decisions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": "Help! My payouts have been failing for 3 days.",
"model": "anyrouter/decision",
"questions": {
"is_urgent": {
"type": "noul",
"instructions": "Does this convey urgency?"
}
}
}'
```
See TypeSafe's [API reference](https://docs.typesafe.ai/api) and [models](https://docs.typesafe.ai/models) for primitives, confidence, and rate limits.
---
# Accounts and keys in the CLI (/cli/accounts-and-keys)
The CLI supports any number of **accounts** (work, personal, a self-hosted gateway…), each with its own key, base URL, and defaults. It can also manage your **API keys** — list, create, switch, revoke — straight from the terminal.
## Multiple accounts [#multiple-accounts]
```bash
anyr account list # * marks the active account
anyr account add work # sign in to a second account
anyr account use work # switch
anyr account rename work team # rename
anyr account remove team # remove (asks first; --yes to skip)
```
Accounts live in `~/.anyrouter/config.yaml`. Three ways to pick one, strongest first:
1. `--profile ` on any command — one-off.
2. `ANYROUTER_PROFILE=` in the environment — per shell or per CI job.
3. `anyr account use ` — the persistent default.
The active account cannot be removed — switch to another first. Removing an account also deletes its stored keys from the OS keychain.
## Who am I? [#who-am-i]
```bash
anyr whoami # or: anyr status
anyr whoami --json
```
Shows your name and email, the active account, a masked key hint, the default model and the config path. Full secrets are never printed.
## Signing out [#signing-out]
```bash
anyr logout # active account
anyr logout --profile work # a specific account
```
Removes the account's API key from the keychain and `config.yaml`. The account itself keeps its settings, so `anyr auth login` restores it in one step.
## Managing API keys [#managing-api-keys]
```bash
anyr keys list # * marks the key this account uses
anyr keys create laptop # create a named key (default: your hostname)
anyr keys use # pick a key interactively and switch to it
anyr keys show # limit, spent, endpoints, expiry
anyr keys update --limit 50 --disable
anyr keys revoke # revoke (asks first; --yes in scripts)
anyr keys # open the dashboard keys page instead
```
These subcommands authenticate with the signed-in API key. Notes:
* CLI login mints (or reuses) a key with Key Management permission by default, so `anyr keys` works immediately after `anyr auth login`.
* `keys create` prints the new key **once** — the API never returns it again. In a terminal the CLI offers to store it for the current account immediately.
* `keys use` switches which key the account uses; keys created before reveal support cannot be fetched again — create a fresh one instead.
* `keys revoke` warns when you are revoking the key the account itself is using.
* If your key was created in the dashboard with Key Management off, enable that permission or run `anyr auth login` again.
## Scoped keys for CI [#scoped-keys-for-ci]
```bash
anyr keys create ci-bot --limit 25 --reset monthly --endpoints chat,embeddings --expires 30d
KEY=$(anyr keys create ci-bot --limit 5 --quiet) # prints only the secret; not saved
anyr keys update --limit 50 --disable # --enable turns it back on
```
* `--limit` is a spend cap in USD; `--reset` sets its window (`daily`, `weekly` or `monthly`).
* `--expires` takes `30d`, `12h`, `45m` or `2w`.
* `--endpoints` restricts the key to the named endpoints: `chat`, `embeddings`, `moderations`, `rerank`, `documents`, `speech`, `decisions`, `mcp`, `me`, `relay`, `models`, `keys`, `credits`, `logs`, `dashboard`, `trace`. Chat, messages and responses share one permission, so `chat` covers all three.
* `--quiet` prints the secret on stdout and nothing else, so it is safe to capture in a script.
## Auditing what the CLI does [#auditing-what-the-cli-does]
```bash
anyr audit # one full report
anyr audit --tool codex # env-injection preview for a specific tool
anyr audit --launches # just the launch history
anyr audit --json # machine-readable
```
`audit` answers "what is configured, and what has this machine done" in one place:
* **Effective config** — config file path, the active profile and *why* it is active (`--profile` flag, `ANYROUTER_PROFILE`, or the saved default), and the base URL.
* **Key storage** — where each key family lives (OS keychain or `config.yaml`). Never the keys themselves.
* **Env injection** — the exact environment variables a launch would set for the tool, with secrets redacted identically to `--dry-run`.
* **Launch history** — a local log (`~/.anyrouter/audit.log`) with one line per launch: time, tool, model, profile, exit code. It never contains key material or prompt content, and it stays on your machine.
## Scripting [#scripting]
`account list` output is stable line-per-account text; `whoami --json` and `keys list --json` emit JSON (masked values and presence flags only — never secrets).
```bash
ANYROUTER_PROFILE=ci anyr whoami --json | jq -r .base_url
anyr keys list --json | jq -r '.[] | select(.current) | .name'
```
---
# Use anyr from scripts and AI agents (/cli/agents)
Every `anyr` command can be run without a terminal. Add `--json` to get output a program can parse. Errors come back as JSON with a fixed exit code, so a script or a coding agent can branch on the result without reading prose.
```bash
anyr help agents # this guide, in the terminal
```
## Discover the commands [#discover-the-commands]
```bash
anyr --help # the everyday commands
anyr help --all # every command, grouped
anyr commands --json # the full command tree as JSON
```
`anyr commands --json` lists each command with its subcommands, flags, aliases, its group, and whether it has JSON output:
```json
{
"schema": "anyr.commands/v1",
"commands": [
{
"name": "chat",
"summary": "Ask a model: prompt, stdin or a REPL; --json for scripts",
"group": "chat",
"aliases": [],
"subcommands": [],
"flags": ["base-url", "config", "key", "model", "no-stream", "profile", "system"],
"json": true
}
],
"exit_codes": [{ "code": 0, "name": "ok", "meaning": "success" }]
}
```
New fields may be added; existing fields keep their meaning while `schema` stays `anyr.commands/v1`.
## JSON output [#json-output]
| Setting | Effect |
| --------------------------- | --------------------------------------------------------------- |
| `--json` | JSON on stdout for any command that has a JSON form |
| `ANYR_OUTPUT=json` | The same as `--json` on every command |
| `--quiet` or `ANYR_QUIET=1` | No update notices and no background update check |
| `NO_COLOR=1` | No colour. Colour is also off whenever stdout is not a terminal |
Without a terminal, `anyr` shows no prompts, spinners or update notices. A command that would ask before acting, such as revoking a key, fails with a usage error until you pass `--yes`.
## Errors and exit codes [#errors-and-exit-codes]
With `--json`, an error prints as one JSON object on stderr, and stdout stays empty:
```json
{"error":{"code":"auth","exit":4,"message":"HTTP 401: invalid key","hint":"sign in again: anyr login"}}
```
| Exit | `code` | Meaning |
| ---- | -------------- | --------------------------------------------------------- |
| 0 | `ok` | Success |
| 1 | `error` | The request or command failed |
| 2 | `usage` | Bad flag, argument or subcommand. Nothing was sent |
| 3 | `not_found` | The model, key, id or resource does not exist |
| 4 | `auth` | No key, a rejected key, or a key without the needed scope |
| 5 | `rate_limited` | Too many requests. Retry later |
## Ask a model [#ask-a-model]
```bash
anyr chat "Reply with ok" --json
```
```json
{"id":"req_…","model":"z-ai/glm-4.7-flash","content":"ok","finish_reason":"stop","usage":{"prompt_tokens":12,"completion_tokens":1}}
```
* `--json` waits for the whole answer and prints one object. `id` is the id `anyr generation ` takes, so you can look up cost and latency afterwards.
* `--no-stream` waits for the whole answer and prints only the text.
* Piped input becomes the prompt. A lone `-` places it after your own words:
```bash
echo "Summarise the AnyRouter docs in one line" | anyr chat --no-stream
git diff | anyr chat "Review this diff" - --json
```
## Authenticate without a login [#authenticate-without-a-login]
Set a key in the environment; no saved login is needed:
```bash
export ANYROUTER_API_KEY=sk-ar-v1-… # inference key
export ANYROUTER_MANAGEMENT_KEY=ak_… # presets, provider keys, device removal
anyr whoami --json
```
## Commands with JSON output [#commands-with-json-output]
```bash
anyr models --json --free
anyr usage --json
anyr logs --json --status error
anyr keys list --json
anyr embed "text" --json
anyr artifact publish page.html --json
anyr api credits # any dashboard resource; prints JSON when piped
```
Launchers such as `anyr claude` start an interactive agent and have no JSON form, but their errors still follow the JSON and exit-code rules above. Use `--dry-run` to see the command and environment a launch would use.
## Related [#related]
* [Usage, logs & transactions](/cli/usage)
* [Configuration](/cli/configuration)
* [Delegate tasks between coding agents](/cli/delegate)
---
# CLI Changelog (/cli/changelog)
# AnyRouter CLI — Changelog [#anyrouter-cli--changelog]
Release history for the native `anyr` binary. **Install today:** `curl -fsSL https://anyrouter.dev/setup.sh | bash`, then run `anyr` (alias `ar`). See the [CLI guide](/cli) to get started.
## 0.1.33 (2026-10-24) [#0133-2026-10-24]
anyr now updates itself in the background without making any command wait, and you can roll back with one command. Pi understands the portable --skills-off and --tools-off flags, and launches in scripts no longer log in, install or prompt unless you ask.
* Updates are staged in the background at most once a day and applied on the next run; anyr update --status shows what happened and anyr update --rollback restores the previous build
* Auto-update stays off in CI, without a terminal, with --json, inside launched agents, and with ANYR\_NO\_AUTO\_UPDATE=1
* anyr pi accepts --skills-off and --tools-off and passes them as Pi's --no-skills and --no-tools; anyr claude maps --skills-off to --disable-slash-commands, and agents with no such switch skip the flag with a note
* \--json, ANYR\_OUTPUT=json and --quiet never start a login, even on a terminal; they exit 4 with the key hint
* A missing agent is installed only on an explicit yes: y/N on a terminal, --install to skip the question, and a scripted run exits 3 with the install command
* anyr pi fetches the model catalog once per launch
## 0.1.32 (2026-10-24) [#0132-2026-10-24]
anyr whoami now shows your name and email, and anyr chat prints a footer with the model, tokens, timing and cost under each reply. The device relay detects dead connections and reconnects with backoff, and it follows the base URL of your profile.
* anyr whoami shows name, email, account, masked key, default model and config path; a default-model picker is included
* anyr chat prints a footer under the latest reply: model, tokens, timing and cost
* anyr claude and the other launchers pass unknown flags through to the agent; --yolo maps to each agent's own flag
* anyr relay detects half-open sockets, backs off with jitter and bounds memory; it dials the websocket and pool API from your profile base URL
## 0.1.31 (2026-10-24) [#0131-2026-10-24]
Every anyr command can now answer in JSON with clear exit codes, so scripts and coding agents can drive it. Launching an agent can connect the AnyRouter MCP gateway for you, the help is shorter, keys never show in error messages, and the download is about a fifth smaller.
* \--json on every command, errors as JSON too, and one exit-code table: 0 ok, 1 error, 2 usage, 3 not found, 4 auth, 5 rate limited
* anyr --help shows the everyday commands; anyr help --all lists everything
* anyr claude, codex, opencode and grok can connect the AnyRouter MCP gateway at launch; turn it off with --no-mcp or anyr mcp auto off
* API keys are masked in every error message, including ones echoed back by a server
* anyr relay keeps one device per machine, so two machines on one key no longer replace each other
* anyr relay start and anyr relay doctor say how to get the Perplexity Decider running when it is missing, stopped or needs a Mac
* The binary is about 20% smaller
* Update with the install script: curl -fsSL [https://anyrouter.dev/setup.sh](https://anyrouter.dev/setup.sh) | bash
## 0.1.30 (2026-10-24) [#0130-2026-10-24]
A stability release. Launching Pi, Grok or OpenCode from anyr now always sends your AnyRouter key, even with a newer agent version or a separate login of its own. --preset works with a plain API key, and the relay names each device after the machine and can start your local model servers.
* anyr pi works with current Pi releases, which used to answer with 401 invalid\_api\_key
* anyr grok sends your AnyRouter key even when you are signed in to Grok, and leaves that Grok login untouched
* anyr opencode works with OpenCode 2 instead of falling back to OpenCode's own provider and key
* anyr --preset launches with a regular API key; a missing preset is reported on the first request
* anyr relay start names the device after this machine, keeps that name, and anyr relay rename changes it
* anyr relay start starts fm serve and the Perplexity Decider app when they are installed but not running, and stops them on exit; --no-launch turns this off
* anyr artifact rm --json prints a result for scripts
* Update with the install script: curl -fsSL [https://anyrouter.dev/setup.sh](https://anyrouter.dev/setup.sh) | bash
## 0.1.29 (2026-10-24) [#0129-2026-10-24]
The anyr command line covers more of the platform: compare one prompt across models, split a task between a planning and a building model, hand work to another coding agent, manage presets and paired devices, wire the MCP gateway into your agent, and look up a call or the shared pool.
* anyr eval runs one prompt across several models and prints answers, latency, tokens and cost side by side
* anyr task plans with one model and implements with another; anyr delegate hands a task to Claude Code, Codex or OpenCode
* anyr presets manages presets, and --preset launches an agent on one
* anyr mcp add wires the AnyRouter MCP gateway into Claude Code, Codex, OpenCode or Cursor
* anyr generation looks up a call's cost and provider; anyr pool shows shared pool health
* anyr relay devices lists, renames, pools and removes paired devices; anyr artifact ls pages with --cursor and --all
* Update with the install script: curl -fsSL [https://anyrouter.dev/setup.sh](https://anyrouter.dev/setup.sh) | bash
## 0.1.28 (2026-10-24) [#0128-2026-10-24]
The anyr command line gains a typed Decisions command, and piping its output into head or another short-lived reader no longer prints a panic.
* anyr decision sends a typed Decisions request from flags or JSON on stdin
* Chat-agent launchers refuse Decisions models instead of failing mid-session
* Piping output into a reader that closes early, such as head, now exits quietly
* Update with the install script: curl -fsSL [https://anyrouter.dev/setup.sh](https://anyrouter.dev/setup.sh) | bash
## 0.1.27 (2026-10-24) [#0127-2026-10-24]
The anyr CLI 0.1.27 adds anyr embed, model filters and a model detail view, anyr byok test and rm, and spend limits, endpoint limits and expiry for new API keys. Update with anyr update.
* anyr embed turns text, a file or stdin into embeddings from the terminal, with --json for loading a vector store
* anyr models --free, --type, --owner, --search and --sort help you find a model, and anyr models show prints its price, context and providers
* anyr byok test checks a saved key (or one you have not saved yet), anyr byok rm deletes it, and anyr byok providers lists who accepts your own key
* anyr keys create takes --limit, --reset, --endpoints and --expires for CI keys, and --quiet prints only the secret
* anyr keys show and anyr keys update let you check and change a key's limit, endpoints, expiry and state
* Install or update: curl -fsSL [https://anyrouter.dev/setup.sh](https://anyrouter.dev/setup.sh) | bash
## 0.1.26 (2026-10-24) [#0126-2026-10-24]
The anyr CLI 0.1.26 adds anyr logs for your recent requests and anyr transactions for your credit ledger. Update with anyr update.
* anyr logs lists your latest requests with model, provider, status, tokens, cost and latency; add --status error to see only failures
* anyr logs shows one request, and anyr logs sessions groups requests by session
* anyr transactions shows your credit ledger with signed amounts; filter with --type purchase
* Both commands accept --json, and a key without the needed permission now exits with code 4 and names the missing scope
* Install or update: curl -fsSL [https://anyrouter.dev/setup.sh](https://anyrouter.dev/setup.sh) | bash
## 0.1.25 (2026-10-24) [#0125-2026-10-24]
The anyr CLI 0.1.25 makes anyr relay easier to run and control. Run it in the background, check on it, stop it and read its logs from any terminal. Update with anyr update.
* anyr relay start --detach runs the relay in the background; anyr relay stop and anyr relay logs --follow manage it
* anyr relay status shows whether the relay is connected, which models it serves and how many requests it handled, with --json for scripts
* anyr relay pool on|off and anyr relay models enable|disable are remembered between runs, so a saved choice survives a restart
* anyr relay doctor checks your Mac, local servers and network, and prints the fix for each failure
* Apple's on-device model now answers through fm serve without a model name error
* Install or update: curl -fsSL [https://anyrouter.dev/setup.sh](https://anyrouter.dev/setup.sh) | bash
## 0.1.24 (2026-10-23) [#0124-2026-10-23]
The anyr CLI 0.1.24 can stream a chat reply and shows model types when you pipe anyr models.
* The CLI can stream a reply from the chat completions endpoint
* Piped anyr models output has a type column
* Restricted keys now get an error that names the missing scope, with one error format across commands
* anyr codex passes the context window for a concrete model
## 0.1.23 (2026-10-23) [#0123-2026-10-23]
The anyr CLI 0.1.23 makes anyr relay serve on-device models by default.
* anyr relay serves on-device models by default, and joins the pool unless you pass --no-pool
## 0.1.22 (2026-10-23) [#0122-2026-10-23]
The anyr CLI 0.1.22 adds commands to publish apps and artifacts, and lets the relay serve several local endpoints.
* anyr publish and anyr apps work with any kind of app, and anyr api accepts --data @file or - for stdin
* anyr artifact publish, ls, rm and visibility manage your artifacts
* anyr relay can serve several local endpoints and routes each request by model
* A visibility change now says whether it waits for review or is listed
* anyr decision shows the server error and a working help example
* anyr upgrade on the beta channel can take a newer stable release
## 0.1.21 (2026-10-21) [#0121-2026-10-21]
anyr claude and anyr pi now default to anyrouter/free and set the right context window.
* anyr claude and anyr pi now default to anyrouter/free
* anyr claude and anyr pi set the context window for the model you pick
## Retired JavaScript CLI (0.2.x and earlier) [#retired-javascript-cli-02x-and-earlier]
The entries below belong to the retired `@anyr/cli` npm package. They are kept for history only and do not describe the current `anyr` binary.
### Unreleased [#unreleased]
#### ✨ Features [#-features]
* **shared selector session** — first-run and `anyr login` walk providers → models → optional reasoning-effort, merge every selected provider connection, then public alias and identity defaults (`--yes` skips). Native module mirrors the contract.
* **Grok-style TUI** — GrokNight palette (magenta accent, teal model ids), `search:` picker bar, `◆` + fill selection (no reverse-video), `[ Tab ]` pills, and a filled launch band. Dialog, picker, settings, key table, and `/cli` preview share the same chrome.
* **`anyr usage`** — prints credits remaining, today's spend, and lifetime used from `GET /credits`. Non-interactive; `--json` for scripting. Honors `--key` and `ANYROUTER_API_KEY` so CI/scripts do not need a saved profile.
#### 🐛 Bug Fixes [#-bug-fixes]
* **`npx @anyr/cli` default bin** — ship a `cli` executable (same target as `anyr`) so npx's last-segment lookup no longer fails with `cli: not found` / "could not determine executable". `anyr` / `anyrouter` / `ar` stay in place.
* **pack-smoke login help** — `validate-install` now matches the auto browser → device → paste login help (`auto-detect`, `prefix…suffix`, `--device` / `--paste`) instead of the retired "Pick a key from anyrouter.dev" copy.
* **`--help` Launch agents** — spawn targets (including `pool` / `poolside`) are generated from the target registry so they cannot be omitted from `--help` (#2283).
* **documented commands** — `usage`, `logs`, `whoami`, `account`, and `login --yes` are parser-tested so they cannot regress as Unknown command/flag (#2276).
* **`models` honors `--key` / `ANYROUTER_API_KEY`** — listing the public catalog no longer requires `~/.anyrouter/config.yaml`, and `--key` is a documented flag on `models --help` (#2278).
* **`login` 403 from GET /credits** — only HTTP 401 is "key rejected". A 403 from credits (WAF/transport) falls through to a `/models` probe instead of failing a valid key (#2279).
* **`usage --help`** — native CLI help no longer promises 24h spend, top models, or fail-cached output that the binary does not implement (#2604).
* **changelog order** — version sections are listed in descending semver order; missing `0.2.9` / `0.2.10` entries restored (#2193).
### 0.2.10 (2026-08-13) [#0210-2026-08-13]
#### ✨ Features [#-features-1]
* **clack prompts** — login and onboarding use `@clack/prompts` for clearer step-by-step flows.
* **unified auto login** — browser → device-code → paste runs automatically without a 3-option menu; force a route with `--device`, `--paste`, or `--key`.
* **post-login wizard** — optional default model and coding-agent pickers after interactive login (`--yes` skips).
### 0.2.9 (2026-08-12) [#029-2026-08-12]
#### ✨ Features [#-features-2]
* **poolside harness** — Poolside launch target with harness switcher icons, sharper tab rendering, and model-aware reasoning levels.
* **post-publish install validation** — `validate-install` smoke-tests a clean `npx` install and can self-heal missing `@anyr/*` deps.
#### 🐛 Bug Fixes [#-bug-fixes-1]
* **key recovery** — fixed a crash in the invalid-key recovery prompt and an unreachable browser branch.
### 0.2.8 [#028]
#### ✨ Features [#-features-3]
* **browser callback redesign** — after PKCE approve, the loopback success page shows the AnyRouter mark, a masked key (`sk-ar-v1-xxxx…yyyy`), default model/preset, and next terminal commands. Exchange runs before the HTML response so the mask is real.
* **login auto-strategy** — `anyr login` and first-run onboarding no longer show a 3-option menu. Browser sign-in runs when a GUI looks available; device code is used automatically over SSH/headless or if the browser handoff fails. Force a route with `--device` / `--device-code`, `--paste`, or `--key`. Full secrets are never printed — only prefix…suffix + storage location.
* **post-login defaults** — after interactive login, optional wizards pick a default model (top-ranked first) and a default coding agent; both persist in `~/.anyrouter/config.yaml` (`--yes` skips them).
* **TUI key confirm** — after pick/generate/paste/login, the launcher shows `✓ Using … · sk-ar-v1-…xxxx` on the main screen.
### 0.2.4 [#024]
#### ✨ Features [#-features-4]
* **launcher tab bar** — the TUI now shows every agent as a tab strip (`┌ Claude Code ┐ Codex Grok …`) under a simple `▲ AnyRouter` brand line; `‹ ›` switches agents in place. Settings gained inline `‹ ›` cycle hints, a read-only Preset row, and an inline API-key row with a ✓/✗ status badge.
* **API key manager** — Enter on the API key row opens a key table (name / masked key / age) listing your AnyRouter keys; pick one to swap to it (server-side reveal), generate a new key in place, log in with the browser when you have no credentials, or fall back to pasting a key.
* **`models --json`** — `npx @anyr/cli models --json` prints the catalog as a machine-readable `{ auto, preset, models[] }` object (each model carries `id`, `name`, `owned_by`, `context_length`, and a `pinned` flag), matching the `--json` scripting flag already on `config get` and `skills`.
#### 🐛 Bug Fixes [#-bug-fixes-2]
* **preflight:** a rejected API key (401/403) now says so — "Your AnyRouter API key was rejected" with a `setup` / dashboard-keys hint — instead of the misleading "model isn't serving" block.
* **preflight:** the pre-launch failure block now renders the AnyRouter brand header (same wordmark as the TUI) instead of a bare error.
* **docs:** fixed a broken first-run login link in the README (`https://cli/authorize` → `https://anyrouter.dev/cli/authorize`).
### [0.2.3](https://github.com/duyet/anyrouter/compare/cli-v0.2.2...cli-v0.2.3) (2026-08-01) [#023-2026-08-01]
#### ✨ Features [#-features-5]
* **cli:** dialog model picker, faceted search, and key hot-swap ([5914502](https://github.com/duyet/anyrouter/commit/591450282ec22c11bd1c3c5e604459219ff8b488))
* **cli:** fuzzy model picker over name/owner, pricing hints, in-TUI browser keys ([b83cfb9](https://github.com/duyet/anyrouter/commit/b83cfb96dd6e34f64d9861ca0d70d65e2b60fe00))
* **cli:** fuzzy-searchable key picker matching model picker UX ([1c4a5d6](https://github.com/duyet/anyrouter/commit/1c4a5d6f12ce8645048f159e01ba9a848b3a110c))
* **cli:** in-TUI model and key pickers, onboarding, terminal marks, faster launch ([7b33ba6](https://github.com/duyet/anyrouter/commit/7b33ba6046484e92e31315fd7ff9dbc18c59a46e))
* **cli:** pricing-aware model options + browser keys command ([e13d974](https://github.com/duyet/anyrouter/commit/e13d974db650f76c85e91fa122bcc2d0b7300d83))
* **telemetry:** email engagement tracking and client-type attribution ([ebbdc9d](https://github.com/duyet/anyrouter/commit/ebbdc9dfceb7e263ec39cdab7bea8e9a036f821d))
#### 🐛 Bug Fixes [#-bug-fixes-3]
* **cli:** repair the broken key-manager import that crashed the binary on startup ([b546af5](https://github.com/duyet/anyrouter/commit/b546af53a81e0f0d317a9683d324ccc28e774471))
* **cli:** suppress SGR colour codes when stdout is not a TTY ([13f1f8b](https://github.com/duyet/anyrouter/commit/13f1f8b3083a897c8fbe18aa068ed1197c669c84))
* **lint:** clear biome errors blocking main ([ccb8cc2](https://github.com/duyet/anyrouter/commit/ccb8cc20481a506485019fbf947968204a678101))
* replace workspace: deps with real versions for npm publish ([7421bdb](https://github.com/duyet/anyrouter/commit/7421bdb0eedceaeebf51fe9243bb5d46e6403ad7))
#### ⚡ Performance [#-performance]
* **cli:** cache launch probes to cut startup latency ([58ec1d0](https://github.com/duyet/anyrouter/commit/58ec1d00161aa203d9b6af5dfaa70ea6c5e88e62))
#### Dependencies [#dependencies]
* The following workspace dependencies were updated
* dependencies
* @anyr/ai-sdk-provider bumped from 0.1.4 to 0.1.6
### 0.2.0 (2026-06-26) [#020-2026-06-26]
#### ✨ Features [#-features-6]
* **Fuzzy model picker** — type to filter the model list in the launcher; matches by subsequence on the model id with contiguous matches ranked first (typing `glm` surfaces `glm-5.2` before `gemini`).
* **Pre-launch credit check** — warns before launch if your credit balance is `$0` on a paid model, with a clear pointer to top up or use a free model. Soft-fails silently if the check can't run, and never false-alarms free-tier usage.
* **opencode rich provider config** — opencode now launches through a full `OPENCODE_CONFIG_CONTENT` provider config (correct base URL + model context window) instead of flat `OPENAI_*` env, so it sizes context correctly.
#### 🔧 Maintenance [#-maintenance]
* Removed the non-functional `gemini` launch target — gemini-cli speaks the Gemini-native protocol and can't reach the gateway's OpenAI/Anthropic endpoints. Supported targets are now Claude Code, Codex, Grok, and opencode.
### 0.1.6 (2026-06-19) [#016-2026-06-19]
#### 🔧 Maintenance [#-maintenance-1]
* Smaller, faster install — the CLI now ships as a single self-contained file.
### 0.1.5 (2026-06-19) [#015-2026-06-19]
#### 🐛 Bug Fixes [#-bug-fixes-4]
* Fixed `npx @anyr/cli` printing nothing on some setups.
### 0.1.4 (2026-06-19) [#014-2026-06-19]
#### ✨ Features [#-features-7]
* `` opens a launcher to review your settings before starting; add `--ok` to start right away.
* Running `npx @anyr/cli` with no arguments reopens your last-used agent.
* Checks the selected model is reachable before launching, with a clear message if it isn't.
#### 🔧 Maintenance [#-maintenance-2]
* Settings now live in `~/.anyrouter/` (existing settings migrate automatically).
### 0.1.3 (2026-06-18) [#013-2026-06-18]
#### 🔧 Maintenance [#-maintenance-3]
* patch release
### 0.1.2 (2026-06-18) [#012-2026-06-18]
#### 🔧 Maintenance [#-maintenance-4]
* bump `@anyr/sdk` to `^0.1.3` — inherits automatic transport retries (connection errors, timeouts, `408`/`409`/`429`/`5xx` with exponential backoff + jitter) and per-request timeout/abort
### 0.1.1 (2026-06-18) [#011-2026-06-18]
#### ✨ Features [#-features-8]
* centralize all network access through `@anyr/sdk` and launch off the shared target registry
* target registry for 7 coding agents — Claude Code, Codex, Grok, OpenCode, Cursor, Cline, and Windsurf — via spawn-launch or editor config export
* animated TUI: spinner, gradient text, logo reveal, and an adaptive settings renderer
* onboarding flow and runtime-env override support
#### 🐛 Bug Fixes [#-bug-fixes-5]
* seamless logo reveal, preset timer cleanup, and held-key decoding
---
# anyr claude (/cli/claude-code)
Route Claude Code through AnyRouter to get unified billing, request audit logs, and a single API key for every model AnyRouter serves — either with one CLI command or two environment variables.
## Setup [#setup]
Most people should use the CLI helper — `anyr claude` handles login, key storage, and the environment for you. Reach for manual configuration only when you want to control the environment yourself.
The fastest path — one command handles everything:
```bash
curl -fsSL https://anyrouter.dev/setup.sh | bash
anyr claude
anyr claude --install --ok # install Claude Code first if it is missing
```
### Step 1 — Run the launcher [#step-1--run-the-launcher]
The first run signs you in automatically (browser when available, device code on SSH/headless). Use `--key`, `--paste`, or `--device` if you need a specific path. See [Login with AnyRouter](/cli/login).
### Step 2 — Reuse it everywhere [#step-2--reuse-it-everywhere]
Your config is saved to `~/.anyrouter/config.yaml` and reused automatically next time. The same key also powers `anyr codex` and the other launchers.
### Step 3 — Pick models and flags [#step-3--pick-models-and-flags]
```bash
# Pin a model for the session
anyr claude --model "z-ai/glm-5.2"
# Set reasoning depth
anyr claude --model auto --effort high
# Pass flags through to Claude Code itself
anyr claude --model auto -- --resume
# Preview the resolved environment without running anything
anyr claude --dry-run
```
`auto` is the catalog id `anyrouter/auto` (smart pick). How the model flag maps onto Claude Code's aliases:
* **`--model auto`, `--model anyrouter/free`, another `anyrouter/*` router, or no `--model`** — Claude Code's **haiku**, **sonnet**, **opus**, and **fable** aliases keep their Anthropic defaults (Claude Haiku 4.5, Sonnet 4.6, Opus 4.6, Fable 5), and subagents use the haiku alias.
* **`--model `** (one catalog model) — that id is used for the session, for every alias you have not set, and for Claude Code's subagents (`CLAUDE_CODE_SUBAGENT_MODEL`).
An alias you set always wins over `--model`:
```bash
anyr claude --haiku "anthropic/claude-haiku-4-5" --sonnet "anthropic/claude-sonnet-4-6" --opus "anthropic/claude-opus-4-6"
anyr models use --haiku "z-ai/glm-4.7-flash"
anyr models --pick # Default / Haiku / Sonnet / Opus / Fable
```
They persist in `~/.anyrouter/config.yaml` as `claude_haiku`, `claude_sonnet`, `claude_opus`, and `claude_fable`. Use `--dry-run` to see the resolved `ANTHROPIC_DEFAULT_*_MODEL` values.
Everything after `--` forwards to Claude Code verbatim. See the [configuration reference](/cli/configuration) for `--model`, `--effort`, `--profile`, and the rest.
### Step 1 — Create an AnyRouter API key [#step-1--create-an-anyrouter-api-key]
Head to your [dashboard](https://anyrouter.dev/dashboard/keys) and create a new API key. It is prefixed with `sk-ar-`.
### Step 2 — Point Claude Code at AnyRouter [#step-2--point-claude-code-at-anyrouter]
Claude Code reads `ANTHROPIC_BASE_URL` and `ANTHROPIC_API_KEY` from the environment. Set them to your AnyRouter endpoint and key:
```bash
export ANTHROPIC_BASE_URL="https://anyrouter.dev/api"
export ANTHROPIC_API_KEY="sk-ar-your-anyrouter-key"
```
Drop these two lines into `~/.bashrc` or `~/.zshrc` so they persist across shells.
### Step 3 — Run Claude Code [#step-3--run-claude-code]
That's it. Every Claude Code request now routes through AnyRouter, billed against your credits and recorded in your audit logs:
```bash
# Start an interactive session
claude
# Run a one-shot prompt
claude "Explain the architecture of this project"
# Pick any AnyRouter model on the fly
claude --model "anthropic/claude-sonnet-4-6" "Refactor this function"
claude --model "z-ai/glm-4.7-flash" "Review this PR"
```
### Optional — wrapper script [#optional--wrapper-script]
If you keep an Anthropic key for other work and only want AnyRouter for some sessions, drop the env vars into a wrapper instead of your shell rc. Save it as `~/bin/claude-ar` (or anywhere on `$PATH`) and `chmod +x`:
```bash
#!/usr/bin/env bash
# Wrapper: route Claude Code through AnyRouter
export ANTHROPIC_AUTH_TOKEN="${ANYROUTER_API_KEY:-sk-ar-your-anyrouter-key}"
export ANTHROPIC_BASE_URL="https://anyrouter.dev/api"
# Optional — pin the model tier names to specific AnyRouter model ids
export ANTHROPIC_DEFAULT_HAIKU_MODEL="anthropic/claude-haiku-4-5"
export ANTHROPIC_DEFAULT_SONNET_MODEL="anthropic/claude-sonnet-4-6"
export ANTHROPIC_DEFAULT_OPUS_MODEL="anthropic/claude-opus-4-6"
# Long-running agent flows: bump the SDK timeout
export API_TIMEOUT_MS=3000000
exec claude "$@"
```
Now `claude-ar` uses AnyRouter while bare `claude` keeps using your Anthropic key. Every flag forwards through to `claude`.
## GLM-5.2 with your own Z-AI key (BYOK) [#glm-52-with-your-own-z-ai-key-byok]
Prefer Z-AI's GLM-5.2 as your coding agent? Bring your own Z-AI key and AnyRouter routes Claude Code to it — you pay Z-AI directly (or use your Z-AI Coding Plan subscription), and AnyRouter adds nothing to the per-token cost.
GLM-5.2 has a **1M-token context**, a **128K output window**, and controllable **reasoning effort**, making it a strong option for long-horizon coding sessions.
### Step 1 — Add your Z-AI key [#step-1--add-your-z-ai-key]
Open [Settings → BYOK](https://anyrouter.dev/dashboard/byok) and paste your Z-AI API key. If you have a **Z-AI Coding Plan**, paste that key — AnyRouter routes it through the Coding Plan endpoint automatically. Your key is encrypted at rest and only used to sign your own requests.
### Step 2 — Point Claude Code at GLM-5.2 [#step-2--point-claude-code-at-glm-52]
```bash
anyr claude --model "z-ai/glm-5.2"
```
Or with manual env vars and the wrapper:
```bash
export ANTHROPIC_BASE_URL="https://anyrouter.dev/api"
export ANTHROPIC_API_KEY="sk-ar-your-anyrouter-key"
claude-ar --model "z-ai/glm-5.2" "Refactor this module and add tests"
```
Pin GLM-5.2 as the default tier models in your wrapper:
```bash
export ANTHROPIC_DEFAULT_SONNET_MODEL="z-ai/glm-5.2"
export ANTHROPIC_DEFAULT_OPUS_MODEL="z-ai/glm-5.2"
```
### Reasoning effort [#reasoning-effort]
GLM-5.2 supports controllable thinking. AnyRouter maps the standard `reasoning_effort` levels onto Z-AI's thinking control: `low`/`medium`/`high`/`max` enable thinking, while `none`/`minimal` disable it. Use Claude Code's `/effort` command or the CLI `--effort` flag:
```bash
anyr claude --model "z-ai/glm-5.2" --effort high "Plan a migration"
```
### 1M context [#1m-context]
To use GLM-5.2's full 1M-token window in Claude Code, raise the auto-compact threshold so Claude Code keeps the long context instead of summarizing early:
```bash
export CLAUDE_CODE_AUTO_COMPACT_WINDOW=1000000
```
## MCP tools [#mcp-tools]
`anyr claude` connects the AnyRouter MCP gateway for you when **Auto-connect in coding agents** is on in the [dashboard](https://anyrouter.dev/dashboard/mcp). It uses the same key the session launches with, so there is nothing to paste. To turn it off, pass `--no-mcp` on a launch, run `anyr mcp auto off`, or switch it off in the dashboard.
## Verify [#verify]
Confirm requests are flowing through AnyRouter:
* Run a one-shot prompt (`claude "hello"` or `anyr claude "hello"`) and check that it responds.
* Open your [usage dashboard](https://anyrouter.dev/dashboard/usage) — the request should appear in your audit log with cost, latency, and token counts.
* With the CLI, `anyr claude --dry-run` prints the resolved command and environment (secrets redacted) so you can confirm the base URL and model before sending anything.
## Troubleshooting [#troubleshooting]
**401 or authentication errors** — your key isn't reaching Claude Code. In manual mode, re-check `ANTHROPIC_API_KEY` is exported in the current shell (`echo $ANTHROPIC_API_KEY`). With the CLI, run `anyr config get` to confirm a key is stored, or re-run `anyr setup`.
**Requests still hit Anthropic directly** — `ANTHROPIC_BASE_URL` isn't set (or a wrapper isn't on `$PATH`). Confirm `echo $ANTHROPIC_BASE_URL` prints `https://anyrouter.dev/api`, and if you use the `claude-ar` wrapper, that `~/bin` is on your `$PATH`.
**Long agent runs time out** — bump the SDK timeout with `export API_TIMEOUT_MS=3000000` (50 minutes) before launching Claude Code.
## Supported models [#supported-models]
Browse every model Claude Code can call by filtering the catalog to Messages-compatible models: [/models?endpoint=messages](https://anyrouter.dev/models?endpoint=messages).
## Related [#related]
* [Codex setup](/cli/codex)
* [OpenCode setup](/cli/opencode)
* [Grok Bot, Herdr, and Anyr CLI](/guides/grok-bot-herdr-anyr) — fleet coding when Cursor cloud is blocked
* [Configuration reference](/cli/configuration)
* [Login with AnyRouter](/cli/login)
* [How to unify AI subscriptions](/scenario-examples/unify-ai-subscriptions)
---
# anyr codex (/cli/codex)
Route [Codex](https://developers.openai.com/codex/cli) through AnyRouter to get unified billing, request audit logs, and a single API key that reaches every model AnyRouter serves — either with one CLI command or two environment variables.
## Setup [#setup]
The CLI helper is the shortest path — `anyr codex` reuses the same stored key as `anyr claude`. Use manual configuration only when you want to set the environment yourself.
The fastest path — one command handles everything:
```bash
anyr codex
```
### Step 1 — Run the launcher [#step-1--run-the-launcher]
The first run signs you in automatically (browser when available, device code on SSH/headless). Use `--key`, `--paste`, or `--device` if you need a specific path. See [Login with AnyRouter](/cli/login).
### Step 2 — Reuse it everywhere [#step-2--reuse-it-everywhere]
Your config is saved to `~/.anyrouter/config.yaml` and reused next time — the same key also powers `anyr claude` for Claude Code.
### Step 3 — Pick models and flags [#step-3--pick-models-and-flags]
```bash
# Pin a model and set reasoning depth
anyr codex --model auto --effort xhigh
# Pass flags through to Codex itself
anyr codex --model auto -- exec
# Preview the resolved environment without running anything
anyr codex --dry-run
```
Anything after `--` forwards to Codex verbatim. See the [configuration reference](/cli/configuration) for all flags.
### Step 1 — Create an AnyRouter API key [#step-1--create-an-anyrouter-api-key]
Head to your [dashboard](https://anyrouter.dev/dashboard/keys) and create a new API key. It is prefixed with `sk-ar-`.
### Step 2 — Point Codex at AnyRouter [#step-2--point-codex-at-anyrouter]
Codex reads `OPENAI_BASE_URL` and `OPENAI_API_KEY`. Point them at AnyRouter's OpenAI-compatible endpoint:
```bash
export OPENAI_BASE_URL="https://anyrouter.dev/api/v1"
export OPENAI_API_KEY="sk-ar-your-anyrouter-key"
```
Drop these two lines into `~/.bashrc` or `~/.zshrc` so they persist across shells.
### Step 3 — Run Codex [#step-3--run-codex]
Every Codex request now routes through AnyRouter, billed against your credits and recorded in your audit logs:
```bash
codex "Refactor this function to use async/await"
```
## MCP tools [#mcp-tools]
`anyr codex` connects the AnyRouter MCP gateway for you when **Auto-connect in coding agents** is on in the [dashboard](https://anyrouter.dev/dashboard/mcp). It uses the same key the session launches with, so there is nothing to paste. To turn it off, pass `--no-mcp` on a launch, run `anyr mcp auto off`, or switch it off in the dashboard.
## Verify [#verify]
Confirm requests are flowing through AnyRouter:
* Run a one-shot prompt (`codex "hello"` or `anyr codex "hello"`) and check that it responds.
* Open your [usage dashboard](https://anyrouter.dev/dashboard/usage) — the request should appear in your audit log with cost, latency, and token counts.
* With the CLI, `anyr codex --dry-run` prints the resolved command and environment (secrets redacted) so you can confirm the base URL and model first.
## Troubleshooting [#troubleshooting]
**401 or authentication errors** — your key isn't reaching Codex. In manual mode, confirm `OPENAI_API_KEY` is exported in the current shell (`echo $OPENAI_API_KEY`). With the CLI, run `anyr config get` to check a key is stored, or re-run `anyr setup`.
**Requests still hit OpenAI directly** — `OPENAI_BASE_URL` isn't set. Confirm `echo $OPENAI_BASE_URL` prints `https://anyrouter.dev/api/v1` (note the `/v1` suffix — Codex uses the OpenAI-compatible path).
**Model not found** — Codex uses the Chat Completions surface. Make sure the model id you pass is Chat-Completions-compatible (see the filter link below).
## Supported models [#supported-models]
Browse every model usable with Codex by filtering the catalog to Chat-Completions-compatible models: [/models?endpoint=chat-completions](https://anyrouter.dev/models?endpoint=chat-completions).
## Related [#related]
* [Claude Code setup](/cli/claude-code)
* [OpenCode setup](/cli/opencode)
* [Configuration reference](/cli/configuration)
* [Login with AnyRouter](/cli/login)
* [Chat Completions API](/api-reference/chat-completions)
---
# Configuration (/cli/configuration)
Reference for how the AnyRouter CLI is configured: the global config file, an optional per-project `anyrouter.yaml`, all profile fields, per-tool overrides, every CLI flag, and the management commands. One stored key drives every tool.
Add `$schema: https://anyrouter.dev/cli/schema.json` to the top of any config file for autocomplete and inline validation in VS Code, WebStorm, and any editor that understands JSON Schema.
## Config file [#config-file]
The CLI stores your profile(s) and tool settings in `~/.anyrouter/config.yaml`. A profile from an older CLI at `~/.config/anyrouter/config.yaml` is migrated forward automatically on first run.
```yaml
$schema: https://anyrouter.dev/cli/schema.json
active_profile: default
profiles:
default:
base_url: https://anyrouter.dev/api
pinned_preset: "@preset/coding-stack"
default_model: auto
timeout_ms: 3000000
tools:
claude:
command: claude
base_url_env: ANTHROPIC_BASE_URL
auth_env: ANTHROPIC_AUTH_TOKEN
model_env: ANTHROPIC_MODEL
codex:
command: codex
base_url_env: OPENAI_BASE_URL
auth_env: OPENAI_API_KEY
model_env: OPENAI_MODEL
```
The full schema is published at **[https://anyrouter.dev/cli/schema.json](https://anyrouter.dev/cli/schema.json)**.
## Profile fields [#profile-fields]
Each entry under `profiles:` accepts:
| Field | Type | Description |
| ---------------------- | ------ | -------------------------------------------------------------------------------------------------------------------------- |
| `base_url` | string | AnyRouter API base URL. Default: `https://anyrouter.dev/api`. |
| `pinned_preset` | string | Default preset slug (e.g. `@preset/coding-stack`). Used when `default_model` is `auto`. |
| `default_model` | string | Default model id, or `auto` to let the pinned preset choose. Set by the post-login selector session. |
| `default_effort` | string | Optional reasoning-effort for models that declare catalog levels. Omitted when skipped. |
| `provider_connections` | string | Comma-separated catalog owner ids from the selector session. Re-login merges this set. |
| `public_alias` | string | Local public model alias (slug of the selected model id, max 30). |
| `identity` | string | Gateway identity id. Default `default`. |
| `default_tool` | string | Preferred coding agent id from the post-login wizard (e.g. `claude`, `codex`, `grok`). Launch with `anyr ` as usual. |
| `timeout_ms` | number | Request timeout in milliseconds. Default: `3000000` (50 minutes). |
## Per-tool overrides [#per-tool-overrides]
The `tools` section overrides the command name and environment variable names for each supported tool — useful if you have a custom wrapper or renamed executable.
| Field | Description |
| -------------- | ------------------------------------------------------------- |
| `command` | Executable to run (resolved from `$PATH`). |
| `base_url_env` | Environment variable the tool reads for the API base URL. |
| `auth_env` | Environment variable the tool reads for the API key or token. |
| `model_env` | Environment variable the tool reads for the model name. |
## Project-level `anyrouter.yaml` [#project-level-anyrouteryaml]
Drop an `anyrouter.yaml` in your repo root (or any parent directory) to pin defaults for that project. The CLI walks up from your current directory to find the nearest one, and its values override your global `~/.anyrouter/config.yaml` when you launch from inside the tree.
```yaml
# yaml-language-server: $schema=https://anyrouter.dev/cli/schema.json
model: auto
preset: coding-stack
effort: high
tool: claude
```
| Field | Description |
| --------------------------- | ---------------------------------------------------------------------- |
| `model` | Default model id, or `auto` to use the pinned preset. |
| `preset` | Preset slug (e.g. `coding-stack` or `@preset/coding-stack`). |
| `effort` | Reasoning depth: `low`, `medium`, `high`, `xhigh`, or `max`. |
| `tool` | Default launcher when none is specified: `claude`, `codex`, or `grok`. |
| `claude` / `codex` / `grok` | Per-tool overrides for model, preset, effort, or base URL. |
## CLI flags [#cli-flags]
Flags go before any `--`; everything after `--` is forwarded verbatim to the underlying tool.
```bash
anyr claude --model auto --effort high -- --resume
anyr codex --model "anthropic/claude-sonnet-4-6" --effort xhigh
```
| Flag | Meaning |
| ---------------------------------------- | -------------------------------------------------------------------------------- |
| `--model auto\|` | Session model. `auto` picks the best model from your pinned preset. |
| `--effort low\|medium\|high\|xhigh\|max` | Reasoning depth. Mapped to each tool's native control. |
| `--hub ` | Load a Skills & Knowledge Hub for this session (Claude Code). |
| `--profile ` | Use a named profile from your config. |
| `--key sk-ar-...` | Provide a key non-interactively (first run or CI). |
| `--device` / `--device-code` | Force the device code flow (headless / SSH). |
| `--paste` | Prompt to paste an `sk-ar-` key (skip auto browser / device detection). |
| `--yes` | Skip interactive prompts (including the post-login selector session on `login`). |
| `--command-path ` | Explicit path to the agent executable. |
| `--dry-run` | Print the resolved command and environment (secrets redacted). Run nothing. |
| `--config ` | Use a different config file. |
| `--plaintext` | Store the key in `config.yaml` instead of the OS keychain. |
On macOS the key is stored in the system keychain by default; elsewhere it is written to `config.yaml` (readable only by you, mode `600`).
## Management commands [#management-commands]
```bash
anyr auth login # sign in (browser first, device code fallback)
anyr setup # save a local profile (same auto sign-in)
anyr whoami # name, email, account, masked key, default model, config path
anyr account list # multiple accounts (* = active)
anyr config get # show the active config (secrets redacted)
anyr config path # print the config file location
anyr config use work # switch to the "work" profile
anyr models # list catalog model ids for --model (--free, --type, --sort; `models show `)
anyr skills sync # sync your Skills & Knowledge Hub locally
```
## Find a command [#find-a-command]
```bash
anyr --help # the everyday commands: login, chat, models, claude, relay, artifact publish, usage
anyr help --all # every command, grouped (sign in, chat and models, launch, relay, publish, account, setup)
anyr --help # flags and examples for one command
anyr commands --json # the full command tree for scripts and agents
```
## Multiple profiles [#multiple-profiles]
Add named profiles under `profiles:` and switch between them with `--profile` or `config use`:
```yaml
$schema: https://anyrouter.dev/cli/schema.json
active_profile: default
profiles:
default:
base_url: https://anyrouter.dev/api
default_model: auto
pinned_preset: "@preset/coding-stack"
timeout_ms: 3000000
work:
base_url: https://anyrouter.dev/api
default_model: "anthropic/claude-sonnet-4-6"
timeout_ms: 600000
```
## Non-interactive / CI environments [#non-interactive--ci-environments]
When there is no TTY (CI, scripts, Docker), the CLI skips the interactive prompt. Supply a key via flag or environment variable:
```bash
# Flag
anyr claude --key sk-ar-your-key "Review this PR"
# Environment variable
ANYROUTER_API_KEY=sk-ar-your-key anyr claude "Review this PR"
# Catalog listing — no saved profile required
anyr models --key sk-ar-your-key
ANYROUTER_API_KEY=sk-ar-your-key anyr models --json
```
`models` lists the public catalog and does not require a prior `login` or `~/.anyrouter/config.yaml`. `--key` and `ANYROUTER_API_KEY` are accepted the same way as on `login`, `usage`, and `logs`.
Every command accepts `--json` (or `ANYR_OUTPUT=json`), reports errors as JSON, and uses fixed exit codes. See [Use anyr from scripts and AI agents](/cli/agents).
## Verify [#verify]
Check that your config resolves as expected before sending a real request:
* `anyr config get` prints the active config with secrets redacted.
* `anyr config path` prints which config file is in effect (useful when a project `anyrouter.yaml` overrides the global file).
* `anyr claude --dry-run` prints the resolved command, model, and environment — nothing is sent.
## Related [#related]
* [AnyRouter CLI overview](/cli)
* [Claude Code setup](/cli/claude-code)
* [Codex setup](/cli/codex)
* [OpenCode setup](/cli/opencode)
* [Login with AnyRouter](/cli/login)
* [Use anyr from scripts and AI agents](/cli/agents)
---
# Delegate tasks between coding agents (/cli/delegate)
One AnyRouter key drives every coding agent — so agents can hand work to each other. The documented path is the CLI:
```bash
anyr delegate --to ""
```
Delegation is limited to **one hop**: a delegated agent can never delegate again, so two agents cannot ping-pong tasks (and spend) between each other.
## From the terminal [#from-the-terminal]
```bash
anyr delegate --to codex "add unit tests for src/utils/slug.ts"
anyr delegate --to claude "explain this stack trace" --model deepseek/deepseek-v3
anyr delegate --to opencode "fix the lint errors in packages/ui" --yes
```
The CLI launches the target agent's non-interactive mode (`claude -p`, `codex exec`, `opencode run`) with the same gateway configuration as a normal `anyr ` launch — your stored key, base URL, and model — streams the agent's output, and exits with the agent's exit code.
Before running, a terminal prompt confirms the target and model (an agent run can make many model calls). In scripts and CI pass `--yes`.
| Flag | Meaning |
| ------------------ | --------------------------------------------------------- |
| `--to ` | Target: `claude`, `codex`, or `opencode` (required) |
| `--model ` | Model for the run (default: your profile's default model) |
| `--yes` | Skip the confirm prompt (required without a terminal) |
| `--dry-run` | Show the exact command and environment (secrets redacted) |
| `--profile ` | Use a named [account](/cli/accounts-and-keys) |
The target agent must be installed; if it is not, the CLI prints the install command.
## Bridging from an already-running agent session [#bridging-from-an-already-running-agent-session]
If you already have a session running on the (deprecated) `@anyr/agent` runtime, it exposes the same capability as a tool the model can call:
```
delegate_task(tool, task, model?)
```
It is a thin bridge — under the hood it runs `anyr delegate` with your stored credentials, with the same one-hop guard and a 10-minute cap. This is a compatibility bridge for existing sessions, not a reason to adopt `@anyr/agent`; for new setups use `anyr delegate` (or launch agents with `anyr claude` / `anyr codex`) directly.
## Task pipelines without an agent [#task-pipelines-without-an-agent]
For a lighter two-step (a strong model plans, a fast model executes — no coding agent spawned), see [`anyr task`](/cli/task).
## Guardrails [#guardrails]
* **One hop.** Delegated agents run with a depth marker; a second delegation is refused.
* **Confirmation.** Terminal runs confirm first; non-interactive runs require `--yes`.
* **Timeout.** In-agent delegations are capped at 10 minutes so a hung child cannot stall your session.
* **Same key, same logs.** Delegated runs authenticate with your stored key and appear in your [request logs](https://anyrouter.dev/dashboard/) like any other usage.
---
# Login with AnyRouter (/cli/login)
The AnyRouter CLI signs you in once, stores a key locally, and reuses it on every later run. There is no multi-option menu — the CLI picks the best path for your environment.
## Sign in [#sign-in]
Install once with `curl -fsSL https://anyrouter.dev/setup.sh | bash`, then run `anyr auth login` (or any launcher such as `anyr claude`). The CLI opens your browser, completes sign-in, and saves the key automatically. On SSH or headless machines it switches to the device code flow for you. `anyr login` is an alias.
```bash
curl -fsSL https://anyrouter.dev/setup.sh | bash
anyr auth login
# same auto path on first run of any launcher:
anyr claude
anyr chat
```
### What happens by default [#what-happens-by-default]
1. **Browser when available** — the CLI opens anyrouter.dev, you sign in and approve, and a key is returned to the terminal over a local callback. The full secret is never printed (only a short `sk-ar-v1-…` prefix…suffix hint).
2. **Device code on SSH / headless / failed browser** — if you are over SSH, in CI, on a display-less Linux shell, or the browser handoff does not finish, the CLI prints a short code and URL. Open the URL on any signed-in device, enter the code, and the CLI picks up the key.
3. **Profile is saved** — the key is stored (macOS keychain by default, otherwise `~/.anyrouter/config.yaml`) and reused by every launcher.
After a successful interactive login a selector session can optionally set:
* **providers** to connect (multi-select; later picks merge with ones already stored)
* a **default model** (filtered by those providers; top-ranked first, or search; `auto` keeps smart routing)
* **reasoning effort** when the model declares catalog levels
* **public alias** and **identity** defaults (Enter accepts, or edit)
* a **default coding agent** (Claude Code, Codex, Grok Build, and other spawn targets)
Those values are written to `~/.anyrouter/config.yaml`. Pass `--yes` to skip the prompts. Secrets are not collected here — use `anyr byok add` for provider keys.
## Escape hatches [#escape-hatches]
Use a flag when you want a specific route:
| Flag | When to use |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `--key sk-ar-v1-...` | Non-interactive or when you already have a key. Also works as `ANYROUTER_API_KEY`. |
| `--device` / `--device-code` | Force the device code flow (headless / SSH), even if a browser looks available. |
| `--paste` | Prompt to paste an `sk-ar-` key from [Dashboard → Keys](https://anyrouter.dev/dashboard/keys), skipping auto-detection. |
| `--plaintext` | Always store the key in `config.yaml` instead of the OS keychain. |
| `--yes` | Skip the post-login selector session and coding-agent prompts. |
```bash
# Force device code (e.g. remote server)
anyr auth login --device
# Paste a key interactively
anyr auth login --paste
# CI / scripts — no TTY
anyr auth login --key sk-ar-v1-your-key
ANYROUTER_API_KEY=sk-ar-v1-your-key anyr claude --yes
```
Non-interactive environments (CI, no TTY) never prompt. Pass `--key`, set `ANYROUTER_API_KEY`, or use `--device` / `--device-code`.
## How browser sign-in works [#how-browser-sign-in-works]
1. The CLI starts a short-lived local callback and opens the authorize page in your browser.
2. You sign in and choose or create an API key.
3. The browser returns a one-time code to `localhost`; the CLI exchanges it for a key.
4. A success page shows only a **masked** key hint. The full secret stays in the CLI process and is saved to the keychain or config file.
Nothing sensitive appears in the browser address bar. You do not need to copy or paste the key in the default browser path.
## Device code (headless / SSH) [#device-code-headless--ssh]
```bash
anyr auth login --device
# or on first launch of a tool:
anyr claude --device
```
1. The CLI prints a short user code and a verification URL.
2. Open the URL on any device where you are signed in, enter the code, and approve.
3. Codes are single-use and expire after a few minutes. The CLI polls and stores the granted key.
## What the key can do [#what-the-key-can-do]
The key created at login has the same capabilities as one you create in the dashboard, including **Key Management** and **Credits** endpoints by default (unrestricted `allowed_endpoints`). It appears under [API Keys](https://anyrouter.dev/dashboard/keys) and can be revoked at any time. `anyr keys list|create|use|revoke` authenticates with this same API key — there is no separate management credential.
If you paste a dashboard key that has Key Management turned off, `anyr keys` returns 403 until you re-run `anyr auth login` (mint a CLI key) or enable Key Management on that key in the dashboard.
## Where the key is stored [#where-the-key-is-stored]
| Platform | Storage |
| --------------- | ---------------------------------------------------------------------------------------- |
| macOS | The system keychain (Keychain Access). The raw secret is not written to disk by default. |
| Linux / Windows | `~/.anyrouter/config.yaml` with permissions `600` (readable only by your user). |
| Override | Pass `--plaintext` to always write to `config.yaml` regardless of platform. |
Shared settings (default model, default agent, presets, profiles) live in `~/.anyrouter/config.yaml`. An older config at `~/.config/anyrouter/config.yaml` is migrated automatically on first run.
## Check credits [#check-credits]
```bash
anyr usage
anyr usage --json
```
Prints remaining credits, today's spend, and lifetime used. If the refresh fails, the CLI shows the last cached snapshot and how old it is.
## Revoking access [#revoking-access]
Open [Dashboard → Keys](https://anyrouter.dev/dashboard/keys), find the key the CLI created, and revoke it. The CLI will ask you to sign in again on its next run that needs a key.
## Troubleshooting [#troubleshooting]
**Browser did not open or timed out** — over SSH or without a display the CLI falls back to device code automatically. To force that path: `anyr auth login --device`.
**Key rejected** — that message means the gateway returned HTTP 401. Make sure the full key was used (`sk-ar-v1-…`). Create a fresh key in [Dashboard → Keys](https://anyrouter.dev/dashboard/keys) and run `anyr auth login --key sk-ar-v1-...` or `--paste`. An HTTP 403 from credits (for example a WAF challenge) is not treated as a rejected key.
**Want to re-run the selector session** — run `anyr auth login` again (or edit `provider_connections` / `default_model` / `default_effort` / `default_tool` in `~/.anyrouter/config.yaml`). Use `--yes` only when you want to skip the prompts. Provider connections merge on re-login instead of replacing the set.
## Related [#related]
* [AnyRouter CLI overview](/cli)
* [Configuration reference](/cli/configuration)
* [API Keys](/features/api-keys)
---
# anyr opencode (/cli/opencode)
Route [OpenCode](https://opencode.ai) through AnyRouter to get unified billing, request audit logs, and a single API key that reaches every model AnyRouter serves — either with one CLI command or a custom provider in `opencode.json`.
## Setup [#setup]
Most people should use the CLI helper — `anyr opencode` handles login, key storage, and the OpenCode provider config for you. Reach for a persistent `opencode.json` only when you want AnyRouter available every time you run `opencode` yourself.
The fastest path — one command handles everything:
```bash
curl -fsSL https://anyrouter.dev/setup.sh | bash
anyr opencode
```

### Step 1 — Run the launcher [#step-1--run-the-launcher]
The first run signs you in automatically (browser when available, device code on SSH/headless). Use `--key`, `--paste`, or `--device` if you need a specific path. See [Login with AnyRouter](/cli/login).
### Step 2 — Reuse it everywhere [#step-2--reuse-it-everywhere]
Your config is saved to `~/.anyrouter/config.yaml` and reused automatically next time. The same key also powers `anyr claude` and `anyr codex`.
### Step 3 — Pick models and flags [#step-3--pick-models-and-flags]
```bash
# Pin a model for the session
anyr opencode --model "anthropic/claude-sonnet-4-6"
# Pass flags through to OpenCode itself
anyr opencode --model auto -- --help
# Preview the resolved environment without running anything
anyr opencode --dry-run
```
Anything after `--` forwards to OpenCode verbatim. See the [configuration reference](/cli/configuration) for `--model`, `--effort`, `--profile`, and the rest.
The launcher does **not** write OpenCode's config files. It injects an `OPENCODE_CONFIG_CONTENT` provider for that process only — quit OpenCode and your global `opencode.json` is unchanged.
### Step 1 — Create an AnyRouter API key [#step-1--create-an-anyrouter-api-key]
Head to your [dashboard](https://anyrouter.dev/dashboard/keys) and create a new API key. It is prefixed with `sk-ar-`. Export it in your shell so OpenCode can interpolate it:
```bash
export ANYROUTER_API_KEY="sk-ar-your-anyrouter-key"
```
Put the same line in `~/.bashrc` or `~/.zshrc` so it persists across shells.
### Step 2 — Add AnyRouter as a custom provider [#step-2--add-anyrouter-as-a-custom-provider]
Create or edit `~/.config/opencode/opencode.json` (global) or `./opencode.json` (this project):
```json
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"anyrouter": {
"npm": "@ai-sdk/openai-compatible",
"name": "AnyRouter",
"options": {
"baseURL": "https://anyrouter.dev/api/v1",
"apiKey": "{env:ANYROUTER_API_KEY}"
},
"models": {
"anthropic/claude-sonnet-4-6": {
"name": "Claude Sonnet 4.6",
"limit": { "context": 200000, "output": 65536 }
},
"z-ai/glm-5.2": {
"name": "GLM-5.2",
"limit": { "context": 1000000, "output": 65536 }
}
}
}
},
"model": "anyrouter/anthropic/claude-sonnet-4-6"
}
```
Key fields:
* `npm: "@ai-sdk/openai-compatible"` — AnyRouter speaks Chat Completions at `/api/v1`.
* `options.baseURL` — must end in `/v1`.
* `options.apiKey` — `{env:ANYROUTER_API_KEY}` is the environment variable **name**, not the secret. Do not paste the `sk-ar-` key into this file.
* `models` keys — exact AnyRouter model ids; OpenCode sends that string as `model` so the gateway can route.
* `limit` — if present, OpenCode requires **both** `context` and `output` (context-only JSON fails validation). Omit `limit` entirely if you don't know the windows.
* `model` — `anyrouter/` selects the default (`provider/model-id`).
### Step 3 — Run OpenCode [#step-3--run-opencode]
```bash
opencode
```
Use `/models` in the TUI to pick any id you listed. Add more from the [Chat Completions catalog](https://anyrouter.dev/models?endpoint=chat-completions).
:::warning
`{env:ANYROUTER_API_KEY}` only resolves if the variable is in the process that launches OpenCode. Desktop launchers that do not inherit your shell rc will see an empty key — export it in the launcher, or run `opencode` from a terminal where `echo $ANYROUTER_API_KEY` prints a value.
:::
## MCP tools [#mcp-tools]
`anyr opencode` connects the AnyRouter MCP gateway for you when **Auto-connect in coding agents** is on in the [dashboard](https://anyrouter.dev/dashboard/mcp). It uses the same key the session launches with, so there is nothing to paste. To turn it off, pass `--no-mcp` on a launch, run `anyr mcp auto off`, or switch it off in the dashboard.
## Verify [#verify]
Confirm requests are flowing through AnyRouter:
* Run a prompt in OpenCode (`anyr opencode` or `opencode`) and check that it responds.
* Open your [request logs](https://anyrouter.dev/dashboard/logs) — the call should appear within a second or two with the model name, token counts, and cost.
* With the CLI, `anyr opencode --dry-run` prints the resolved command and the inline provider config (the key is never embedded — only `{env:OPENAI_API_KEY}`).
## Troubleshooting [#troubleshooting]
:::details{summary="401 or authentication errors"}
Your key isn't reaching OpenCode. In persistent config, confirm `echo $ANYROUTER_API_KEY` prints a live `sk-ar-` key. With the CLI, run `anyr config get` to confirm a key is stored, or re-run `anyr setup`.
:::
:::details{summary="Config is invalid / missing limit.output"}
OpenCode 1.18+ requires both `limit.context` and `limit.output` if you set `limit` at all. Add `"output": 65536` (or omit the whole `limit` object). The AnyRouter CLI writes both automatically.
:::
:::details{summary="Model not found"}
The models-map **key** must be an exact AnyRouter id (`anthropic/claude-sonnet-4-6`, not `claude-sonnet-4-6`). Browse [Chat-Completions-compatible models](https://anyrouter.dev/models?endpoint=chat-completions). The default `model` field is `anyrouter/`.
:::
:::details{summary="Requests still hit OpenCode Zen or another provider"}
The CLI session injects AnyRouter only for that process. A global `opencode.json` that still points at Zen, or a `/connect` credential for another provider, can win when you launch `opencode` yourself. Check `~/.config/opencode/opencode.json` and the `/models` picker — the AnyRouter models are namespaced `anyrouter/…`.
:::
## Supported models [#supported-models]
Browse every model OpenCode can call by filtering the catalog to Chat-Completions-compatible models: [anyrouter.dev/models?endpoint=chat-completions](https://anyrouter.dev/models?endpoint=chat-completions).
---
# Plan and implement with anyr task (/cli/task)
`anyr task` runs your request through two models in sequence. First a **planning model** writes a numbered step-by-step plan. Then an **implementation model** executes that exact plan. Both phases stream to your terminal, and a token summary prints at the end.
Pairing a strong planner with a fast, cheap implementer gets you most of the quality of the strong model at a fraction of the cost.
```bash
anyr task "write a script that dedupes my downloads folder"
```
## Choosing the models [#choosing-the-models]
By default both phases use your profile's default model. Override either phase:
```bash
anyr task "refactor this CSV parser to stream instead of buffering" \
--plan-model openai/gpt-5.2 \
--do-model z-ai/glm-4.7-flash
```
* `--plan-model ` — the model that writes the plan. Pick a strong reasoning model.
* `--do-model ` — the model that follows the plan. A fast model works well here, because the hard thinking already happened.
Any model id from [the catalog](https://anyrouter.dev/models) works. Run `anyr models` to list them.
## Confirmation and cost [#confirmation-and-cost]
Before running, the CLI shows both models with their per-token prices from the catalog and asks for confirmation — a task run makes two model calls.
* In an interactive terminal: review the line, press Enter to run.
* In scripts, pipes, or CI the prompt cannot be shown, so pass `--yes` explicitly.
## Scripting with --json [#scripting-with---json]
`--json` suppresses streaming and prints a single JSON object with the plan, the result, and per-phase token usage:
```bash
anyr task "summarize RFC 8628 in five bullets" --yes --json | jq -r .result
```
The object contains `task`, `plan_model`, `do_model`, `plan`, `result`, and `usage.plan` / `usage.implement` with prompt and completion token counts.
## Options [#options]
| Flag | Meaning |
| -------------------- | --------------------------------------------------------------------- |
| `--plan-model ` | Model for the planning phase (default: profile default model) |
| `--do-model ` | Model for the implementation phase (default: profile default model) |
| `--yes` | Skip the confirm prompt (required in non-interactive shells) |
| `--json` | Print `{plan, result, usage}` as JSON instead of streaming |
| `--profile ` | Use a named profile |
| `--key sk-ar-v1-...` | Use an explicit key (or the `ANYROUTER_API_KEY` environment variable) |
Signed out? Run `anyr auth login` first — see [Login with AnyRouter](/cli/login).
---
# Usage, logs & transactions (/cli/usage)
Three read-only commands surface your account activity without leaving the terminal. They authenticate with the same key your launches use — no extra setup after [login](/cli/login).
## Credits & spend [#credits--spend]
```bash
anyr usage
```
```text
Credits remaining $18.42
monthly $16.00 · top-up $2.42
Spend (24h) $1.37
Lifetime used $103.11
Top models
anthropic/claude-sonnet-4-6 $0.98 · 41 req
z-ai/glm-4.7 $0.39 · 87 req
```
The 24h spend and top-models lines are aggregated from your recent request logs. On a very busy day the CLI bounds how far it crawls and shows the number as a floor (`≥ $…`). If the network is down, the last cached snapshot prints instead, clearly marked `(cached · 2h ago)`. Pass `--no-detail` to skip the log aggregation and make exactly one API call.
## Request logs [#request-logs]
```bash
anyr logs # latest 20 requests, newest first
anyr logs --limit 50 # 1 to 100 per call
anyr logs --status error # ok | error (the error code shows inline)
anyr logs --model z-ai/glm-4.7
anyr logs # one request in detail
anyr logs sessions # requests grouped by session
```
Columns: time (your local zone), model, provider, status, tokens in and out, cost, and latency. History depth follows your plan's log-retention window.
## Credit ledger [#credit-ledger]
```bash
anyr transactions # newest first, 20 rows
anyr transactions --type purchase
anyr transactions --limit 100
```
Every credit movement, such as top-ups, per-request spend and refunds, with the signed amount, your balance after it (when the entry has one) and a note.
## Scripting [#scripting]
All three commands accept `--json` for machine-readable output, print plain text with no colors when piped, and exit non-zero with a one-line reason on failure:
```bash
anyr usage --json | jq .balance
anyr logs --json --status error | jq '.data | length'
anyr transactions --json | jq '.data[0]'
```
Errors print `error: ` on stderr; with `--json` they print as one JSON object instead. The exit code tells you what went wrong: 1 error, 2 usage, 3 not found, 4 no key or a key without the needed permission (the message names the missing scope), 5 rate limited. See [Use anyr from scripts and AI agents](/cli/agents).
A key can also be passed explicitly with `--key` or the `ANYROUTER_API_KEY` environment variable — useful in CI.
---
# AnyRouter CLI (/cli)
Run your coding agents through the AnyRouter gateway with a single command — one key fronts every provider, with automatic failover and unified billing. Install the native `anyr` binary (aliases `anyrouter`, `ar`) with no Node required; first run still configures you once, and every run after that goes straight into the tool.
```bash
curl -fsSL https://anyrouter.dev/setup.sh | bash
anyr claude # Claude Code
anyr codex # Codex
anyr opencode # OpenCode
anyr grok # Grok Build
```
The binary is `anyr` (short alias `ar`). Prefer `anyr` in docs and scripts; `ar` is fine in short examples.
Prefer the [product page](https://anyrouter.dev/cli) for copy-paste commands and a launcher overview. This doc is the full reference.
## Aliases [#aliases]
`setup.sh` installs several names for the same binary — use whichever reads best:
```bash
anyr claude
anyrouter claude
ar claude
```
Older per-tool npm packages (`@anyr/claude`, `@anyr/codex`, and similar) are deprecated. Prefer `anyr ` from the native install above.
## Targets [#targets]
Run `anyr `. There are two kinds.
**Spawn targets** launch the tool as a child process pointed at the gateway:
| Target | Tool |
| ------------------------- | ----------- |
| `claude` (alias `cc`) | Claude Code |
| `codex` | Codex |
| `grok` | Grok Build |
| `opencode` | opencode |
| `pi` | Pi |
| `pool` (alias `poolside`) | Poolside |
**Editor targets** can't be launched from a terminal, so the CLI prints a copy-paste block for the editor's OpenAI-compatible provider settings: the base URL (`https://anyrouter.dev/api/v1`) and a `$ANYROUTER_API_KEY` placeholder. Replace the placeholder with your `sk-ar-` key from the [Keys dashboard](https://anyrouter.dev/dashboard/keys) — the CLI never prints your key:
| Target | Editor |
| ---------- | -------- |
| `cursor` | Cursor |
| `cline` | Cline |
| `windsurf` | Windsurf |
The CLI never edits a tool's own config files or stores credentials inside them. For spawn targets it sets the gateway base URL and auth token **only in the environment of the child process it launches** — quit the tool and nothing about your global setup has changed.
## What you see [#what-you-see]
Run a spawn target with no flags and the CLI shows an interactive launcher:
```text
▲ AnyRouter v0.2.x
account default · sk-ar-v1-ab…yz · render sync-gpu
[ Claude Code ] Codex Grok Build OpenCode Poolside Chat
────────────────────────────────────────
Model auto
Effort —
Preset @preset/coding-stack
API key sk-ar-v1-ab…yz
────────────────────────────────────────
◆ ▶ Launch Claude Code ↵ ‹ ›
Full info
↑↓ move ←→ agent ↵ select m model ? help q quit
```
Use arrow keys to navigate and Enter to select. Press `x` or `q` to exit without launching. GPU-class terminals (Kitty, Ghostty, WezTerm, iTerm) redraw without flicker.
## Chat [#chat]
```bash
anyr chat
anyr chat --model z-ai/glm-5.2
```
Opens a streaming chat TUI against the gateway. Slash commands: `/help`, `/model [id]`, `/clear`, `/exit`. Esc clears the input; Ctrl-C quits.
## What it does [#what-it-does]
The AnyRouter CLI is a thin launcher. It never changes how your tools work — it just injects the right environment so each one talks to AnyRouter:
| Tool | Base URL env | Auth env | Model env |
| ----------- | ------------------------- | ----------------------- | ------------------------- |
| Claude Code | `ANTHROPIC_BASE_URL` | `ANTHROPIC_AUTH_TOKEN` | `ANTHROPIC_MODEL` |
| Codex | `OPENAI_BASE_URL` | `OPENAI_API_KEY` | `OPENAI_MODEL` |
| Grok Build | `GROK_MODELS_BASE_URL` | `GROK_CODE_XAI_API_KEY` | pick in-CLI with `Ctrl+M` |
| Gemini CLI | `GOOGLE_GEMINI_BASE_URL` | `GEMINI_API_KEY` | `GEMINI_MODEL` |
| OpenCode | `OPENCODE_CONFIG_CONTENT` | `OPENAI_API_KEY` | inline `model` |
OpenCode does not rely on flat `OPENAI_BASE_URL` / `OPENAI_MODEL` — the CLI injects a custom AnyRouter provider via `OPENCODE_CONFIG_CONTENT` so context windows size correctly. `OPENAI_API_KEY` stays set so `{env:OPENAI_API_KEY}` in that config resolves. See [OpenCode setup](/cli/opencode).
Use `--dry-run` to see exactly what gets set before launching.
**Grok Build** reads its base URL from `GROK_MODELS_BASE_URL` and has no model env var — choose a model from the in-session picker (`Ctrl+M`), which lists the full AnyRouter catalog. If you previously ran `grok login`, run `grok logout` first; an existing session takes precedence over the gateway key.
## Install [#install]
Primary path is a native Rust binary from public [GitHub Releases](https://github.com/anyrouter-dev/cli/releases) — Linux (x86\_64, arm64), macOS (Intel, Apple Silicon), and Windows x86\_64. No Node required.
```bash
curl -fsSL https://anyrouter.dev/setup.sh | bash
anyr auth login # browser, device code, or paste (`login` still works)
anyr claude # launch Claude Code through the gateway
anyr claude --yolo --model anyrouter/auto[1m] # auto-route ≥1M context
anyr claude --model anyrouter/auto[500k] # auto-route ≥500k context
anyr claude --install --ok # install Claude Code first if it is missing
anyr upgrade # pull the latest GitHub Release
anyr upgrade --check --channel beta
```
Install and docs: [anyrouter.dev/cli](https://anyrouter.dev/cli).
Source: [github.com/anyrouter-dev/cli](https://github.com/anyrouter-dev/cli).
## First run [#first-run]
The first time you run a launcher or `auth login` with no saved config, the CLI signs you in automatically — no multi-option menu:
::::steps
:::step{title="Sign in"}
With a browser, the CLI opens anyrouter.dev, you approve, and a key is returned to the terminal. Over SSH, in CI, or if the browser handoff fails, it switches to the **device code** flow. Force a path with `--device`, `--paste`, or `--key sk-ar-...` — see [Login with AnyRouter](/cli/login).
:::
:::step{title="Optional defaults"}
Interactively, you can pick a **default model** (top-ranked catalog first) and a **default coding agent**. Pass `--yes` to skip. Choices are saved under your profile.
:::
:::step{title="Your profile is saved"}
Config is written to `~/.anyrouter/config.yaml` (key on the macOS keychain when available) and reused automatically next time.
:::
::::
Non-interactive environments (CI, no TTY) never prompt — pass `--key sk-ar-...` or set `ANYROUTER_API_KEY`. Use `--device` / `--device-code` for headless sign-in when a human can approve a code on another device.
## Shared config [#shared-config]
All launchers share `~/.anyrouter/config.yaml`, so you log in once and every tool reuses the same key. (A profile from an older CLI at `~/.config/anyrouter/config.yaml` is migrated forward automatically on first run.)
```yaml
active_profile: default
profiles:
default:
base_url: https://anyrouter.dev/api
pinned_preset: "@preset/coding-stack"
default_model: auto
timeout_ms: 3000000
tools:
claude:
command: claude
base_url_env: ANTHROPIC_BASE_URL
auth_env: ANTHROPIC_AUTH_TOKEN
model_env: ANTHROPIC_MODEL
codex:
command: codex
base_url_env: OPENAI_BASE_URL
auth_env: OPENAI_API_KEY
model_env: OPENAI_MODEL
grok:
command: grok
base_url_env: GROK_MODELS_BASE_URL
auth_env: GROK_CODE_XAI_API_KEY
```
## MCP in launched agents [#mcp-in-launched-agents]
`anyr claude` connects the AnyRouter MCP gateway for you when **Auto-connect in coding agents** is on in the [dashboard](https://anyrouter.dev/dashboard/mcp). It uses the same key the session launches with, so there is nothing to paste. To turn it off, pass `--no-mcp` on a launch, run `anyr mcp auto off`, or switch it off in the dashboard. The same applies to `anyr codex`, `anyr opencode`, and `anyr grok`. Pi has no MCP support, so nothing is connected there.
## Management commands [#management-commands]
Beyond the launch targets, the CLI has a handful of management commands:
```bash
anyr auth login # sign in (browser → device code fallback)
anyr auth logout
anyr auth status
anyr config # interactive TUI: key, credits, model
anyr usage # credits remaining, 24h spend + top models, lifetime used
anyr logs # recent requests: status, model, tokens, cost, latency (also , sessions)
anyr transactions # credit ledger: top-ups, spend, refunds
anyr whoami # name, email, account, masked key, default model, config path
anyr account list # multiple accounts (* = active)
anyr account use work # switch saved profiles
anyr keys list # switch with: anyr keys use
anyr models # list catalog model ids for --model
anyr models --free # $0 models only (also --type, --owner, --search, --sort)
anyr models show # pricing, context, modalities, providers
anyr models use # persist default_model
anyr embed "hello" # embeddings from the terminal (args, --file or stdin)
anyr byok providers # providers that accept your own keys (also add, test, rm)
anyr upgrade # install the latest GitHub Release
anyr audit # effective config, key storage, injected env (redacted), launch history
anyr setup # save a local profile (same auto sign-in)
anyr config get # show the active config (secrets redacted)
anyr config path # print the config file location
anyr config use work # switch to the "work" profile
anyr skills sync # sync your Skills & Knowledge Hub locally
```
## Browse models [#browse-models]
```bash
anyr models --free # $0 models only
anyr models --type embedding # chat | embedding | decision
anyr models --owner nvidia --sort price # sort: price | context | new
anyr models --search glm
anyr models show z-ai/glm-4.7 # pricing per 1M tokens, context, modalities, providers
```
Filtered lists show the id, type, context, price per 1M input and output tokens, and whether the model is free. A model counts as free when its platform price is $0; models you pay for through your own provider key never do. `--json` prints the same list as JSON.
## Embeddings [#embeddings]
```bash
anyr embed "hello world" # default embedding model: dimensions + first 8 values
anyr embed --model "hello"
anyr embed --file notes.txt # one input per line, sent as one request
echo "hello" | anyr embed - # read inputs from stdin
anyr embed --dimensions 256 "hello"
anyr embed --json hi | jq '.data[0].embedding | length'
```
The text output shows the model, input count, dimensions, tokens, cost and a shortened vector per input. `--json` prints the full response, ready to load into a vector store.
## Skills & Knowledge Hub [#skills--knowledge-hub]
`anyr skills` downloads the skills, knowledge, and plugins from the hubs your account or org can see into `~/.anyrouter/hubs//{skills,plugins}`. Load one at launch with `--hub`:
```bash
anyr skills list # hubs + items available to you
anyr skills sync # sync every hub you can see
anyr claude --hub myhub # launch Claude Code with that hub loaded
```
When you pass `--hub`, the CLI loads the hub via `claude --plugin-dir` — its skills plus any plugins — without touching your own `~/.claude/skills`. See the [Skills & Knowledge Hub](/hub) guide for details.
## Related [#related]
* [Claude Code setup](/cli/claude-code) — one command or manual env vars
* [Codex setup](/cli/codex) — one command or manual env vars
* [OpenCode setup](/cli/opencode) — one command or `opencode.json` provider
* [Grok Bot, Herdr, and Anyr CLI](/guides/grok-bot-herdr-anyr) — fleet coding with Herdr spaces
* [Configuration reference](/cli/configuration) — `anyrouter.yaml` fields, JSON schema, profiles
* [Login with AnyRouter](/cli/login) — auto browser sign-in, device code, `--paste` / `--key`
* [Grok Build](https://docs.x.ai/build/overview) — install with `curl -fsSL https://x.ai/cli/install.sh | bash`, then `anyr grok`
* [Skills & Knowledge Hub](/hub) — sync skills and plugins into any session
---
# Account Settings (/features/account-settings)
Your account is the root of everything you do on AnyRouter — the keys you mint, the organizations you belong to, the credits on your balance, and the logs of every request. If you lose access to it, or if a stale session lingers on a device you no longer control, all of that is exposed. Account settings is where you keep that root secure: manage your profile, add multiple ways to sign in, verify email addresses, and revoke sessions the moment something looks wrong.
## Overview [#overview]
Everything lives on one page, [`/dashboard/settings`](https://anyrouter.dev/dashboard/settings). A list on the left (a row of links at the top on a phone) jumps to each section: Profile, Public profile, Sign-in & security, Connected accounts, Key safety, Management keys, Routing, Privacy & guardrails, Notifications, Organizations, and the Danger zone last. Each section has its own link, for example [`/dashboard/settings#privacy`](https://anyrouter.dev/dashboard/settings#privacy).
The **Edit profile** button (and **Manage** beside each account section) opens the full editor, where you change your name, manage emails, add authentication methods, sign sessions out, and delete the account.
Profile and field-level changes save as soon as you confirm the field — there is no separate "save" button. Leaving a field commits the change.
## How it works [#how-it-works]
### Profile [#profile]
Your profile is the identity AnyRouter shows alongside your usage logs, organization memberships, and team-facing surfaces.
* **Name.** Shown in the dashboard header, org member lists, and audit entries. Changes propagate immediately but do not rewrite past audit logs.
* **Avatar.** Uploaded images are cropped to a square. If you sign in with an OAuth provider that supplies a photo (e.g. Google), that photo is used until you upload your own.
* **Username.** Optional; appears as `@your-name` on the profile card. Not used for authentication — you always sign in with email, an OAuth provider, or a passkey.
* **Primary email.** The single address used for billing receipts, security alerts, and account recovery (see [Email addresses](#email-addresses)).
### Authentication methods [#authentication-methods]
AnyRouter supports several sign-in methods on the same account; any one is enough to get in. Add more to reduce lock-out risk.
| Method | How it works | Notes |
| ------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Password** | Email + password. Add, change, or remove from the account editor. | Minimum length enforced; breached passwords rejected. Removing your only method is blocked. Use *Forgot password* to reset via your primary email. |
| **Magic link** | AnyRouter emails a one-time sign-in link. | Available automatically once you have a verified email — no toggle. Links expire quickly and are single-use. |
| **OAuth providers** | Sign in with Google, GitHub, or Microsoft. | Connect from *Connected accounts*. AnyRouter requests only email, name, and avatar — never inbox, repos, or calendar. Cannot disconnect your only method. |
| **Passkeys** | WebAuthn (Face ID, Touch ID, Windows Hello, hardware key, password manager). | Phishing-resistant. Name each passkey (e.g. *MacBook Pro*). Add at least two so losing one device doesn't lock you out. |
Passkeys are the modern, phishing-resistant alternative to passwords, and pair well with magic links for a convenient-yet-secure setup. Adding two passkeys — say your laptop and your phone — means losing one device never locks you out.
### Email addresses [#email-addresses]
Your account can hold multiple emails. Exactly one is the **primary**; the rest are **verified secondaries**.
The **primary email** is used for sign-in (magic links, password resets), security notifications (new-device sign-ins, password and auth-method changes), billing (invoices, receipts, balance alerts), and account recovery. **Secondary emails** are verified addresses linked to your account — useful for receiving magic links at both a work and personal address, or for linking OAuth providers whose email differs from your primary. Secondaries do **not** receive billing or security notifications; promote one to primary to move those.
### Active sessions and devices [#active-sessions-and-devices]
A session is created each time you sign in on a browser or device, and keeps you signed in across reloads. The *Active sessions* list shows every browser and device holding a valid session:
* **Device** — OS and browser (e.g. *macOS · Safari*).
* **Location** — approximate city and country from the sign-in IP.
* **Last active** — the most recent request from this session.
* **Current session** — the one you're using now, labelled and not revocable without signing yourself out.
API keys are **not** affected by session revocation — they are separate credentials with their own lifecycle. Signing out of all sessions does not rotate your keys. To rotate keys, go to the [API keys page](https://anyrouter.dev/dashboard/keys).
### Two-factor authentication [#two-factor-authentication]
If you sign in with a password, we strongly recommend a second factor. 2FA requires a one-time code from an authenticator app (1Password, Authy, Google Authenticator) in addition to your password.
* **Enable** — scan the QR code and enter the 6-digit code. AnyRouter shows one-time backup codes; store them safely. Each backup code works once.
* **Disable** — requires a current 2FA code.
* **Regenerate backup codes** — invalidates the previous set.
If you use a passkey, 2FA is not required — passkeys already combine "something you have" (the device) with "something you are" (biometric) into a single phishing-resistant step.
### Email preferences [#email-preferences]
AnyRouter sends account emails (welcome, plan and credit notices) and, separately, a periodic usage-digest email. There's no in-dashboard toggle for these today — each email carries a one-click **Unsubscribe** link in its footer that either opts you out of that specific email type or out of all non-essential AnyRouter email. Security and billing-critical messages (receipts, new-device sign-in alerts) are not affected by unsubscribing.
### Connected accounts [#connected-accounts]
This section lists the OAuth providers linked to your account, each showing the provider name and the email it identifies you by. **Disconnect** removes the link; the next sign-in with that provider starts a fresh consent flow. Connecting a provider that returns an already-linked email replaces the old token silently — no duplicates.
## Configure [#configure]
### Add an email address [#add-an-email-address]
1. Click *Add email* and enter the address.
2. AnyRouter sends a verification link — open the email and click it.
3. Once verified, the address appears in your secondary emails.
Unverified emails cannot be used for sign-in or promoted to primary, and are removed automatically after a few days if not verified.
### Promote or remove an email [#promote-or-remove-an-email]
* **Make primary** — click *Make primary* next to a verified secondary. Billing and security notifications immediately move to it; the previous primary is demoted to secondary and stays verified.
* **Remove** — click the trash icon. You cannot remove the primary (promote a different address first). Removing an email also disconnects any OAuth provider linked through it.
### Revoke a session [#revoke-a-session]
* **Single session** — click *Sign out* on any entry to invalidate it immediately. The next request from that browser or device is rejected. Use this for a session you don't recognize, a public computer you forgot to sign out of, or a lost device.
* **Everywhere** — *Sign out of all other sessions* revokes every session except your current one. The fastest way to recover from a suspected compromise; pair it with rotating your password or adding a passkey.
### Delete your account [#delete-your-account]
Deleting your account is permanent — no undo, no soft-delete window.
1. Open the *Account* page.
2. Scroll to the *Danger zone* at the bottom.
3. Click *Delete account*.
4. Re-enter your password (or complete a passkey challenge).
5. Type the confirmation phrase exactly as shown.
You'll be signed out immediately and the account becomes unrecoverable.
**What gets deleted immediately:** your profile (name, avatar, username) and all email addresses; all authentication methods (password, passkeys, OAuth links, 2FA, backup codes); all API keys you personally own (active requests begin failing with `401 Unauthorized` within seconds); all presets you authored (anyone referencing `@preset/` gets "preset not found"); all conversation history and usage logs scoped to your user; any BYOK credentials (also revoke them on the upstream provider); and any unspent personal credits (non-refundable on deletion).
**Organization memberships:** if you are a regular **member**, you are removed and the organization is unaffected. If you are the **sole owner** of an organization, deletion is blocked until you transfer ownership or delete the organization — the UI tells you which orgs need attention (see [Organizations](/features/organizations)). Organization-owned keys, presets, credits, and logs always stay with the organization.
---
# AnyRouter Agent (/features/anyrouter-agent)
**AnyRouter Agent** is a free on-site helper on [anyrouter.dev](https://anyrouter.dev). Open the chat icon in the header (tooltip **Ask AnyRouter**), or go to [/agent](https://anyrouter.dev/agent).
It is not human Support. For billing lockouts or outages, use the [status page](https://anyrouter.dev/health) and [docs](/) — the copilot will say so.
## What it can do [#what-it-can-do]
Without signing in:
* Search [docs](https://docs.anyrouter.dev) (including `llms.txt` / per-page markdown)
* Look up public models and pricing
* Fetch public AnyRouter pages (docs, changelog, status)
* Report platform status
When you ask about your own account, the copilot shows **Allow account access**. After you allow it and sign in on anyrouter.dev:
* Your plan and credit balance
* Your API keys as prefix / last characters only (never the full `sk-ar-v1-` secret)
* Recent usage summaries
* Create or revoke a key — only after you confirm a card in the chat. The server rejects writes without that confirmation.
## Privacy [#privacy]
The rail stays closed until you open it. Closed means no prompt is sent.
Anonymous chats use an isolated session cookie. After opt-in, the session is bound to your signed-in account. You can revoke account access from the chat footer.
The copilot redacts pasted API keys (`sk-…`, `ak_…`) before they reach a model.
## Models [#models]
Visitor chat is served by AnyRouter with free **text** models. The composer shows the most-used ones first; search to pick any free-tier text model. Default is `deepseek/deepseek-v4-flash`. That site chat does **not** use the `anyrouter/free` API id.
Calling free models **from your own API key** still follows the [free tier](/features/free-tier) rules (Go plan or donate).
Each reply shows the model that answered. You can copy the text or rate it with thumbs up or down.
## Limits [#limits]
Anonymous: 100 new threads per day per IP, 180 messages per hour per IP, and a 30 requests / minute burst on the chat endpoint. Signed-in: 1000 messages per day. The widget returns `429` with `Retry-After` when a limit is hit.
---
# Managing API Keys (/features/api-keys)
A single shared secret is a liability: you can't tell which app spent what, a leak forces you to break every client at once, and there's no ceiling on the damage a runaway loop can do. AnyRouter API keys fix that — each key is an independent `sk-ar-…` credential with its own name, usage history, expiration, rate limit, and spending cap, so you can scope, monitor, and revoke access one application at a time.
## Overview [#overview]
The **API Keys** page (`https://anyrouter.dev/dashboard/keys`) is where you create, configure, monitor, and revoke the credentials your applications use to call the AnyRouter API. You can hold as many keys as you need — there is no per-account limit — and every key carries its own configuration and analytics.
* For request authentication mechanics, see [Authentication](/getting-started/authentication).
* For the underlying REST endpoints, see the [Keys API reference](/api-reference/keys).
AnyRouter never persists the plaintext form of a key — only a one-way hash used to verify requests. The full secret is shown **once**, at creation. If you lose it there is no recovery path: create a new key and revoke the old one.
## How it works [#how-it-works]
### Where to find your keys [#where-to-find-your-keys]
Open **Keys** in the dashboard sidebar. The page lists every key on your account, including disabled ones, with a card-and-table toggle in the top right. Each key card shows:
* The friendly name you gave the key
* Last-14-day request count and estimated spend
* The "last used" timestamp
* Quick links to that key's logs and usage charts
* An overflow menu (`⋯`) for edit, rename, and delete
Click any card to open the **single-key detail page** at `/dashboard/key/`, which shows the full configuration, recent spend breakdown, and per-model activity.
### The "shown once" rule [#the-shown-once-rule]
Because AnyRouter stores only a hash, treat every secret as unrecoverable the moment you close the reveal dialog:
* Copy the secret to your secret manager immediately after creation.
* Do not paste API keys into chat, email, or screenshots.
* Do not commit keys to source control, even on private branches.
* If you suspect a leak, treat it as confirmed and rotate immediately (see [Rotate without downtime](#rotate-without-downtime)).
The masked prefix on the keys page (e.g. `sk-ar-7d2f***`) is safe to share when coordinating with teammates or filing support tickets — it identifies the key without exposing the secret.
### Configuration fields [#configuration-fields]
The create dialog (and the edit dialog) expose these controls.
| Field | What it does |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Name** | Internal-only label, never sent with requests. We recommend one name per `(app, environment)` — e.g. `web-app-prod`. |
| **Expiration** | `Never` (default), quick-picks (`7 days`, `30 days`, `90 days`), or an exact date-time. After expiry the key returns `401 Unauthorized` until replaced. |
| **Rate limit (RPM)** | Caps requests per minute at the AnyRouter edge. Requests over the cap return `429 Too Many Requests` with a `Retry-After` header. |
| **Credit limit** | Spend ceiling. Choose a dollar **Limit** and a **reset cadence** (`Daily`, `Weekly`, `Monthly`). The cap is measured over a rolling window, so a key left with no cadence is still measured over 30 days like `Monthly` — it is not a lifetime cap. Over the limit, requests return `402 Payment Required` until the window rolls. |
| **Include BYOK usage** | Off by default. On, BYOK-routed requests count toward this key's credit limit even though they don't consume AnyRouter credits — useful for team-wide spend budgeting. |
There is no token-per-minute (TPM) cap on individual keys — TPM is enforced upstream per model. If you need a TPM-style cap, set a tight RPM and pair it with a credit limit.
### Per-key analytics [#per-key-analytics]
Opening a key's detail page (`/dashboard/key/`) gives you a dedicated view:
* **Headline metrics** — total spend and request count for the last 24 hours, 7 days, and 30 days; the "last seen" timestamp with relative formatting (`2m ago`); created-at and expires-at.
* **Spend over time** — a bar chart of daily spend for the trailing 14 days. Hover a bar for the exact figure and request count.
* **Top models** — a leaderboard of the models this key called most, with per-model request counts and spend. Use it to spot unexpected model usage, confirm a migration is complete, or find repeated prompts worth [caching](/features/caching).
* **Recent errors** — the most recent failed requests with timestamps, model, status code, and message. The fastest way to debug a key that started rejecting requests (common causes: `402` credit-limit hits, `429` rate-limit hits, revoked downstream credentials).
The **Logs** link at the top jumps to the full [request log](/features/request-logs) filtered to this key.
### Restrictions [#restrictions]
* **Allowed models.** Restrict a key to a subset of the catalog — useful for budget-approved model lists, compliance policies, or a "preview" key that can call experimental models without risking production. A request for a disallowed model returns `403 Forbidden` and is **not** charged.
* **Network restrictions.** IP allow-lists and `Referer` checks are not currently exposed on individual keys. If your security model depends on them, raise it with us. In the meantime, rely on per-key rotation and credit limits, and route outbound traffic through a fixed egress (NAT gateway, VPN) if you need source-IP attribution.
## Configure [#configure]
### Create a key [#create-a-key]
1. Click **Create key** in the top right of the API Keys page.
2. Set the **Name**, and optionally an **Expiration**, **Rate limit**, **Credit limit**, and **Include BYOK usage** (see [Configuration fields](#configuration-fields)).
3. Click **Create key**. The dialog switches to a one-time reveal panel showing the full secret.
4. **Copy the secret now** — the single click-to-copy button puts it on your clipboard. Paste it into your secret manager or `.env` before closing.
After closing, the new key appears in the list with a masked `sk-ar-***` label. The rest of the secret is gone from the dashboard forever.
### Edit a key [#edit-a-key]
Open the overflow menu (`⋯`) and choose **Edit** to change expiration, credit limit, reset cadence, or the BYOK-in-limit toggle. **Rename** is a separate action because it's the most common edit and touches no limits. Changes apply within seconds — no application restart needed.
The one thing that cannot be changed on an existing key is the owner or workspace
the key belongs to. To move a key between those, issue a new one.
### Rotate a key in place [#rotate-a-key-in-place]
Use **Rotate** when a key's secret needs replacing but its identity should stay
put — a suspected leak, a key that ended up somewhere you don't control, or just
a routine swap. The key keeps its id, name, credit limit, restrictions, usage
history and every dashboard link; only the secret changes. Anything still holding
the old secret starts getting `401 Unauthorized` immediately, with no overlap
window.
The new secret is shown once, right after the rotate, with a copy button. After
that you can still fetch it on demand with **Reveal** (keys created after secure
storage landed), but it is never displayed again on its own.
### Rotate without downtime [#rotate-without-downtime]
In-place rotation is immediate by design — the right behavior for a suspected
leak, but it means any service still holding the old secret starts failing. When
you need a handoff with zero failed requests, run two keys side by side instead.
1. **Issue** a new key with the same configuration (name it `app-prod-v2`).
2. **Deploy** the new key alongside the old one. Most secret managers let two values coexist; if not, deploy the new key first and roll forward.
3. **Verify** the new key is serving production traffic — the card's "Last used" timestamp updates within seconds of the first request.
4. **Revoke** the old key once its traffic drops to zero. Wait at least one full request cycle (typically a few minutes) before deleting.
This sequence guarantees there is never a moment when neither key is valid, so in-flight requests never hit a `401`. The cost is that the old key stays live for the length of the handoff — exactly the window you do not want if you are rotating because of a leak.
**Compromise rotation.** If you suspect a leak, skip the gradual handoff and revoke immediately. This fails in-flight requests on the leaked key, but every extra second it stays valid is more time for an attacker to drain your credits. Burnt requests are recoverable; drained credits are not.
### Revoke and delete [#revoke-and-delete]
Two ways to take a key out of service:
* **Disable** — flip the toggle in table view or use **Disable** in the overflow menu. The key is preserved but rejects all requests with `401 Unauthorized`, and can be re-enabled later. Good for temporary suspensions or incident response.
* **Delete** — choose **Delete**, confirm the prompt. The key is permanently removed. Historical usage is retained for billing and audit, but the secret can never be re-validated.
Both take effect within seconds at the edge. Already-streaming responses are allowed to complete — we do not tear down in-flight connections — but no new requests are accepted. For an instantaneous kill switch during an active incident, pair **Disable** with a credit-limit drop to `$0.01` so anything that slips through propagation is still caught.
## Best practices [#best-practices]
* **One key per application per environment.** A separate key for each `(app, env)` pair gives per-application spend visibility, blast-radius isolation, and independent rotation. Three apps across two environments means six keys: `web-prod`, `web-staging`, `mobile-prod`, `mobile-staging`, `batch-prod`, `batch-staging`.
* **Never reuse keys across environments.** A shared key means a misconfigured staging deploy can drain production credits, and a leaked staging key compromises production. A second key costs nothing.
* **Set a credit limit on every key.** Pick a comfortable ceiling (e.g. 3× current peak) with a monthly reset. You won't notice it in normal operation, but you'll be glad it's there the day a bug spawns an infinite loop.
* **Monitor spend trends.** The per-key spend chart shows 14 days. A step-change that doesn't match a deploy is worth investigating before the bill arrives. For automated alerting, poll the [Keys API](/api-reference/keys) daily.
* **Alert on sudden spend.** Combine a hard credit limit (`$200/mo`) with a tighter soft alert (`$150`) — the limit catches catastrophic runaway, the alert catches gradual drift.
* **Keep management keys separate.** A small number of accounts have **Management** keys (labelled with a **Management** badge) that call administrative endpoints. Treat them like root credentials: one per administrator, never shared, short rotation cadence, never used by an application, and stored in a separate vault.
* **Off-board promptly.** When a teammate leaves, revoke their management key the same day and rotate or transfer any application keys they created.
## Frequently asked questions [#frequently-asked-questions]
:::details{summary="Why is my last-used timestamp not updating?"}
Verify the application sends the `Authorization: Bearer sk-ar-…` header on every request, and that the prefix matches the dashboard. A mismatched prefix means the request is hitting a different key — check for typos and stray whitespace.
:::
:::details{summary="Why am I getting 401 after a successful create?"}
The key was likely disabled, expired, or deleted — check its status. Also confirm you copied the full secret; truncated secrets fail validation silently.
:::
:::details{summary="Why am I getting 402 mid-month?"}
The key hit its credit limit. Raise the limit in **Edit**, lower the reset cadence, or wait for the reset.
:::
:::details{summary="Why am I getting 429 at modest volumes?"}
Check the key's per-minute rate limit in **Edit**. AnyRouter also enforces account-wide and per-model rate limits — the key cap is one of several gates.
:::
:::details{summary="Can I share a key across two workspaces?"}
No. Keys are owned by exactly one workspace. Create one key per workspace.
:::
---
# App Attribution (/features/app-attribution)
By default your API traffic is anonymous — AnyRouter can't tell which of your apps made which call, and none of it surfaces publicly. **App attribution** ties each request to the app that made it, so your app earns a spot in AnyRouter's public rankings and model leaderboards, and you get per-app usage analytics. It's driven by one header you probably already send — `HTTP-Referer` — plus optional `X-AnyRouter-*` headers for display metadata and marketplace categories.
## Overview [#overview]
Send `HTTP-Referer` on your requests and AnyRouter creates an app page keyed to your origin. Add the optional headers to control the display name, platform, version, and marketplace categories.
When your app is attributed:
* **Public app rankings** — your app appears on the [rankings page](https://anyrouter.dev/data/apps) with daily, weekly, and monthly views, sorted by total token consumption.
* **Top apps on model pages** — once your app clears the [listing thresholds](#get-your-app-listed), it appears in the "Top apps" strip on the pages of the models it uses.
* **Per-app analytics** — model-usage breakdowns, token consumption, and adoption metrics scoped to your app.
* **Marketplace visibility** — categorized apps surface in topic pages for discovery.
`HTTP-Referer` is required. Without it, no app page is created and your usage never appears in any leaderboard.
## How it works [#how-it-works]
### The `HTTP-Referer` identifier [#the-http-referer-identifier]
`HTTP-Referer` identifies your app's URL and is the **primary identifier** for rankings. Your normalized Referer URL becomes the app's unique identifier:
* The host is lowercased and the path is dropped — `https://myapp.com/playground` and `https://myapp.com/docs` attribute to the same app `https://myapp.com`.
* The scheme is preserved — `https://myapp.com` and `http://myapp.com` are different apps.
* The port is preserved when non-default — `https://myapp.com:8443` is a distinct app.
#### GitHub Actions [#github-actions]
GitHub Actions is attributed as its own app, not as generic `github.com` traffic — a workflow step that calls AnyRouter won't be grouped with gists, GitHub Apps, or docs pages that happen to share the host.
If you run AnyRouter from a workflow, send the run's repository in the `ref` parameter of the same header:
```
HTTP-Referer: https://github.com/features/actions?ref=https://github.com//
```
Both repositories land on the **same** GitHub Actions app record, and its [app page](https://anyrouter.dev/app?id=github-actions) then breaks the usage down per repository. A GitHub Actions app with per-repository usage is more useful than one app record per repository.
* `ref` is only honored for a GitHub Actions referer.
* Only `https://github.com//` is accepted — a `ref` pointing anywhere else is ignored, never stored.
* Repo names are matched case-insensitively, the way GitHub itself does.
* Repository names are shown publicly on the app page. To keep a private repository's name out of that breakdown, enable **Hide repository names on public app pages** under [Dashboard → Privacy](https://anyrouter.dev/dashboard/settings#privacy) — your usage then counts toward an anonymized "Private repo" row instead, and the toggle hides already-recorded rows too.
**Localhost / loopback URLs.** Apps on `localhost`, `127.0.0.1`, `::1`, or any `*.localhost` host **must also send `X-AnyRouter-Title`** to be tracked — otherwise different developers on the same local port would collapse into one shared app. With a title, the local app is registered under `local://`.
### Optional headers [#optional-headers]
| Header | Purpose |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `X-AnyRouter-Title` | Sets your app's display name in rankings and analytics. Without it, your Referer host is used. |
| `X-Title` | The OpenRouter display-name header, read as a fallback for `X-AnyRouter-Title` so apps migrating from OpenRouter keep attribution with no changes. If both are sent, `X-AnyRouter-Title` wins. |
| `X-AnyRouter-Source` | Free-form platform/channel id (`web-app`, `desktop`, `vscode-extension`). Used for filtering in the dashboard. Not part of the dedup key. Spelling doesn't matter — `Claude Code`, `claude-code` and `claude_code` are recorded as the same app. |
| `X-AnyRouter-App` | Alias for `X-AnyRouter-Source`, for tools that already send this name. `X-AnyRouter-Source` wins when both are present. |
| `X-AnyRouter-Version` | Your app's version string, for comparing usage across releases. Semver (`1.2.3`) recommended. |
| `X-AnyRouter-Categories` | Comma-separated marketplace categories — **up to 2 per request**. Lowercase, hyphen-separated, each ≤30 chars. Unrecognized values are silently ignored. |
Categories are **merged** with whatever your app has accumulated across requests, capped at 10 total. You don't need to resend the full list each time — one or two new categories on a single request is enough to add them.
### Category catalog [#category-catalog]
| Group | Category | For |
| ----------------- | -------------------- | ------------------------------------ |
| **Coding** | `cli-agent` | Terminal-based coding assistants |
| | `ide-extension` | Editor / IDE integrations |
| | `cloud-agent` | Cloud-hosted coding agents |
| | `programming-app` | Programming apps |
| | `native-app-builder` | Mobile and desktop app builders |
| **Creative** | `creative-writing` | Creative writing tools |
| | `video-gen` | Video generation apps |
| | `image-gen` | Image generation apps |
| **Productivity** | `writing-assistant` | AI-powered writing tools |
| | `general-chat` | General chat apps |
| | `personal-agent` | Personal AI agents |
| **Entertainment** | `roleplay` | Roleplay apps and character chat |
| | `game` | Gaming and interactive entertainment |
Only recognized categories are accepted; unrecognized values are silently dropped. If your app doesn't fit, email **[support@anyrouter.dev](mailto\:support@anyrouter.dev)** and we'll evaluate adding it.
## Get your app listed [#get-your-app-listed]
Listing is opt-in, and it has to be deliberate: send **both** headers on your requests.
```
HTTP-Referer: https://yourapp.com
X-Title: Your App
```
`X-Title` is the same display-name header OpenRouter uses, so an app migrating from OpenRouter keeps its listing with no changes. `X-AnyRouter-Title` works too and wins if both are sent. A browser's automatic `Referer` never counts, and neither does a title guessed from your `User-Agent`.
An app shows in a model's **Top apps** strip (last 7 days) when it meets all of these:
* Only requests that sent `HTTP-Referer` **and** `X-Title` (or `X-AnyRouter-Title`) count. Each request counts on its own: one caller sending both headers does not list a host for everyone else who uses it. Requests that sent only one of them, including traffic recorded before you added both, never count.
* It made at least 50 such requests to that model in the window.
* Those requests came from at least 5 different users, so a small team's app is never named.
* Its host is a public domain name. Localhost, IP addresses, and internal hostnames (`.local`, `.internal`, …) are never listed.
Apps without a logo are shown by their origin (for example `yourapp.com`), not by the title you send. To get your display name, logo, and a link to your app page, email **[support@anyrouter.dev](mailto\:support@anyrouter.dev)** to be added to the curated registry.
The same rules decide whether an uncurated app's page (`/app?id=yourapp.com`) shows usage: until your app meets them, its page looks the same as an app that has never been seen.
What is public: the app's origin or curated name, its logo if curated, and its share of the listed apps' tokens. Prompts, completions, API keys, account details, and request counts are never shown. The list refreshes about once an hour.
### Hide a workspace from public listings [#hide-a-workspace-from-public-listings]
To keep a workspace's traffic out of every public app surface (the rankings page, app pages, and the **Top apps** strip on model pages), a workspace owner or admin can turn on **Hide this workspace from public app listings** under [Dashboard → Privacy](https://anyrouter.dev/dashboard/settings#privacy). It applies to traffic already recorded too, and hidden requests no longer count toward any app's listing thresholds. Rankings and app pages update within minutes. Model pages are cached, so the change can take up to an hour to show there. Your own dashboard analytics are not affected.
## Configure [#configure]
Add the attribution headers to your existing request. `HTTP-Referer` is required; the rest are optional display metadata.
```typescript
// TypeScript / JavaScript
fetch('https://anyrouter.dev/api/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': 'Bearer sk-ar-your-key',
'Content-Type': 'application/json',
// Required for attribution
'HTTP-Referer': 'https://myapp.com',
// Optional display metadata
'X-AnyRouter-Title': 'My AI Assistant',
'X-AnyRouter-Source': 'web-app',
'X-AnyRouter-Version': '1.0.0',
'X-AnyRouter-Categories': 'general-chat,personal-agent',
},
body: JSON.stringify({
model: 'google/gemma-4-26b-a4b-it',
messages: [{ role: 'user', content: 'Hello, world!' }],
}),
});
```
```python
# Python
import requests
response = requests.post(
url='https://anyrouter.dev/api/v1/chat/completions',
headers={
'Authorization': 'Bearer sk-ar-your-key',
'Content-Type': 'application/json',
# Required for attribution
'HTTP-Referer': 'https://myapp.com',
# Optional display metadata
'X-AnyRouter-Title': 'My AI Assistant',
'X-AnyRouter-Source': 'web-app',
'X-AnyRouter-Version': '1.0.0',
'X-AnyRouter-Categories': 'general-chat,personal-agent',
},
json={
'model': 'google/gemma-4-26b-a4b-it',
'messages': [{'role': 'user', 'content': 'Hello, world!'}],
},
)
```
```bash
# cURL
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk-ar-your-key" \
-H "HTTP-Referer: https://myapp.com" \
-H "X-AnyRouter-Title: My AI Assistant" \
-H "X-AnyRouter-Source: web-app" \
-H "X-AnyRouter-Version: 1.0.0" \
-H "X-AnyRouter-Categories: general-chat,personal-agent" \
-d '{
"model": "google/gemma-4-26b-a4b-it",
"messages": [
{ "role": "user", "content": "Hello, world!" }
]
}'
```
### Best practices [#best-practices]
* **Always send `HTTP-Referer`** — without it the request is unattributed and your app is invisible to rankings.
* **Pin the Referer to your canonical origin** (`https://yourapp.com`), not a deep page URL — paths are dropped anyway, but a stable origin makes attribution predictable.
* **Send `X-AnyRouter-Title`** for a friendly display name; otherwise the host is shown.
* **Send `X-AnyRouter-Categories`** to surface in marketplace topic pages.
* **For local development**, include `X-AnyRouter-Title` so your loopback app doesn't collide with other developers'.
* **Use semver for versions** — useful for comparing rollouts.
## Frequently asked questions [#frequently-asked-questions]
:::details{summary="Where does my app appear once attributed?"}
On the main rankings page (daily/weekly/monthly, sorted by token consumption) and in the "Top apps" strip of each model page it uses, once it meets the [listing thresholds](#get-your-app-listed).
:::
:::details{summary="My app was logged under two Referer URLs — can I merge them?"}
Yes. If you migrated (e.g. `myapp.com` → `myapp.io`), email **[support@anyrouter.dev](mailto\:support@anyrouter.dev)** with both URLs and we'll merge them into one canonical record. The same address handles splitting an entry if you need the inverse.
:::
:::details{summary="Is any of my request data exposed by attribution?"}
No. Attribution headers carry identity only — they don't expose anything about your request payloads. Your app's Referer, title, and chosen categories are publicly visible in rankings; the prompts and completions are not.
:::
## Related [#related]
* [Quickstart Guide](/getting-started/quickstart) — basic setup with attribution headers.
* [API Reference](/api-reference/overview) — complete header documentation.
* [Request Logs](/features/request-logs) — the **App** column shows your attributed name per request.
---
# AI21 (BYOK) (/features/byok-providers/ai21)
AI21 is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the AI21 console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **AI21** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [AI21 console](https://studio.ai21.com/account/api-key)
4. Click **Save**. AnyRouter runs a live validation call against AI21 before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# AIHubMix (BYOK) (/features/byok-providers/aihubmix)
AIHubMix is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
This provider has a free tier or free credits — a good place to start.
## Get your API key [#get-your-api-key]
1. Sign in to the AIHubMix console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **AIHubMix** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [AIHubMix console](https://aihubmix.com/token)
* **Default base URL** (editable on the key form): `https://aihubmix.com/v1`
4. Click **Save**. AnyRouter runs a live validation call against AIHubMix before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# AI/ML API (BYOK) (/features/byok-providers/aimlapi)
AI/ML API is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the AI/ML API console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **AI/ML API** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [AI/ML API console](https://aimlapi.com/app/keys)
* **Default base URL** (editable on the key form): `https://api.aimlapi.com/v1`
4. Click **Save**. AnyRouter runs a live validation call against AI/ML API before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# AionLabs (BYOK) (/features/byok-providers/aionlabs)
AionLabs is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the AionLabs console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **AionLabs** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against AionLabs before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# AkashML (BYOK) (/features/byok-providers/akash)
AkashML is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the AkashML console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **AkashML** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against AkashML before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Alibaba Cloud Int. (BYOK) (/features/byok-providers/alibaba-cloud)
Alibaba Cloud Int. is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Alibaba Cloud Int. console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Alibaba Cloud Int.** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against Alibaba Cloud Int. before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Amazon Bedrock (BYOK) (/features/byok-providers/amazon-bedrock)
Use Claude from your own AWS account. AnyRouter calls Claude in Amazon Bedrock with your Bedrock API key, so AWS bills you directly.
## Get your API key [#get-your-api-key]
1. In the [AWS console](https://console.aws.amazon.com/bedrock/home#/modelaccess), open **Amazon Bedrock → Model access** and enable the Claude models you want in your region.
2. Open **Amazon Bedrock → API keys** and create a key. Copy it immediately.
3. Paste a JSON object into AnyRouter: `{ "api_key": "", "region": "us-east-1" }`. Use the region where Claude is enabled.
## Gotchas [#gotchas]
Only Bedrock API keys are supported for now. An access key ID with a secret does not work yet. Claude Opus 4.7 and later, Sonnet 5 and later, Haiku 4.5 and later, and Fable are served; Opus 4.6 and Sonnet 4.6 are not. Regional endpoints cost 10% more than global ones on AWS.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Amazon Bedrock** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against Amazon Bedrock before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Anthropic (BYOK) (/features/byok-providers/anthropic)
Anthropic is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Log in to the [Anthropic Console](https://console.anthropic.com/).
2. Open **Settings → API Keys**.
3. Click **Create Key**, name it, and choose workspace permissions.
4. Copy the secret key and paste it into the **Anthropic** card in AnyRouter.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Anthropic** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Anthropic console](https://platform.claude.com/settings/keys)
* **Default base URL** (editable on the key form): `https://api.anthropic.com/v1`
4. Click **Save**. AnyRouter runs a live validation call against Anthropic before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Antigravity (Google) (BYOK) (/features/byok-providers/antigravity)
Antigravity is Google's agentic IDE. It has no API-key console and no developer settings page — there is nothing to sign up for and nothing to copy. The credential it authenticates with lives on the machine where Antigravity is installed, so the `anyr byok` CLI finds it for you, shows you exactly what it found, and uploads it as a BYOK key when you say so.
## Get your API key [#get-your-api-key]
1. On the machine where Antigravity is installed, run `anyr byok locate antigravity` to see what the CLI can find. It is read-only and shows a masked preview.
2. Run `anyr byok add antigravity` to review the credential and a plain description of what will happen — nothing is uploaded until you pass `--yes`.
3. Prefer uploading the full oauth\_creds.json composite (access + refresh) so the Antigravity proxy can renew server-side; access-only keys still use `anyr byok rotate antigravity`.
4. Donating the key to the shared pool is a separate, explicit opt-in (`--donate --donate-yes`, or the toggle in the dashboard) — it never happens as a side effect of adding the key.
## Gotchas [#gotchas]
Antigravity's credential is a short-lived OAuth access token. Prefer uploading the full `oauth_creds.json` composite (access + refresh) via `anyr byok add antigravity` so `anyrouter-antigravity-proxy` can renew the access token server-side (#4212). Access-only keys still expire in about an hour — run `anyr byok rotate antigravity` on the machine that holds the refresh grant (`--dry-run` / `--watch 15m` supported). Rotate never contacts Google and never sends the refresh token; a rejected upload leaves the previous credential in place.
## Where the credential comes from [#where-the-credential-comes-from]
Antigravity does not hand out API keys. When you sign in, its CLI stores a token in your operating system's secure keyring — Apple Keychain on macOS, Secret Service on Linux.
`anyr byok locate antigravity` reads that keyring first (macOS via `security`, Linux via `secret-tool`; other platforms skip straight to the file scan), then falls back to scanning the usual per-OS config folders for installs that do write credential files. Each candidate is reported with where it came from (`keyring` or `file: …`) so you can tell which one is live.
The command is strictly read-only:
* It never writes to or changes your keyring or config files.
* It makes no network calls.
* It prints only a masked preview — the last four characters — never the full token.
* It never waits on a system pop-up. If your keyring is locked, or the platform has no supported backend, it says so and moves on to the file scan.
Not finding anything is a normal outcome, not an error.
## Add the key [#add-the-key]
Review first, with no flags:
```bash
anyr byok add antigravity
```
That is a dry run. It shows the source, the masked preview, and a plain description of exactly what will happen — the value gets encrypted and stored under an Antigravity (Google) key, your own future requests route through it, billed against your own Google quota. Nothing is sent anywhere.
When you are sure, confirm it:
```bash
anyr byok add antigravity --yes
```
AnyRouter runs a live validation call before storing the key. If it fails, you get the upstream error and the key is not saved.
If an Antigravity key already exists, the command stops and tells you rather than silently replacing it. To deliberately put a new value on an existing key, pass `--replace`; the key keeps its ID, label, priority, and pool-donation state.
## Troubleshooting [#troubleshooting]
| What you see | What it means | What to do |
| ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A message about a locked keyring, or no keyring backend | Your operating system's secure store is not readable right now — locked, or a headless session with no unlock prompt. | Unlock your keyring and run `anyr byok locate antigravity` again. It falls back to scanning config folders, so you can also just add the key from the dashboard. |
| `Nothing found` | The credential is in the keyring and not in the scanned folders — usually because Antigravity has never been signed in on this machine, or this install keeps its state somewhere else. | Open Antigravity and sign in once so it mints a token, then run `anyr byok locate antigravity` again. |
| Requests start failing after an hour or so of working fine | The stored copy is a short-lived access token and it has expired. | Run `anyr byok rotate antigravity` to replace the stored value in place. |
| `401` right after adding the key | The value that was picked up was already stale, or it was not the live credential. | Sign in to Antigravity so it mints a fresh token, then run `anyr byok rotate antigravity`. |
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Antigravity (Google)** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against Antigravity (Google) before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Arcee AI (BYOK) (/features/byok-providers/arcee)
Arcee AI is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Arcee AI console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Arcee AI** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against Arcee AI before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# AutoJev (BYOK) (/features/byok-providers/autojev)
AutoJev is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the AutoJev console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **AutoJev** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [AutoJev console](https://autojev.ai/settings/apikeys)
* **Default base URL** (editable on the key form): `https://autojev.ai/api`
4. Click **Save**. AnyRouter runs a live validation call against AutoJev before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Azure Foundry (Claude) (BYOK) (/features/byok-providers/azure-claude)
Use Claude from your own Microsoft Foundry resource. AnyRouter calls your resource directly, so Azure bills you through your Marketplace subscription.
## Get your API key [#get-your-api-key]
1. In [Microsoft Foundry](https://ai.azure.com/), open **Discover → Models**, pick a Claude model and **Deploy** it. Keep the default deployment name (the model name).
2. Open the deployment and copy the resource endpoint (`https://.services.ai.azure.com`) and the API key.
3. Paste a JSON object into AnyRouter: `{ "endpoint_url": "https://.services.ai.azure.com", "api_key": "" }`.
## Gotchas [#gotchas]
If you renamed a deployment, add a `"deployments"` map from the model id to your name, for example `{ "claude-sonnet-4-6": "my-sonnet" }`. Claude on Foundry needs a pay-as-you-go Azure subscription; student, trial and credit-only subscriptions cannot deploy it.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Azure Foundry (Claude)** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against Azure Foundry (Claude) before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Azure (BYOK) (/features/byok-providers/azure)
Azure is a supported [BYOK](/features/byok) provider. Bring your own Azure Foundry resource (endpoint URL, deployment name and key), and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. In the [Azure AI Foundry portal](https://ai.azure.com/), deploy **Microsoft-Decision-1** from the model catalog.
2. On the deployment page, copy the **resource endpoint**, the **deployment name** and the **API key**.
3. Enter them in the **Resource endpoint**, **Deployment name** and **API key** fields when adding the key. For the API or CLI, send them as one JSON object with `endpoint_url`, `deployment` and `api_key`.
## Gotchas [#gotchas]
The endpoint must be `https://.services.ai.azure.com` or `https://.openai.azure.com` (no path, port or IP). Requests go straight to your Azure resource and bill to your Azure account.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Azure** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against Azure before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Baseten (BYOK) (/features/byok-providers/baseten)
Baseten is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to [Baseten](https://baseten.co/).
2. Open **Settings → API Keys**.
3. Create a key and copy it.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Baseten** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Default base URL** (editable on the key form): `https://inference.baseten.co/v1`
4. Click **Save**. AnyRouter runs a live validation call against Baseten before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Blackbox AI (BYOK) (/features/byok-providers/blackbox)
Blackbox AI is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to [Blackbox AI](https://www.blackbox.ai/).
2. Open **Settings → API Keys**.
3. Create a key and copy it.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Blackbox AI** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Blackbox AI console](https://app.blackbox.ai/dashboard)
* **Default base URL** (editable on the key form): `https://www.blackbox.com/api/v1`
4. Click **Save**. AnyRouter runs a live validation call against Blackbox AI before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Cerebras (BYOK) (/features/byok-providers/cerebras)
Cerebras is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
This provider has a free tier or free credits — a good place to start.
## Get your API key [#get-your-api-key]
1. Sign in to the [Cerebras Cloud Platform](https://cloud.cerebras.ai/).
2. Open **API Keys**.
3. Click **Create API Key** and copy it (`csk-...`).
## Gotchas [#gotchas]
Cerebras has a free tier; keys begin with `csk_`.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Cerebras** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Cerebras console](https://cloud.cerebras.ai)
4. Click **Save**. AnyRouter runs a live validation call against Cerebras before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Chutes (BYOK) (/features/byok-providers/chutes)
Chutes is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Chutes console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Chutes** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Default base URL** (editable on the key form): `https://llm.chutes.ai/v1`
4. Click **Save**. AnyRouter runs a live validation call against Chutes before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Cirrascale (BYOK) (/features/byok-providers/cirrascale)
Cirrascale is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Cirrascale console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Cirrascale** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Cirrascale console](https://aisuite.cirrascale.com/account/api-keys)
4. Click **Save**. AnyRouter runs a live validation call against Cirrascale before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Clarifai (BYOK) (/features/byok-providers/clarifai)
Clarifai is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Clarifai console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Clarifai** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against Clarifai before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Cline (BYOK) (/features/byok-providers/cline)
Cline is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Cline console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Cline** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Cline console](https://app.cline.bot)
* **Default base URL** (editable on the key form): `https://api.cline.bot/api/v1`
4. Click **Save**. AnyRouter runs a live validation call against Cline before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Cloudflare (BYOK) (/features/byok-providers/cloudflare)
Cloudflare is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Cloudflare console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Cloudflare** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against Cloudflare before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Cohere (BYOK) (/features/byok-providers/cohere)
Cohere is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Log in to the [Cohere Dashboard](https://dashboard.cohere.ai/).
2. Open **API Keys**.
3. Click **Create API key** (trial or production) and copy it.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Cohere** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Cohere console](https://dashboard.cohere.com/api-keys)
* **Default base URL** (editable on the key form): `https://api.cohere.ai/compatibility/v1`
4. Click **Save**. AnyRouter runs a live validation call against Cohere before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# CommandCode (BYOK) (/features/byok-providers/commandcode)
CommandCode is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the CommandCode console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **CommandCode** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [CommandCode console](https://commandcode.ai)
4. Click **Save**. AnyRouter runs a live validation call against CommandCode before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# CoreWeave (BYOK) (/features/byok-providers/coreweave)
CoreWeave is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the CoreWeave console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **CoreWeave** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [CoreWeave console](https://forge.coreweave.com/settings)
4. Click **Save**. AnyRouter runs a live validation call against CoreWeave before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Crusoe (BYOK) (/features/byok-providers/crusoe)
Crusoe is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Crusoe console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Crusoe** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Crusoe console](https://console.crusoecloud.com)
4. Click **Save**. AnyRouter runs a live validation call against Crusoe before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Decart (BYOK) (/features/byok-providers/decart)
Decart is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Decart console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Decart** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Decart console](https://cogito.decart.ai)
4. Click **Save**. AnyRouter runs a live validation call against Decart before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# DeepInfra (BYOK) (/features/byok-providers/deepinfra)
DeepInfra is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the [DeepInfra Dashboard](https://deepinfra.com/).
2. Open **API Keys**.
3. Click **Create new token**, name it, and copy it.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **DeepInfra** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [DeepInfra console](https://deepinfra.com/dash/api_keys)
* **Default base URL** (editable on the key form): `https://api.deepinfra.com/v1/openai`
4. Click **Save**. AnyRouter runs a live validation call against DeepInfra before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# DeepSeek (BYOK) (/features/byok-providers/deepseek)
DeepSeek is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Log in to the [DeepSeek Platform](https://platform.deepseek.com/).
2. Open **API Keys**.
3. Click **Create API key**, name it, and copy the key (`sk-...`).
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **DeepSeek** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [DeepSeek console](https://platform.deepseek.com/api_keys)
* **Default base URL** (editable on the key form): `https://api.deepseek.com`
4. Click **Save**. AnyRouter runs a live validation call against DeepSeek before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# DigitalOcean (BYOK) (/features/byok-providers/digitalocean)
DigitalOcean is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the DigitalOcean console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **DigitalOcean** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [DigitalOcean console](https://cloud.digitalocean.com)
4. Click **Save**. AnyRouter runs a live validation call against DigitalOcean before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Experiential (BYOK) (/features/byok-providers/experiential)
Experiential is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to [Experiential Labs](https://platform.experientiallabs.ai).
2. Open **API keys** and create a gateway key (begins with `xpl_`).
3. Copy the key immediately.
## Gotchas [#gotchas]
Point the OpenAI SDK at `https://api.experientiallabs.ai/v1`. Model ids are catalog slugs such as `qwen3.8-27b`.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Experiential** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Experiential console](https://platform.experientiallabs.ai)
* **Default base URL** (editable on the key form): `https://api.experientiallabs.ai/v1`
4. Click **Save**. AnyRouter runs a live validation call against Experiential before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Fastino (BYOK) (/features/byok-providers/fastino)
Fastino is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Fastino console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Fastino** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Fastino console](https://labs.fastino.ai)
* **Default base URL** (editable on the key form): `https://api.fastino.ai/v1`
4. Click **Save**. AnyRouter runs a live validation call against Fastino before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Featherless (BYOK) (/features/byok-providers/featherless)
Featherless is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Featherless console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Featherless** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Featherless console](https://featherless.ai/account/api-keys)
4. Click **Save**. AnyRouter runs a live validation call against Featherless before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Fireworks (BYOK) (/features/byok-providers/fireworks)
Fireworks is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Log in to the [Fireworks Console](https://fireworks.ai/).
2. Open **User settings → API keys**.
3. Click **Create new key** and copy it.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Fireworks** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Fireworks console](https://app.fireworks.ai/settings/users/api-keys)
4. Click **Save**. AnyRouter runs a live validation call against Fireworks before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# FPT Cloud (BYOK) (/features/byok-providers/fpt)
FPT Cloud is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the FPT Cloud console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **FPT Cloud** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [FPT Cloud console](https://marketplace.fptcloud.com/en/my-account?tab=my-api-key)
* **Default base URL** (editable on the key form): `https://mkp-api.fptcloud.com/v1`
4. Click **Save**. AnyRouter runs a live validation call against FPT Cloud before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Freebuff (BYOK) (/features/byok-providers/freebuff)
Freebuff is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
This provider has a free tier or free credits — a good place to start.
## Get your API key [#get-your-api-key]
1. Sign in to the Freebuff console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Freebuff** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Freebuff console](https://freebuff.llm.pm)
* **Default base URL** (editable on the key form): `https://www.codebuff.com/api/v1`
4. Click **Save**. AnyRouter runs a live validation call against Freebuff before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Friendli (BYOK) (/features/byok-providers/friendli)
Friendli is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Friendli console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Friendli** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against Friendli before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# GMICloud (BYOK) (/features/byok-providers/gmicloud)
GMICloud is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the GMICloud console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **GMICloud** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [GMICloud console](https://console.gmicloud.ai)
4. Click **Save**. AnyRouter runs a live validation call against GMICloud before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Google AI Studio (BYOK) (/features/byok-providers/google-ai-studio)
Google AI Studio keys work with both the native Gemini API and the OpenAI-compatible endpoint AnyRouter uses. New keys are created as **Auth keys** by default.
This provider has a free tier or free credits — a good place to start.
## Get your API key [#get-your-api-key]
1. Go to [Google AI Studio](https://aistudio.google.com/).
2. Open [API keys](https://aistudio.google.com/apikey) (or click **Get API key**).
3. Click **Create API key**, select or create a Google Cloud project, then **Create**.
4. Copy the key (usually starts with `AIzaSy...`) and add it under **Google AI Studio**.
## Gotchas [#gotchas]
The free tier includes generous usage of Gemini Flash and Pro models. Keys are plain alphanumeric strings.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Google AI Studio** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Google AI Studio console](https://aistudio.google.com/apikey)
* **Default base URL** (editable on the key form): `https://generativelanguage.googleapis.com/v1beta/openai`
4. Click **Save**. AnyRouter runs a live validation call against Google AI Studio before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Google Vertex (BYOK) (/features/byok-providers/google-vertex)
Vertex uses Google Cloud service-account credentials rather than a single API key.
## Get your API key [#get-your-api-key]
1. In the [Google Cloud Console](https://console.cloud.google.com/), create or pick a project.
2. Create a **service account** and grant it the **Vertex AI User** role.
3. Generate a JSON key for that service account (**Keys → Add key → Create new key → JSON**).
4. Paste the full service-account JSON into AnyRouter; optionally set a `region` (defaults to `us-central1`).
## Gotchas [#gotchas]
The JSON must include `type`, `project_id`, `private_key`, and `client_email`. Requests are billed to the linked Google Cloud project, not AnyRouter credits.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Google Vertex** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against Google Vertex before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Groq (BYOK) (/features/byok-providers/groq)
Groq is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
This provider has a free tier or free credits — a good place to start.
## Get your API key [#get-your-api-key]
1. Log in to the [Groq Console](https://console.groq.com/).
2. Open **API Keys** in the sidebar.
3. Click **Create API Key**, enter a name, **Create**, and copy it immediately (`gsk_...`).
## Gotchas [#gotchas]
Groq has a free tier; keys begin with `gsk_`.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Groq** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Groq console](https://console.groq.com/keys)
* **Default base URL** (editable on the key form): `https://api.groq.com/openai/v1`
4. Click **Save**. AnyRouter runs a live validation call against Groq before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Hugging Face (BYOK) (/features/byok-providers/huggingface)
Hugging Face is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
This provider has a free tier or free credits — a good place to start.
## Get your API key [#get-your-api-key]
1. Sign in to the Hugging Face console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Hugging Face** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Hugging Face console](https://huggingface.co/settings/tokens)
4. Click **Save**. AnyRouter runs a live validation call against Hugging Face before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Inception (BYOK) (/features/byok-providers/inception)
Inception is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Inception console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Inception** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Inception console](https://platform.inceptionlabs.ai)
4. Click **Save**. AnyRouter runs a live validation call against Inception before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Inceptron (BYOK) (/features/byok-providers/inceptron)
Inceptron is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Inceptron console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Inceptron** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against Inceptron before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# inference.net (BYOK) (/features/byok-providers/inference-net)
inference.net is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the inference.net console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **inference.net** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [inference.net console](https://inference.net/dashboard/api-keys/)
4. Click **Save**. AnyRouter runs a live validation call against inference.net before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Infermatic (BYOK) (/features/byok-providers/infermatic)
Infermatic is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Infermatic console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Infermatic** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Infermatic console](https://ui.infermatic.ai)
4. Click **Save**. AnyRouter runs a live validation call against Infermatic before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Inflection (BYOK) (/features/byok-providers/inflection)
Inflection is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Inflection console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Inflection** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Inflection console](https://developers.inflection.ai/keys)
4. Click **Save**. AnyRouter runs a live validation call against Inflection before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Infron (BYOK) (/features/byok-providers/infron)
Infron is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
This provider has a free tier or free credits — a good place to start.
## Get your API key [#get-your-api-key]
1. Sign in to the Infron console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Infron** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Infron console](https://infron.ai/dashboard/apiKeys)
* **Default base URL** (editable on the key form): `https://api.infron.ai/v1`
4. Click **Save**. AnyRouter runs a live validation call against Infron before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Intern (BYOK) (/features/byok-providers/intern)
Intern is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Intern console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Intern** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Intern console](https://discovery.intern-ai.org.cn/token-plan/home?lang=en-US)
* **Default base URL** (editable on the key form): `https://discovery-api.intern-ai.org.cn/v1`
4. Click **Save**. AnyRouter runs a live validation call against Intern before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# io.net (BYOK) (/features/byok-providers/io-net)
io.net is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the io.net console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **io.net** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against io.net before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Liquid (BYOK) (/features/byok-providers/liquid)
Liquid is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Liquid console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Liquid** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Liquid console](https://console.liquid.ai)
* **Default base URL** (editable on the key form): `https://api.liquid.ai/decisions/v1`
4. Click **Save**. AnyRouter runs a live validation call against Liquid before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# LLM Gateway (BYOK) (/features/byok-providers/llmgateway)
LLM Gateway is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the LLM Gateway console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **LLM Gateway** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [LLM Gateway console](https://llmgateway.io)
* **Default base URL** (editable on the key form): `https://api.llmgateway.io/v1`
4. Click **Save**. AnyRouter runs a live validation call against LLM Gateway before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# LongCat (BYOK) (/features/byok-providers/longcat)
LongCat is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to [LongCat](https://longcat.chat/).
2. Open **API Keys**.
3. Create a key (begins with `sk-...`) and copy it.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **LongCat** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [LongCat console](https://longcat.chat/platform/api_keys)
* **Default base URL** (editable on the key form): `https://api.longcat.chat/openai/v1`
4. Click **Save**. AnyRouter runs a live validation call against LongCat before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Makora (BYOK) (/features/byok-providers/makora)
Makora is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Makora console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Makora** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Makora console](https://app.makora.com)
4. Click **Save**. AnyRouter runs a live validation call against Makora before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Mancer (BYOK) (/features/byok-providers/mancer)
Mancer is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Mancer console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Mancer** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against Mancer before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Meta (BYOK) (/features/byok-providers/meta)
Meta is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Meta console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Meta** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Meta console](https://dev.meta.ai/api/auth/login)
* **Default base URL** (editable on the key form): `https://api.meta.ai/v1`
4. Click **Save**. AnyRouter runs a live validation call against Meta before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# MiniMax (BYOK) (/features/byok-providers/minimax)
MiniMax is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the MiniMax console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **MiniMax** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [MiniMax console](https://platform.minimax.io)
4. Click **Save**. AnyRouter runs a live validation call against MiniMax before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Mistral (BYOK) (/features/byok-providers/mistral)
Mistral is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
This provider has a free tier or free credits — a good place to start.
## Get your API key [#get-your-api-key]
1. Log in to the [Mistral Console](https://console.mistral.ai/).
2. Open **API keys** in the sidebar.
3. Click **Create new key**, name it, and copy it.
## Gotchas [#gotchas]
Mistral offers a free tier; keys are plain alphanumeric strings.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Mistral** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Mistral console](https://console.mistral.ai/api-keys)
* **Default base URL** (editable on the key form): `https://api.mistral.ai/v1`
4. Click **Save**. AnyRouter runs a live validation call against Mistral before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Modal (BYOK) (/features/byok-providers/modal)
Modal is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Modal console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Modal** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Modal console](https://modal.com/settings)
4. Click **Save**. AnyRouter runs a live validation call against Modal before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Moonshot AI (BYOK) (/features/byok-providers/moonshot)
Moonshot AI is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Log in to the [Moonshot AI Platform](https://platform.moonshot.cn/).
2. Open **API Keys**.
3. Create a key and copy it.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Moonshot AI** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Moonshot AI console](https://platform.moonshot.ai/console/api-keys)
* **Default base URL** (editable on the key form): `https://api.moonshot.ai/v1`
4. Click **Save**. AnyRouter runs a live validation call against Moonshot AI before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Morph (BYOK) (/features/byok-providers/morph)
Morph is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Morph console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Morph** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Morph console](https://morphllm.com/dashboard/api-keys)
4. Click **Save**. AnyRouter runs a live validation call against Morph before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# MuleRouter (BYOK) (/features/byok-providers/mulerouter)
MuleRouter is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the MuleRouter console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **MuleRouter** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [MuleRouter console](https://www.mulerouter.ai/app/api-keys)
4. Click **Save**. AnyRouter runs a live validation call against MuleRouter before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Nace AI (BYOK) (/features/byok-providers/nace-ai)
Nace AI is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Nace AI console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Nace AI** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Nace AI console](https://console.nace.ai)
* **Default base URL** (editable on the key form): `https://console.nace.ai/v1`
4. Click **Save**. AnyRouter runs a live validation call against Nace AI before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Nebius Token Factory (BYOK) (/features/byok-providers/nebius)
Nebius Token Factory is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Nebius Token Factory console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Nebius Token Factory** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Nebius Token Factory console](https://tokenfactory.nebius.com/settings/api-keys)
4. Click **Save**. AnyRouter runs a live validation call against Nebius Token Factory before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# NextBit (BYOK) (/features/byok-providers/nextbit)
NextBit is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the NextBit console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **NextBit** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against NextBit before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Nous Research (BYOK) (/features/byok-providers/nousresearch)
Nous Research is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the [Nous Portal](https://portal.nousresearch.com/).
2. Open **API Keys**.
3. Create a key and copy it.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Nous Research** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Nous Research console](https://portal.nousresearch.com)
* **Default base URL** (editable on the key form): `https://inference-api.nousresearch.com/v1`
4. Click **Save**. AnyRouter runs a live validation call against Nous Research before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# NovitaAI (BYOK) (/features/byok-providers/novita)
NovitaAI is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Log in to [Novita AI](https://novita.ai/).
2. Open **API Keys**.
3. Create a key and copy it.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **NovitaAI** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [NovitaAI console](https://novita.ai/settings/key-management)
4. Click **Save**. AnyRouter runs a live validation call against NovitaAI before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# NVIDIA NIM (BYOK) (/features/byok-providers/nvidia)
NVIDIA NIM is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
This provider has a free tier or free credits — a good place to start.
## Get your API key [#get-your-api-key]
1. Log in to [NVIDIA Build](https://build.nvidia.com/).
2. Pick any model from the catalog (for example Llama 3).
3. Click **Get API Key**, or open your profile to retrieve an existing key.
4. Copy the key (begins with `nvapi-...`).
## Gotchas [#gotchas]
NVIDIA NIM has a free tier; keys begin with `nvapi-`.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **NVIDIA NIM** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [NVIDIA NIM console](https://build.nvidia.com)
* **Default base URL** (editable on the key form): `https://integrate.api.nvidia.com/v1`
4. Click **Save**. AnyRouter runs a live validation call against NVIDIA NIM before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Ollama Cloud (BYOK) (/features/byok-providers/ollama)
Ollama Cloud is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
This provider has a free tier or free credits — a good place to start.
## Get your API key [#get-your-api-key]
1. Go to [Ollama Cloud](https://ollama.com/settings/keys).
2. Create a new key and copy it.
## Gotchas [#gotchas]
Ollama Cloud has a free tier.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Ollama Cloud** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Ollama Cloud console](https://ollama.com/settings/keys)
* **Default base URL** (editable on the key form): `https://ollama.com/v1`
4. Click **Save**. AnyRouter runs a live validation call against Ollama Cloud before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# OneToken (BYOK) (/features/byok-providers/onetoken)
OneToken is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the OneToken console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **OneToken** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [OneToken console](https://llm-ave.onetokenai.com)
* **Default base URL** (editable on the key form): `https://llm-ave.onetokenai.com/v1`
4. Click **Save**. AnyRouter runs a live validation call against OneToken before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# OpenAI (BYOK) (/features/byok-providers/openai)
OpenAI is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the [OpenAI Platform](https://platform.openai.com/).
2. Open **API keys** in the left sidebar.
3. Click **Create new secret key**, give it a name and an optional project, then **Create secret key**.
4. Copy the key immediately — it is shown only once.
## Gotchas [#gotchas]
Project-scoped keys begin with `sk-proj-...`; both `sk-...` and `sk-proj-...` work. To pin requests to a specific OpenAI organization or project, set a **Base URL** on the AnyRouter key form or pass custom headers.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **OpenAI** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [OpenAI console](https://platform.openai.com/api-keys)
* **Default base URL** (editable on the key form): `https://api.openai.com/v1`
4. Click **Save**. AnyRouter runs a live validation call against OpenAI before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# OpenCode Zen Go (BYOK) (/features/byok-providers/opencode-go)
OpenCode Zen Go is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in at [opencode.ai/auth](https://opencode.ai/auth).
2. Open **API Keys** and create a Go-tier key.
3. Copy the key.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **OpenCode Zen Go** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [OpenCode Zen Go console](https://opencode.ai/auth)
* **Default base URL** (editable on the key form): `https://opencode.ai/zen/go/v1`
4. Click **Save**. AnyRouter runs a live validation call against OpenCode Zen Go before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# OpenCode Zen (BYOK) (/features/byok-providers/opencode-zen)
OpenCode Zen is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
This provider has a free tier or free credits — a good place to start.
## Get your API key [#get-your-api-key]
1. Sign in at [opencode.ai/auth](https://opencode.ai/auth).
2. Open **API Keys** in your account.
3. Create a key and copy it.
## Gotchas [#gotchas]
OpenCode Zen has a free tier.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **OpenCode Zen** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [OpenCode Zen console](https://opencode.ai/auth)
* **Default base URL** (editable on the key form): `https://opencode.ai/zen/v1`
4. Click **Save**. AnyRouter runs a live validation call against OpenCode Zen before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# OpenInference (BYOK) (/features/byok-providers/openinference)
OpenInference is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the OpenInference console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **OpenInference** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [OpenInference console](https://openinference.xyz/console)
4. Click **Save**. AnyRouter runs a live validation call against OpenInference before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# OpenRouter (BYOK) (/features/byok-providers/openrouter)
OpenRouter is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
This provider has a free tier or free credits — a good place to start.
## Get your API key [#get-your-api-key]
1. Sign in to [OpenRouter](https://openrouter.ai/).
2. Open **Settings → Keys**.
3. Click **Create Key**, add a description and optional limits, **Create**, and copy it (`sk-or-...`).
## Gotchas [#gotchas]
You don't need to set a Base URL for OpenRouter. AnyRouter picks the right model automatically from your key pool.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **OpenRouter** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [OpenRouter console](https://openrouter.ai/keys)
* **Default base URL** (editable on the key form): `https://openrouter.ai/api/v1`
4. Click **Save**. AnyRouter runs a live validation call against OpenRouter before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# OrcaRouter (BYOK) (/features/byok-providers/orcarouter)
OrcaRouter is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the OrcaRouter console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **OrcaRouter** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [OrcaRouter console](https://www.orcarouter.ai)
4. Click **Save**. AnyRouter runs a live validation call against OrcaRouter before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# OVHcloud (BYOK) (/features/byok-providers/ovhcloud)
OVHcloud is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the OVHcloud console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **OVHcloud** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [OVHcloud console](https://docs.ovhcloud.com/en/guides/public-cloud/ai-machine-learning/ai-endpoints-getting-started)
* **Default base URL** (editable on the key form): `https://oai.endpoints.kepler.ai.cloud.ovh.net/v1`
4. Click **Save**. AnyRouter runs a live validation call against OVHcloud before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Parasail (BYOK) (/features/byok-providers/parasail)
Parasail is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Parasail console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Parasail** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Parasail console](https://www.saas.parasail.io/keys)
4. Click **Save**. AnyRouter runs a live validation call against Parasail before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Perplexity (BYOK) (/features/byok-providers/perplexity)
Perplexity is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Perplexity console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Perplexity** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Perplexity console](https://www.perplexity.ai/settings/api)
* **Default base URL** (editable on the key form): `https://api.perplexity.ai/v1`
4. Click **Save**. AnyRouter runs a live validation call against Perplexity before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Phala (BYOK) (/features/byok-providers/phala)
Phala is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Phala console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Phala** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against Phala before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Poolside (BYOK) (/features/byok-providers/poolside)
Poolside is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to [Poolside](https://poolside.ai/).
2. Open **API Keys** in your account.
3. Create a key and copy it.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Poolside** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Poolside console](https://platform.poolside.ai/api-keys)
* **Default base URL** (editable on the key form): `https://inference.poolside.ai/v1`
4. Click **Save**. AnyRouter runs a live validation call against Poolside before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Qwen (Alibaba) (BYOK) (/features/byok-providers/qwen)
Qwen (Alibaba) is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Log in to [DashScope (Alibaba Cloud)](https://dashscope.console.aliyun.com/).
2. Open **API Key Management**.
3. Create an API key and copy it.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Qwen (Alibaba)** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Default base URL** (editable on the key form): `https://dashscope-intl.aliyuncs.com/compatible-mode/v1`
4. Click **Save**. AnyRouter runs a live validation call against Qwen (Alibaba) before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# QwenCloud (BYOK) (/features/byok-providers/qwencloud)
QwenCloud is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
This provider has a free tier or free credits — a good place to start.
## Get your API key [#get-your-api-key]
1. Sign in to the QwenCloud console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **QwenCloud** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [QwenCloud console](https://dashscope.aliyuncs.com/api-key)
4. Click **Save**. AnyRouter runs a live validation call against QwenCloud before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Refuseless (BYOK) (/features/byok-providers/refuseless)
Refuseless is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Refuseless console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Refuseless** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Refuseless console](https://refuseless.com)
* **Default base URL** (editable on the key form): `https://api.refuseless.com/v1`
4. Click **Save**. AnyRouter runs a live validation call against Refuseless before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Reka AI (BYOK) (/features/byok-providers/reka)
Reka AI is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Reka AI console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Reka AI** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Reka AI console](https://platform.reka.ai/apikeys)
4. Click **Save**. AnyRouter runs a live validation call against Reka AI before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Relace (BYOK) (/features/byok-providers/relace)
Relace is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Relace console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Relace** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against Relace before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Requesty (BYOK) (/features/byok-providers/requesty)
Requesty is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Requesty console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Requesty** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Requesty console](https://app.requesty.ai/api-keys)
* **Default base URL** (editable on the key form): `https://router.requesty.ai/v1`
4. Click **Save**. AnyRouter runs a live validation call against Requesty before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Respan (BYOK) (/features/byok-providers/respan)
Respan is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Respan console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Respan** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Respan console](https://platform.respan.ai/platform/api/api-keys)
* **Default base URL** (editable on the key form): `https://api.respan.ai/api`
4. Click **Save**. AnyRouter runs a live validation call against Respan before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Ramp Router (BYOK) (/features/byok-providers/router)
Ramp Router is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
This provider has a free tier or free credits — a good place to start.
## Get your API key [#get-your-api-key]
1. Sign in to the Ramp Router console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Ramp Router** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Ramp Router console](https://router.com)
* **Default base URL** (editable on the key form): `https://api.router.com/v1`
4. Click **Save**. AnyRouter runs a live validation call against Ramp Router before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# RunInfra (BYOK) (/features/byok-providers/runinfra)
RunInfra is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the RunInfra console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **RunInfra** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [RunInfra console](https://runinfra.ai/settings/api-keys)
* **Default base URL** (editable on the key form): `https://api.runinfra.ai/v1`
4. Click **Save**. AnyRouter runs a live validation call against RunInfra before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Sail Research (BYOK) (/features/byok-providers/sail-research)
Sail Research is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Sail Research console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Sail Research** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Sail Research console](https://app.sailresearch.com/api-keys)
4. Click **Save**. AnyRouter runs a live validation call against Sail Research before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Sakana AI (BYOK) (/features/byok-providers/sakana)
Sakana AI is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Sakana AI console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Sakana AI** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Sakana AI console](https://console.sakana.ai/get-started)
4. Click **Save**. AnyRouter runs a live validation call against Sakana AI before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# SambaNova (BYOK) (/features/byok-providers/sambanova)
SambaNova is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to [SambaNova Cloud](https://cloud.sambanova.ai/).
2. Open **API Keys**.
3. Click **Create Key** and copy the generated secret.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **SambaNova** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [SambaNova console](https://cloud.sambanova.ai/apis)
4. Click **Save**. AnyRouter runs a live validation call against SambaNova before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# SiliconFlow (BYOK) (/features/byok-providers/siliconflow)
SiliconFlow is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Log in to [SiliconFlow](https://siliconflow.cn/).
2. Open **API Keys**.
3. Create a key and copy it.
## Gotchas [#gotchas]
SiliconFlow offers a free tier and serves DeepSeek, Qwen, GLM, Kimi and more.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **SiliconFlow** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [SiliconFlow console](https://cloud.siliconflow.com/account/ak)
4. Click **Save**. AnyRouter runs a live validation call against SiliconFlow before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# StepFun (BYOK) (/features/byok-providers/stepfun)
StepFun is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Log in to the [StepFun Platform](https://platform.stepfun.ai/).
2. Open **API Keys**.
3. Create a key and copy it.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **StepFun** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [StepFun console](https://platform.stepfun.ai/interface-key)
* **Default base URL** (editable on the key form): `https://api.stepfun.ai/v1`
4. Click **Save**. AnyRouter runs a live validation call against StepFun before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# StepFun Step Plan (BYOK) (/features/byok-providers/stepplan)
StepFun Step Plan is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the StepFun Step Plan console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **StepFun Step Plan** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [StepFun Step Plan console](https://platform.stepfun.ai/interface-key)
* **Default base URL** (editable on the key form): `https://api.stepfun.ai/step_plan/v1`
4. Click **Save**. AnyRouter runs a live validation call against StepFun Step Plan before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Strands Decider (BYOK) (/features/byok-providers/strands-decider)
Strands Decider is self-hosted: there is no hosted API, no account, and no key console. You run `strands-decider serve` on your own hardware, and the saved key is a passthrough — the server defines no auth and ignores whatever you store.
## Get your API key [#get-your-api-key]
1. Install the server: `pip install strands-decider` ([docs](https://github.com/strands-labs/strands-decider#running-as-a-server)).
2. Start it: `strands-decider serve StrandsAgents/strands-decider-2B-hobson-v19 --port 8000`.
3. In AnyRouter, add a **Strands Decider** key with any placeholder value — the serve does not read it.
## Gotchas [#gotchas]
Requests route to the documented default serve origin (`http://localhost:8000`); a live call only succeeds while your `strands-decider serve` process is running and reachable at that origin. AnyRouter runs a live validation call before storing the key, so a serve that is not up and frontable yet will fail validation.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Strands Decider** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Strands Decider console](https://github.com/strands-labs/strands-decider#running-as-a-server)
* **Default base URL** (editable on the key form): `http://localhost:8000/v1`
4. Click **Save**. AnyRouter runs a live validation call against Strands Decider before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# StreamLake (BYOK) (/features/byok-providers/streamlake)
StreamLake is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the StreamLake console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **StreamLake** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [StreamLake console](https://console.streamlake.ai/console/wanqing/api-key)
4. Click **Save**. AnyRouter runs a live validation call against StreamLake before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# SubQ (BYOK) (/features/byok-providers/subq)
SubQ is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the SubQ console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Models [#models]
| Model | Status | Use it for | AnyRouter endpoint |
| -------------------------------- | -------------- | ------------------------------------------------- | ------------------------------- |
| `subquadratic/subq-preview` | Early access | Long-context chat, files, function calling | `POST /api/v1/chat/completions` |
| `subquadratic/subq-snap-preview` | Public preview | Fast structured decisions (yes/no, choice, score) | `POST /api/v1/decisions` |
Both models run on your SubQ key, not on platform credits.
## Decisions [#decisions]
`subquadratic/subq-snap-preview` returns typed answers with probabilities instead of chat text. Send the state to evaluate and one or more named questions:
```bash
curl https://anyrouter.dev/api/v1/decisions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "subquadratic/subq-snap-preview",
"state": "Patient with asthma reports a sore throat, fever since yesterday, and shortness of breath.",
"questions": {
"escalate": {
"type": "noul",
"instructions": "Should a nurse see this chat before the patient books anything?",
"criteria": {
"true": "Breathing trouble, chest pain, or a flare of a known condition",
"false": "Stable symptoms the patient can book for themselves"
}
}
}
}'
```
The response has one answer per question name. For a `noul` question, `noul` is the probability of yes, from 0 to 1.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **SubQ** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [SubQ console](https://console.subq.ai)
* **Default base URL** (editable on the key form): `https://api.subq.ai/v1`
4. Click **Save**. AnyRouter runs a live validation call against SubQ before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Switchpoint (BYOK) (/features/byok-providers/switchpoint)
Switchpoint is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Switchpoint console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Switchpoint** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against Switchpoint before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Together (BYOK) (/features/byok-providers/together)
Together is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Log in to the [Together AI Console](https://api.together.ai/).
2. Open **Settings → API Keys**.
3. Click **Create Key** or copy the default key provided (usually a 64-char hex string).
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Together** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Together console](https://api.together.xyz/settings/api-keys)
* **Default base URL** (editable on the key form): `https://api.together.xyz/v1`
4. Click **Save**. AnyRouter runs a live validation call against Together before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# TypeSafe (BYOK) (/features/byok-providers/typesafe)
TypeSafe is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the TypeSafe console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **TypeSafe** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [TypeSafe console](https://console.typesafe.ai/settings/keys)
* **Default base URL** (editable on the key form): `https://api.typesafe.ai/v1`
4. Click **Save**. AnyRouter runs a live validation call against TypeSafe before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Unbiased (BYOK) (/features/byok-providers/unbiased)
Unbiased is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Unbiased console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Unbiased** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Unbiased console](https://platform.unbiased.ai/dashboard)
* **Default base URL** (editable on the key form): `https://api.unbiased.ai/v1`
4. Click **Save**. AnyRouter runs a live validation call against Unbiased before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Venice (BYOK) (/features/byok-providers/venice)
Venice is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Log in to [Venice AI](https://venice.ai/).
2. Open **Settings → API Keys**.
3. Create a key and copy it.
## Gotchas [#gotchas]
Venice is a privacy-first provider with E2EE models.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Venice** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Venice console](https://venice.ai/settings/api)
4. Click **Save**. AnyRouter runs a live validation call against Venice before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Vercel AI Gateway (BYOK) (/features/byok-providers/vercel)
Vercel AI Gateway is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Vercel AI Gateway console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Vercel AI Gateway** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Vercel AI Gateway console](https://vercel.com/docs/ai-gateway/authentication-and-byok/api-keys)
* **Default base URL** (editable on the key form): `https://ai-gateway.vercel.sh/typesafe/v1`
4. Click **Save**. AnyRouter runs a live validation call against Vercel AI Gateway before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Google Vertex AI (Claude) (BYOK) (/features/byok-providers/vertex-claude)
Use Claude from your own Google Cloud project. AnyRouter calls Claude on Vertex AI with your service account, so Google bills you directly.
## Get your API key [#get-your-api-key]
1. In the [Google Cloud console](https://console.cloud.google.com/vertex-ai/model-garden), open the **Model Garden**, find Claude and enable the models you want.
2. Create a service account with the **Vertex AI User** role and download its JSON key.
3. Paste the service-account JSON into AnyRouter. To pick a region, add a `"region"` field (for example `"us-east5"`); the default is `"global"`.
## Gotchas [#gotchas]
Claude models newer than Sonnet 4.6 are only served on `global` and multi-region locations, so a single-region key works for older models only. Regional endpoints cost 10% more than global on Google Cloud.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Google Vertex AI (Claude)** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
4. Click **Save**. AnyRouter runs a live validation call against Google Vertex AI (Claude) before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Wafer (BYOK) (/features/byok-providers/wafer)
Wafer is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Wafer console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Wafer** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Wafer console](https://pass.wafer.ai)
4. Click **Save**. AnyRouter runs a live validation call against Wafer before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Weights & Biases (BYOK) (/features/byok-providers/wandb)
Weights & Biases is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Weights & Biases console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Weights & Biases** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Weights & Biases console](https://wandb.ai/authorize)
4. Click **Save**. AnyRouter runs a live validation call against Weights & Biases before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# xAI (BYOK) (/features/byok-providers/xai)
xAI is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the [xAI Console](https://console.x.ai/).
2. Open **API Keys**.
3. Click **Create API Key** and copy the secret (`xai-...`).
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **xAI** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [xAI console](https://console.x.ai)
4. Click **Save**. AnyRouter runs a live validation call against xAI before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Xiaomi (BYOK) (/features/byok-providers/xiaomi)
Xiaomi is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
## Get your API key [#get-your-api-key]
1. Sign in to the Xiaomi console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Xiaomi** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Default base URL** (editable on the key form): `https://token-plan-cn.xiaomimimo.com/v1`
4. Click **Save**. AnyRouter runs a live validation call against Xiaomi before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Z.ai (Coding Plan) (BYOK) (/features/byok-providers/z-ai-coding)
Dedicated key for the Z.ai coding subscription plan.
## Get your API key [#get-your-api-key]
1. Log in to [Z.ai](https://z.ai/).
2. Open [API keys](https://z.ai/manage-apikey/apikey-list).
3. Create a key under your **Coding Plan** and copy it.
4. Add it under **Z.ai (Coding Plan)** in AnyRouter.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Z.ai (Coding Plan)** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Z.ai (Coding Plan) console](https://z.ai/manage-apikey/apikey-list)
* **Default base URL** (editable on the key form): `https://api.z.ai/api/coding/paas/v4`
4. Click **Save**. AnyRouter runs a live validation call against Z.ai (Coding Plan) before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# Z.ai (BYOK) (/features/byok-providers/z-ai)
Z.ai (BigModel / GLM) keys use the `ID.Secret` format. Two variants exist — the standard PaaS key and the dedicated Coding Plan key.
## Get your API key [#get-your-api-key]
1. Log in to [Z.ai](https://z.ai/).
2. Open [API keys](https://z.ai/manage-apikey/apikey-list).
3. Create a new key (or use the default key from signup) and copy it.
4. Add it under **Z.ai** (standard) or **Z.ai (Coding Plan)** to match your plan.
## Gotchas [#gotchas]
The standard key uses base URL `https://api.z.ai/api/paas/v4`; the Coding Plan uses `https://api.z.ai/api/coding/paas/v4`. Pick the card that matches your plan so routing hits the right endpoint.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **Z.ai** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [Z.ai console](https://z.ai/manage-apikey/apikey-list)
* **Default base URL** (editable on the key form): `https://api.z.ai/api/paas/v4`
4. Click **Save**. AnyRouter runs a live validation call against Z.ai before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# ZenMux (BYOK) (/features/byok-providers/zenmux)
ZenMux is a supported [BYOK](/features/byok) provider. Bring your own API key string, and AnyRouter routes your requests through it — no platform credits are charged for BYOK-backed calls.
This provider has a free tier or free credits — a good place to start.
## Get your API key [#get-your-api-key]
1. Sign in to the ZenMux console.
2. Open the API keys / credentials section and create a new key.
3. Copy the key immediately — most providers show it only once.
## Add it to AnyRouter [#add-it-to-anyrouter]
1. Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**.
2. Find **ZenMux** under **Available** and click **Add key**.
3. Paste your key into the **API key** field.
* **Create key**: [ZenMux console](https://zenmux.ai/settings/keys)
* **Default base URL** (editable on the key form): `https://zenmux.ai/api/v1`
4. Click **Save**. AnyRouter runs a live validation call against ZenMux before storing the key — if it fails, the dialog shows the upstream error and the key isn't saved.
## Related [#related]
* [BYOK Provider Setup Guide](/features/byok-providers) — the full provider list and the general flow
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers behind one preset
---
# BYOK Provider Setup Guide (/features/byok-providers)
Every [BYOK](/features/byok) provider hides its API-key page in a slightly different corner of its console, and each key has its own prefix and format. Pick a provider below for exact, step-by-step instructions on where to generate the key and how to add it to AnyRouter.
## The general flow [#the-general-flow]
Adding any key follows the same three steps — generate, paste, save:
::::steps
:::step{title="Generate a key in the provider console"}
Open the provider's API-key page (linked on each provider page), create a new key, and copy it. Many providers show the secret only once, so copy it immediately.
:::
:::step{title="Add it under Dashboard → BYOK"}
Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**, find the provider under **Available**, and click **Add key**. Paste the key into the **API key** field.
:::
:::step{title="Save"}
Click **Save**. AnyRouter runs a live validation call against the provider before storing the key. If validation fails, the dialog surfaces the upstream error and the key isn't saved.
:::
::::
The dashboard lives at **`anyrouter.dev/dashboard`**, and the old `dash.anyrouter.dev` address leads there too.
## What the API reports about each key [#what-the-api-reports-about-each-key]
Adding a key from the dashboard is the easy path. If you add keys from a script instead, the API tells you two extra things about every key it returns:
| Field | Values | What it means |
| -------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `kind` | `byok` or `donated` | `donated` while the key is shared with the community [shared pool](/features/shared-pool) — including while a donated key is temporarily paused. `byok` means it serves only your own requests. Donating and withdrawing are what change it. |
| `source` | `cli`, `dashboard`, or `api` | Which app added the key: the command-line tool, the dashboard, or your own script. |
Pass `source` when you add a key so you can tell one script's keys from another's later:
```bash
curl -X POST https://anyrouter.dev/api/v1/auth/byok \
-H "Authorization: Bearer ak_live_..." \
-H "Content-Type: application/json" \
-d '{
"provider_id": "openai",
"api_key": "sk-proj-…",
"label": "laptop",
"source": "cli"
}'
```
`source` accepts `cli`, `dashboard`, or `api` — anything else is rejected with `400`. Leave it out and it defaults to `api`, which is also what keys added before this field existed report. The dashboard sets `dashboard` for keys you add there. The installed `anyr` binary doesn't set `cli` yet, so keys it adds report `api` until a new release ships — set it yourself when your own script adds a key.
The response (and every later `GET /api/v1/auth/byok`) carries both fields:
```json
{
"id": "byok_01JQZ…",
"provider_id": "openai",
"label": "laptop",
"key_preview": "sk-p****7c2",
"enabled": true,
"route_coverage": "all",
"kind": "byok",
"source": "cli"
}
```
`key_preview` is a masked copy of the key — the first four characters, `****`, and the last three. The key itself is never returned. Keys are listed per workspace, so a key added under one organization doesn't appear under another.
## Providers [#providers]
Choose your provider for detailed setup steps:
| Provider |
| ------------------------------------------------------------- |
| [AI21](/features/byok-providers/ai21) |
| [AIHubMix](/features/byok-providers/aihubmix) |
| [AionLabs](/features/byok-providers/aionlabs) |
| [AkashML](/features/byok-providers/akash) |
| [Alibaba Cloud Int.](/features/byok-providers/alibaba-cloud) |
| [Amazon Bedrock](/features/byok-providers/amazon-bedrock) |
| [Anthropic](/features/byok-providers/anthropic) |
| [Arcee AI](/features/byok-providers/arcee) |
| [AutoJev](/features/byok-providers/autojev) |
| [Azure](/features/byok-providers/azure) |
| [Baseten](/features/byok-providers/baseten) |
| [Blackbox AI](/features/byok-providers/blackbox) |
| [Cerebras](/features/byok-providers/cerebras) |
| [Chutes](/features/byok-providers/chutes) |
| [Cirrascale](/features/byok-providers/cirrascale) |
| [Clarifai](/features/byok-providers/clarifai) |
| [Cloudflare](/features/byok-providers/cloudflare) |
| [Cohere](/features/byok-providers/cohere) |
| [Crusoe](/features/byok-providers/crusoe) |
| [DeepInfra](/features/byok-providers/deepinfra) |
| [DeepSeek](/features/byok-providers/deepseek) |
| [Experiential](/features/byok-providers/experiential) |
| [Featherless](/features/byok-providers/featherless) |
| [Fireworks](/features/byok-providers/fireworks) |
| [Friendli](/features/byok-providers/friendli) |
| [GMICloud](/features/byok-providers/gmicloud) |
| [Google AI Studio](/features/byok-providers/google-ai-studio) |
| [Google Vertex](/features/byok-providers/google-vertex) |
| [Groq](/features/byok-providers/groq) |
| [Inception](/features/byok-providers/inception) |
| [Inceptron](/features/byok-providers/inceptron) |
| [Infermatic](/features/byok-providers/infermatic) |
| [Inflection](/features/byok-providers/inflection) |
| [io.net](/features/byok-providers/io-net) |
| [Liquid](/features/byok-providers/liquid) |
| [LLM Gateway](/features/byok-providers/llmgateway) |
| [LongCat](/features/byok-providers/longcat) |
| [Mancer](/features/byok-providers/mancer) |
| [MiniMax](/features/byok-providers/minimax) |
| [Mistral](/features/byok-providers/mistral) |
| [Moonshot AI](/features/byok-providers/moonshot) |
| [Morph](/features/byok-providers/morph) |
| [Nebius Token Factory](/features/byok-providers/nebius) |
| [NextBit](/features/byok-providers/nextbit) |
| [Nous Research](/features/byok-providers/nousresearch) |
| [NovitaAI](/features/byok-providers/novita) |
| [NVIDIA NIM](/features/byok-providers/nvidia) |
| [Ollama Cloud](/features/byok-providers/ollama) |
| [OpenAI](/features/byok-providers/openai) |
| [OneToken](/features/byok-providers/onetoken) |
| [OpenCode Zen Go](/features/byok-providers/opencode-go) |
| [OpenCode Zen](/features/byok-providers/opencode-zen) |
| [OpenInference](/features/byok-providers/openinference) |
| [OpenRouter](/features/byok-providers/openrouter) |
| [Parasail](/features/byok-providers/parasail) |
| [Perplexity](/features/byok-providers/perplexity) |
| [Phala](/features/byok-providers/phala) |
| [Poolside](/features/byok-providers/poolside) |
| [Qwen (Alibaba)](/features/byok-providers/qwen) |
| [Reka AI](/features/byok-providers/reka) |
| [Relace](/features/byok-providers/relace) |
| [SambaNova](/features/byok-providers/sambanova) |
| [SiliconFlow](/features/byok-providers/siliconflow) |
| [StepFun](/features/byok-providers/stepfun) |
| [Switchpoint](/features/byok-providers/switchpoint) |
| [Together](/features/byok-providers/together) |
| [TypeSafe](/features/byok-providers/typesafe) |
| [Venice](/features/byok-providers/venice) |
| [Vercel AI Gateway](/features/byok-providers/vercel) |
| [Weights & Biases](/features/byok-providers/wandb) |
| [xAI](/features/byok-providers/xai) |
| [Xiaomi](/features/byok-providers/xiaomi) |
| [Z.ai (Coding Plan)](/features/byok-providers/z-ai-coding) |
| [Z.ai](/features/byok-providers/z-ai) |
## Related [#related]
* [Bring Your Own Key (BYOK)](/features/byok) — routing precedence, load balancing, security, and costs
* [Free-tier aggregation](/features/free-tier-aggregation) — combine free tiers from these providers behind one preset
* [Smart Routing](/features/routing) — how AnyRouter picks an upstream per request
---
# Bring Your Own Key (BYOK) (/features/byok)
If you already hold provider accounts with negotiated pricing, prepaid credits, or preview-tier access, paying AnyRouter credits on top is wasted money. BYOK lets you attach your own upstream provider API keys (OpenAI, Anthropic, Google, and others) to your account: when a request matches a model served by a provider you've configured, AnyRouter forwards the call on your key. You keep every AnyRouter feature — unified API surface, logs, analytics, presets, routing, app attribution — and pay the upstream provider directly at their list price.
## Overview [#overview]
AnyRouter supports two billing modes side by side, and you can mix them — BYOK for providers where you have an account, credits as a fallback for everything else:
| Mode | Who pays the upstream | Pricing | Best for |
| --------------------- | ----------------------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| Pay-as-you-go credits | AnyRouter | List price, no AnyRouter markup, billed against your credit balance | Quickly trying many models, no upstream accounts, single invoice |
| BYOK | You, on your provider account | Upstream list price, no AnyRouter markup | Existing provider credits, negotiated rates, preview-tier access, data residency |
The dashboard exposes BYOK for every provider AnyRouter routes to. Common ones include OpenAI, Anthropic, Google AI Studio, Groq, DeepSeek, Together AI, Mistral AI, DeepInfra, xAI, OpenRouter, Z-AI (Standard), and Z-AI Coding Plan. Enterprise endpoints such as Azure OpenAI, Amazon Bedrock, and Vertex AI accept structured credentials — the dialog shows the expected format when you select the provider.
AnyRouter never proxies your key to anyone other than the upstream provider it's scoped to.
## How it works [#how-it-works]
### Routing precedence: BYOK vs. managed capacity [#routing-precedence-byok-vs-managed-capacity]
When a request comes in, AnyRouter picks the upstream in this order:
1. **Per-request override.** If the request sends an `X-AnyRouter-BYOK-Key` header naming a specific BYOK key alias, that key is used (and only that key). If the named key is missing, disabled, or doesn't serve the requested model, the request fails — there's no silent fallback when you've explicitly named a key.
2. **`Always use` keys.** A BYOK key with the **Always use** toggle on is tried first for matching models. How it behaves when it fails depends on its mode (see [Always use modes](#always-use-modes)).
3. **Regular BYOK keys.** With **Always use** off (or when a Prefer pin falls back), AnyRouter selects one using the configured load-balancing strategy (below). If they all fail, the request falls through to managed capacity.
4. **Managed (pay-as-you-go) capacity.** If you have no BYOK key for the provider, or every BYOK attempt failed and no Strict **Always use** pin applies, AnyRouter bills credits.
The behavior is deterministic. You can always see which path was taken by checking the `byok_key_alias` field in the request log.
### Always use modes [#always-use-modes]
Turning on **Always use** for a key gives it a mode:
* **Prefer** (the default for a new pin) - the pinned key is tried first. If it fails with 401, 402, 429 or a 5xx error, AnyRouter moves on to your other keys and then managed capacity. Your other keys stay available as fallbacks.
* **Strict** - requests for matching models use only the pinned key. AnyRouter never bills platform credits and never uses the community pool for those models. If the key fails, the request fails (`byok_always_use_exhausted`). Choose this when you must never route anywhere else.
Under **Only these models** (collapsed by default) you can limit a pin to a list of model ids. Requests for other models ignore the pin. A pin is also skipped automatically for a model its provider cannot serve, so it never blocks a request it could not have answered.
**Automatic relax after repeated auth errors.** If a pinned key returns 401 or 402 three times in a row, AnyRouter relaxes the pin so a bad or empty key does not take your traffic down: a Strict pin becomes Prefer, and a Prefer pin is released. Any successful response resets the count. The key shows a notice such as "Pin relaxed to Prefer after repeated 401 errors. Re-enable to pin again." Fix the key, then save the pin again (turn Always use on, or press **Re-enable pin**) to clear the notice.
### Seeing why a request was routed [#seeing-why-a-request-was-routed]
When an Always use pin applies to the requested model, the response includes `anyrouter_metadata.routingReason`:
| Value | Meaning |
| ------------------------ | ----------------------------------------------------------------------------------------------- |
| `pinned:` | The pinned key served the request. |
| `pin_fallback:` | The pinned key failed with this HTTP status and another route served the request (Prefer mode). |
| `pin_skipped:unservable` | The pinned provider cannot serve this model, so the pin was skipped. |
The field is absent when no pin applies to the model.
### Load balancing multiple keys [#load-balancing-multiple-keys]
If you have more than one key for the same provider, the provider card exposes four strategies:
* **Fallback** — Try keys in priority order, lowest number first; move on to the next key on error. This is the default.
* **Round Robin** — Cycle through enabled keys evenly. Best when all keys have the same quota.
* **Weighted** — Distribute requests proportional to each key's **Weight** (a weight-2 key receives roughly twice as many requests as a weight-1 key).
* **Random** — Pick a key at random for each request.
Disabled keys are skipped by every strategy. Keys marked **Always use** are tried before other keys, regardless of strategy.
### Auto-routing models [#auto-routing-models]
Once you've added one or more keys, three built-in model ids route across **all** of them without naming a specific model:
| Model id | Routes to |
| ------------------ | ----------------------------------------------------- |
| `anyrouter/byok` | Every model your configured keys can reach |
| `anyrouter/coding` | The coding-capable subset |
| `anyrouter/agent` | The tool / function-calling subset (built for agents) |
Use them anywhere you'd use a normal model id:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "model": "anyrouter/coding", "messages": [{ "role": "user", "content": "Refactor this function" }] }'
```
AnyRouter picks the strongest available model first and falls back through the rest if a provider is busy. Add a key for a new provider and its models join the pool automatically — no config change. The candidate list always reflects your own keys plus your privacy and Zero Data Retention settings; anything your settings hide is left out of routing. Preview exactly which models each resolves to under **Dashboard → BYOK → Auto-routing models**. Because these only use your own keys, they never spend AnyRouter credits — and calling one with no matching keys returns a clear error telling you to add a key rather than silently falling back to paid capacity.
### Failure modes [#failure-modes]
BYOK puts you on the upstream provider's account, so their failure modes become yours. AnyRouter surfaces them as cleanly as possible:
| Failure | What AnyRouter does | Where you see it |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
| Invalid or revoked key | Returns the provider's `401`/`403` untouched; the key is marked **Unhealthy** after repeated auth failures. | Dashboard → BYOK → provider card, plus the request log |
| Rate-limited upstream | Returns the provider's `429` with their `Retry-After`. With more than one key and **Fallback** on, tries the next key before returning the error. | Request log, provider card status |
| Provider 5xx | Same as above — the next key in the pool is tried; if all fail, the upstream error is returned. | Request log |
| Quota exhausted | Treated as a 4xx from the provider; not retried on the same key. Round-robin and weighted strategies move on. | Request log |
| Key removed mid-request | In-flight requests already dispatched complete normally; new requests fall back per the precedence rules. | Dashboard banner on the BYOK page |
The dashboard surfaces a per-key health status (healthy / warning / unhealthy) computed from the last 24 hours of traffic. An **Unhealthy** key is automatically skipped by load balancing until it returns to healthy — it's not deleted, and you can re-test it from the dialog any time.
### Security [#security]
BYOK keys are sensitive credentials, and AnyRouter treats them that way:
* **Encrypted at rest with a per-row salt.** Each key is encrypted before storage using a salt unique to that row, so a leak of one key's ciphertext doesn't weaken any other.
* **Write-only in the UI.** After save, the dashboard shows only the alias and a masked identifier (for example `byok_01H…`). The full value can't be retrieved through the dashboard or API.
* **Never logged.** Provider keys are stripped from request and response logs; the alias and provider are preserved so you can still tell which credential served a request.
* **Live validation on save.** AnyRouter calls the provider once at save time to confirm the key is accepted, then discards the response.
* **No third-party sharing.** Your key is only ever sent to the upstream provider it's scoped to (or, where applicable, to Cloudflare AI Gateway acting as transport).
If you suspect a key is compromised, rotate it at the upstream provider and then delete the matching key in the BYOK dashboard. Issue a separate provider key per AnyRouter account so rotations stay isolated.
### The shared pool: donate a key or subscribe [#the-shared-pool-donate-a-key-or-subscribe]
The shared pool lets the community route requests through **donated** BYOK keys instead of AnyRouter's managed capacity. It's open to two groups:
* **Donors** — anyone who has flipped **Donate to pool** on at least one eligible key. Your key serves other users' requests, and you earn **up to 8% of the token cost** back as credits, automatically (8% on Pro, Pro+, and Max; 5% on Go or with no plan).
* **Paid subscribers** — anyone on the **Go ($2/mo)**, **Pro ($10/mo)**, **Pro+ ($45/mo)**, or **Max ($100/mo)** plan. This unlocks the pool for your own requests without you having to donate a key. Donating a key waives the Go subscription.
On the **Free** plan, you can't use the shared pool's free models until you donate at least one eligible key or subscribe to **Go**. Donating is opt-in per key and withdrawable instantly; your account keeps first call on its own key. See [/pool](https://anyrouter.dev/pool) and [/pool](https://anyrouter.dev/pool) for the full flow and live stats.
New to the pool? The [Share a Key to the Shared Pool](/features/shared-pool) guide walks you through creating an account and donating your first key step by step.
### Costs [#costs]
* AnyRouter **charges nothing** for BYOK traffic — no markup, no per-request fee, no minimum.
* You pay the upstream provider directly, on their billing cycle, at your account's list price.
* If you've configured an external gateway (for example Cloudflare AI Gateway) in front of your provider account, any fees it charges are between you and the gateway.
* Pay-as-you-go and BYOK usage are reported separately in **Dashboard → Usage** so you can attribute spend per source.
## Configure [#configure]
Adding a key takes a minute per provider. AnyRouter runs a live validation call on save; if it fails, the dialog surfaces the upstream error and the key isn't stored.
::::steps
:::step{title="Open the BYOK dashboard"}
Go to **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**. In the **Available** grid, click **Add key** on the provider you want. If you've already added a key for that provider, expand its card under **Configured** and use **Add key** there.
:::
:::step{title="Paste the key and options"}
Paste your provider API key into the **API key** field (write-only — never displayed again after save). Optionally set a **Key alias** (shows in logs), override the **Base URL** for regional/enterprise deployments, and set **Priority** / **Weight** if you'll run more than one key per provider. For Z-AI, pick the type matching your plan — Standard API (`https://api.z.ai/api/paas/v4`) or Coding Plan (`https://api.z.ai/api/coding/paas/v4`).
:::
:::step{title="Save and validate"}
Click **Save**. AnyRouter validates the key against the provider before storing it. Use **Add another key** in the dialog to seed a round-robin pool in one pass.
:::
::::
To scope a key to specific AnyRouter API keys (for example, isolating production from staging), open the provider card and edit the scope — requests made with keys outside that scope fall back to pay-as-you-go credits. To remove a key, expand the provider card and click the trash icon; in-flight requests finish on the old key, new requests fall back to the next key or managed capacity, and the value is purged from storage (no undo). Disabling a key first (toggle off **Enabled**) lets you watch traffic shift before deleting permanently.
For step-by-step instructions on generating a key at each provider — dashboard URLs, key formats, and gotchas — see the [BYOK Provider Setup Guide](/features/byok-providers).
## From the CLI [#from-the-cli]
```bash
anyr byok providers # providers that accept BYOK; shows which you have set up
anyr byok test openai # one real call with your saved key
echo $KEY | anyr byok test openai --key-stdin # check a key before saving it
anyr byok rm openai --yes # delete the saved key
```
`byok` needs a management key (`ANYROUTER_MANAGEMENT_KEY`) with the BYOK read and write permissions. `rm` asks first on a terminal; in a script pass `--yes`. `test` exits 1 when your provider rejects the key, and your key never appears in the output.
## Frequently asked questions [#frequently-asked-questions]
:::details{summary="Can I use the same provider key on multiple AnyRouter accounts?"}
Yes, but we don't recommend it. Each AnyRouter account stores its own encrypted copy, so rotating the key on the provider side means rotating it on every AnyRouter account.
:::
:::details{summary="Does BYOK work with streaming responses?"}
Yes. BYOK is transparent to the response shape — streaming, tool calls, multimodal input, and structured outputs work the same as with managed capacity.
:::
:::details{summary="Can I send the BYOK key inline on every request instead of saving it?"}
No. AnyRouter only accepts BYOK keys saved to your account; this is what lets us validate them, encrypt them, and log per-key health.
:::
:::details{summary="Where can I see which BYOK key served a request?"}
Every entry in **Dashboard → Logs** shows the provider, the BYOK key alias (or `managed` if AnyRouter capacity was used), and the upstream latency.
:::
---
# Prompt Caching (/features/caching)
Resending the same multi-thousand-token system prompt on every turn means paying full input price for tokens the model has already seen. AnyRouter supports **prompt caching** on every upstream that exposes it (Anthropic, OpenAI, DeepSeek, Gemini): cache a long prefix once and reuse it across thousands of requests, with cached tokens billed at a fraction of the normal input price.
## Overview [#overview]
Prompt caching pays off any time the same prefix is sent many times. Concrete wins:
| Use case | What to cache |
| ----------------- | ------------------------------------------------------------------ |
| System prompts | A multi-thousand-token persona or tool spec repeated on every turn |
| RAG | Retrieved documents that are the same for a batch of questions |
| Agent scaffolds | Long tool catalogs shared across every step of a chain |
| Few-shot examples | Static demonstrations pinned to the front of the prompt |
Rule of thumb: if a chunk is repeated across **two or more requests within five minutes**, cache it. The break-even vs. uncached input is typically 1.5–2 uses.
## How it works [#how-it-works]
### Anthropic caching [#anthropic-caching]
Anthropic supports breakpoint-style caching. Mark blocks with `cache_control`:
```typescript
const response = await client.messages.create({
model: "anthropic/claude-sonnet-4-6",
max_tokens: 1024,
system: [
{
type: "text",
text: LONG_SYSTEM_PROMPT,
cache_control: { type: "ephemeral" },
},
],
messages: [{ role: "user", content: "What's the policy on refunds?" }],
})
```
Cache reads are billed at 10% of input price; cache writes are billed at 125%. The cache TTL is 5 minutes.
### OpenAI automatic caching [#openai-automatic-caching]
OpenAI caches prompts ≥1024 tokens automatically — no request changes needed. The first request warms the cache; subsequent requests within the cache window pay reduced input pricing on cached tokens.
### Pricing [#pricing]
Every model's [detail page](https://anyrouter.dev/models) shows both standard and cache-read pricing. Cached tokens are reflected in the `usage` field of the response:
```json
{
"usage": {
"prompt_tokens": 5840,
"completion_tokens": 120,
"total_tokens": 5960,
"prompt_tokens_details": {
"cached_tokens": 5800
}
}
}
```
AnyRouter bills cached tokens at the provider's cache-read rate, pass-through — no extra AnyRouter markup on cache reads.
---
# How AnyRouter Chooses a Credential (/features/credential-selection)
Every request can be served three different ways: through **your own provider key**, through the **community shared pool**, or against your **AnyRouter credits**. This page is the map of how AnyRouter decides which one actually handles a given request, and what happens when the first choice doesn't work out.
## Overview [#overview]
| Source | Who pays the upstream | Requires |
| ----------------- | --------------------------------- | ----------------------------------------------------------------- |
| Your own BYOK key | You, at the provider's list price | Adding a key on the [BYOK](/features/byok) page |
| Shared pool | A donor's provider account | You've [donated a key](/features/shared-pool) or hold a paid plan |
| AnyRouter credits | AnyRouter | Nothing extra — this is the default |
Using your own key is always attempted before AnyRouter reaches for anything else. The shared pool is additional capacity available to donors and paid-plan accounts on top of that. Credits are the catch-all: if nothing else served the request, it's billed against your AnyRouter balance. **BYOK requests are never charged to your credits, whether they succeed or fail.**
## How it works [#how-it-works]
### Your own keys come first [#your-own-keys-come-first]
If you've added a BYOK key for the provider a request needs, AnyRouter tries it before anything else. You can also pin a single key for one request with the `X-AnyRouter-BYOK-Key` header, or mark a key **Always use** so it's the only thing tried for matching models. See [BYOK: Routing precedence](/features/byok#routing-precedence-byok-vs-managed-capacity) for the exact ordering and what happens if a pinned or always-use key isn't available.
### Choosing among several of your own keys [#choosing-among-several-of-your-own-keys]
When you have more than one key for the same provider, a per-provider strategy decides which one goes first:
* **Fallback** (default) — try keys in priority order; move to the next on error.
* **Round Robin** — cycle through enabled keys evenly.
* **Weighted** — send requests proportional to each key's configured weight.
* **Random** — pick a key at random per request.
You set this on the provider's card in **Dashboard → BYOK**. Full details in [BYOK: Load balancing multiple keys](/features/byok#load-balancing-multiple-keys).
### The shared pool [#the-shared-pool]
If you don't have a working key for the provider — or your own keys are temporarily unavailable — and you're eligible for the shared pool (you've donated a key, or you're on a paid plan), AnyRouter can route the request through a donated key from another user's account instead of billing your credits. See [Share a Key to the Shared Pool](/features/shared-pool) for eligibility and how donating works.
### Falling back to credits [#falling-back-to-credits]
If none of the above apply — no BYOK key, not eligible for the pool, or every attempt above failed — the request is billed against your AnyRouter credits at the provider's list price, with no AnyRouter markup. This is the same managed capacity used by accounts with no BYOK keys at all.
## What happens when a key fails or gets rate limited [#what-happens-when-a-key-fails-or-gets-rate-limited]
A single failed attempt doesn't fail the whole request. AnyRouter retries, and a retry can land on a different key of the same provider before the router gives up on that provider and moves to the next option in the order above.
* **Rate limited.** A key that gets rate limited by its provider is set aside for a short cooldown and skipped by the router, then automatically returns to rotation once the cooldown passes. Being throttled doesn't mark a key as broken.
* **Rejected.** A key the provider actively rejects (for example, an invalid or revoked credential) is parked the same way a rate-limited key is, so it stops being tried until you fix it.
* **Bad request.** If a request is rejected because of something in the request itself — not the credential — that isn't held against the key; the same key stays eligible for your next request.
For the general routing and failover mechanics that apply after a credential is chosen (candidate ordering, provider preferences, forcing a specific upstream), see [Smart Routing](/features/routing). For request-rate ceilings independent of any single key, see [Rate Limits](/features/rate-limits).
---
# Credits & Billing (/features/credits)
AnyRouter uses a **credit-based billing system**: your account holds a USD balance and each API request deducts its exact cost — pay-as-you-go usage has no fixed fee, you pay only for the tokens you actually use. Subscribing to a paid plan (Go, Pro, Pro+, or Max) does carry its own fixed monthly price on top of that, in exchange for included credits and higher limits; see [Pricing & Plans](/features/pricing).
## Overview [#overview]
Your billing settings are organized into three surfaces in the dashboard:
| Surface | What it covers |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| **Subscription** | Your plan — **Free**, **Go**, **Pro**, **Pro+**, or **Max**. Upgrade, see what's included, and manage your subscription. |
| **Pay as you go** | Your credit balance and usage — top up, view your monthly and top-up buckets, and review credits, invoices, and transaction history. |
| **Payment methods** | Saved cards for top-ups. Auto-charge (automatically topping up when your balance runs low) is opt-in and coming soon. |
A plan and pay-as-you-go credits work together: a subscription gives you included monthly credits and higher limits, while pay-as-you-go credits cover anything beyond that — and you can use pay-as-you-go on the Free plan without subscribing at all.
## How it works [#how-it-works]
### Two kinds of credits [#two-kinds-of-credits]
Your balance is made up of two separate buckets:
| Bucket | What it is | Expires? |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| **Monthly credits** | Credits AnyRouter issues you — your plan's monthly credit (Go $4, Pro $20, Pro+ $100, Max $300) and any referral or promotional bonuses. The Free plan includes none. | Topped up by AnyRouter; use them while you have them. |
| **Top-up credits** | Credits you purchase yourself from the Credits dashboard. | **Never expire** — they stay until used. |
**Usage is always charged to your monthly credits first**, and only spills into your purchased top-up credits once the monthly bucket is empty. So the credits AnyRouter gives you get spent before the credits you paid for, and your purchased balance lasts as long as possible.
Your **total balance** is simply the sum of the two buckets. The [Credits dashboard](https://anyrouter.dev/dashboard/credits) shows each bucket on its own card, plus a combined summary with your total balance and total spend ("burned") to date.
### Getting your first credits [#getting-your-first-credits]
The Free plan includes no monthly credit and no signup bonus. There are two low-cost ways to get started with included credits:
* **Subscribe to Go ($2/mo)** — includes **$4 in credits every month**, while free models (`anyrouter/free`) have no daily cap on any plan. The Free plan has no monthly credit.
* **Donate a provider key** to the [shared pool](/features/byok) — this auto-grants Go for free (same $4/mo included credit) and earns you up to 8% of the tokens your key serves (8% on Pro/Pro+/Max, 5% on Go).
You can also **top up** your balance any time from the [Credits dashboard](https://anyrouter.dev/dashboard/credits) and pay per token with no subscription, or use **BYOK** (your own provider keys never deduct credits).
### How costs are calculated [#how-costs-are-calculated]
Each API request is billed on the model's per-token pricing:
| Token type | Billed at | Notes |
| ------------------ | ----------------------------------------------------- | --------------------------- |
| Input tokens | Model's input price per 1M tokens | The prompt you send |
| Output tokens | Model's output price per 1M tokens | The completion you receive |
| Cached tokens | Provider's cache-read rate (typically 10% of input) | Tokens served from cache |
| Cache write tokens | Provider's cache-write rate (typically 125% of input) | First-time cache population |
Per-model pricing is on the [models page](https://anyrouter.dev/models); every model detail page shows both standard and cache pricing.
A worked example:
```
Model: anthropic/claude-sonnet-4-6
Input: 1,000 tokens @ $3.00/1M = $0.003
Output: 500 tokens @ $15.00/1M = $0.0075
Total: $0.0105
```
Cached tokens reduce costs significantly. If 800 of the 1,000 input tokens were cached:
```
Cached: 800 tokens @ $0.30/1M (10% of input) = $0.00024
Input: 200 tokens @ $3.00/1M = $0.0006
Output: 500 tokens @ $15.00/1M = $0.0075
Total: $0.00834 (20% savings)
```
### Topping up [#topping-up]
Top up your balance from the [Credits dashboard](https://anyrouter.dev/dashboard/credits). Purchases are processed via [Polar](https://polar.sh), and you can top up any amount — the preset amounts are just suggestions. Very small amounts may be declined by the payment processor; if so, try a larger amount.
**What you pay vs what you receive.** You choose a credit amount (the base). Polar, our Merchant of Record, charges that base plus a **5% + 50¢ processing fee**. AnyRouter keeps **$0** of that fee. Polar may add sales tax on the card depending on your country — tax is extra on the card, not extra credits, and not AnyRouter revenue.
Worked example — you top up **$10.00**:
| | Amount |
| ------------------------------- | -------------------------------------------- |
| Credits you receive | **$10.00** |
| Polar processing fee (5% + 50¢) | $1.00 — goes to Polar |
| Charged by Polar before tax | **$11.00** |
| AnyRouter's take of the fee | **$0** |
| Polar MoR sales tax | extra on the card, if any; not extra credits |
Purchased credits land in your **top-up** bucket and **never expire** — they stay on your balance until used, and are only spent after your monthly credits run out. Polar also emails a receipt to the checkout address (credits added, Polar processing fee, AnyRouter keeps $0). Turn billing emails off under [Notifications](https://anyrouter.dev/dashboard/settings#notifications).
If a request would drop your balance below zero, the API returns `insufficient_balance` and the request is **not** processed — no tokens are consumed and no charge is applied. Top up and retry.
```json
{
"error": {
"code": "insufficient_balance",
"message": "Insufficient credits. Your balance is $0.00. Please purchase credits at /dashboard/credits, add your own provider key (BYOK) at /dashboard/byok, subscribe to a plan at /pricing, or donate a free key to the shared Pool at /dashboard/byok/new?donate=1. Free models and anyrouter/free still work.",
"metadata": {
"type": "insufficient_credits"
},
"actions": [
{ "type": "top_up", "label": "Top up credits", "url": "https://anyrouter.dev/dashboard/credits?topup=1" },
{ "type": "add_byok", "label": "Add your own provider key (BYOK)", "url": "https://anyrouter.dev/dashboard/byok" },
{ "type": "subscribe", "label": "Subscribe to a plan", "url": "https://anyrouter.dev/pricing" },
{ "type": "donate_key", "label": "Donate a free key to the shared Pool", "url": "https://anyrouter.dev/dashboard/byok/new?donate=1" },
{ "type": "use_free_models", "label": "Keep going on free models", "url": "https://docs.anyrouter.dev/features/free-tier", "model": "anyrouter/free" }
]
}
}
```
`error.actions` lists what to do next, so a client or agent can show the options without parsing the message.
### Free requests for new accounts [#free-requests-for-new-accounts]
A new account gets **5 free requests** on paid models, once, so you can try one before adding credits. Each successful request counts down from 5. A request that fails, or that you cancel, does not count. Free models (including `anyrouter/free`) never use them. A request that runs on your own provider key (BYOK) does not use them either.
Free requests cover listed models priced at or under $3 per 1M input tokens and $15 per 1M output tokens. The `x-anyrouter-free-requests-remaining` response header shows how many are left, and the [Credits page](https://anyrouter.dev/dashboard/credits) shows the same count. Once you have bought or spent credits, free requests no longer apply.
When they run out, the next paid-model request returns `insufficient_balance` with the `actions` above.
### Checking your balance [#checking-your-balance]
In the dashboard, the [Credits page](https://anyrouter.dev/dashboard/credits) shows your monthly and top-up credits on separate cards, a combined summary (total balance, total spend, and today's cost), and a full transaction history.
Programmatically, check your balance from the API:
```bash
curl https://anyrouter.dev/api/v1/credits \
-H "Authorization: Bearer sk-ar-your-key"
```
```json
{
"balance": 12.53,
"monthly_balance": 8.0,
"topup_balance": 4.53,
"used": 12.47,
"today_cost": 0.42,
"currency": "usd"
}
```
`balance` is the total (`monthly_balance + topup_balance`). `monthly_balance` is the AnyRouter-issued bucket spent first; `topup_balance` is your purchased bucket spent after.
List all credit transactions (purchases, usage, grants, refunds):
```bash
curl "https://anyrouter.dev/api/v1/credits/transactions?limit=20" \
-H "Authorization: Bearer sk-ar-your-key"
```
Each transaction includes:
| Field | Description |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type` | `purchase` (top-up), `usage`, `signup_bonus` (ledger type only — not issued to new signups while disabled), `pro_credit` (monthly Go/Pro/Pro+/Max plan credit), `refund`, `admin_grant`, `bonus` |
| `amount` | Positive for credits added, negative for usage deducted |
| `model_id` | The model used (for `usage` transactions) |
| `balance_after` | Your balance after this transaction |
### Usage in the response [#usage-in-the-response]
Every chat completion response includes a `usage` object so you can track costs client-side:
```json
{
"usage": {
"prompt_tokens": 5840,
"completion_tokens": 120,
"total_tokens": 5960,
"cached_tokens": 5800
}
}
```
Use these values with the model's published pricing to compute the request cost in your own code.
## Configure [#configure]
* **[Credits dashboard](https://anyrouter.dev/dashboard/credits)** — view buckets, top up, and browse transaction history.
* **Subscription** — the monthly plan credit (Go, Pro, Pro+, or Max) is granted automatically on activation and each renewal (idempotent, so a retried payment never double-grants). It lands in your monthly-credits bucket.
BYOK requests never deduct AnyRouter credits, on any plan — you pay the upstream provider directly at their list price, with no AnyRouter fee on top. See [BYOK](/features/byok).
---
# Dashboard Overview (/features/dashboard-overview)
The dashboard home is one screen: whether anything needs you, three numbers for the last 7 days, and the last five requests. Charts, heatmaps, and the full plan picker live on Usage and Credits — home stays an operations snapshot, not a settings page.
If your account has **no plan and no credits**, a welcome banner at the top asks you to pick a plan (with the same Go benefits shown on [Pricing](/features/pricing)) or [donate a key](https://anyrouter.dev/pool) to unlock Go for free. The banner hides as soon as you have a plan or any credits.
## Overview [#overview]
Every panel reflects the workspace selected in the sidebar. Switch workspaces and the page refreshes against the new scope.
1. **Status sentence** — "does anything need me?" Empty balance, missing API key, or no traffic yet, with a link to fix it. When nothing is wrong, it says so.
2. **Three numbers** — requests, errors, and spend for the last 7 days, each with today's total underneath.
3. **Last five requests** — status, time, model, latency, and cost. Click a row to open it in Logs.
If you have not created an API key yet, a quick-start guide appears under those three blocks. The welcome banner (pick a plan or donate a key) only shows while you have no plan and no credits.
| Panel | Answers | Deeper view |
| ------------------ | ----------------------------------------------------------- | ------------------------------------------------------------ |
| Status | "Do I need to add a key, credits, or send a first request?" | [API keys](/features/api-keys), [Credits](/features/credits) |
| Requests / spend | "Is volume or cost off today?" | [Usage Explorer](/features/usage-explorer) |
| Errors | "Are calls failing?" | [Request Logs](/features/request-logs) |
| Last five requests | "What did this specific request do?" | [Request Logs](/features/request-logs) |
## How it works [#how-it-works]
### Status sentence [#status-sentence]
Home reads your keys, credit balance, and recent traffic. It surfaces the most urgent item (for example an empty balance that would 402 paid requests) and a count of anything else. A healthy account reads **Nothing needs you.**
### Three numbers [#three-numbers]
The three tiles summarize the last **7 days** for the active workspace:
| Tile | Value | Detail |
| ------------ | ------------------------------- | ----------------------------------------------------------------------------- |
| **Requests** | Terminal requests in the window | Today's count |
| **Errors** | Failed requests in the window | Today's failures. Opens Logs filtered to errors when the count is above zero. |
| **Spend** | Billable spend in USD | Today's spend |
Counts include successes and post-selection errors. Retries that resolve internally fold into the parent and are not double-counted. For other ranges (today, 30 days, 90 days, custom) or day/week/month grouping, open the [Usage Explorer](/features/usage-explorer). Heatmaps and weekly model charts also live there, not on home.
### Last five requests [#last-five-requests]
The table lists the five most recent requests — status, time, model (with logo), latency, and estimated cost. Click a row to open it in [Request Logs](/features/request-logs). A brand-new workspace shows an empty feed. This list never shows prompt or completion contents; open the log for payloads, subject to [Privacy Controls](/features/privacy-controls).
## Configure [#configure]
### Populate an empty dashboard [#populate-an-empty-dashboard]
A just-created workspace shows zeros and an empty request list — that's expected. To start populating it:
1. Open the **Playground** from the page header and send a test request. It appears in the last-five list within a few seconds.
2. Or follow the [Quickstart](/getting-started/quickstart) to mint an API key and send a real request from your own code:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-..." \
-H "Content-Type: application/json" \
-d '{
"model": "z-ai/glm-4.7-flash",
"messages": [{ "role": "user", "content": "Hello" }]
}'
```
3. Refresh the dashboard. The Requests and Spend tiles update, and the request shows up in the last-five list.
## Frequently asked questions [#frequently-asked-questions]
:::details{summary="Why is my dashboard spend higher than the upstream provider's price list?"}
AnyRouter bills managed (non-BYOK) requests at the provider's list price — no AnyRouter markup. A common reason the total looks higher than a flat input/output estimate is cache-write tokens, billed at roughly 1.25× the input rate the first time a prompt populates the provider's cache. The [Usage Explorer](/features/usage-explorer) breaks spend down per request so you can see exactly what was charged.
:::
:::details{summary="Where did the heatmap and weekly charts go?"}
They live on **Usage**. Home is the one-screen snapshot; Usage is the place to explore ranges, models, and trends.
:::
:::details{summary="Where do I pick a plan?"}
On **Credits** (user menu → balance / Credits), or from the welcome banner when your account has no plan and no credits. Home does not show the full Go / Pro / Max checkout grid.
:::
:::details{summary="Where do I see per-request details?"}
Click any of the last five rows, or open **Logs** in the sidebar. [Request Logs](/features/request-logs) exposes prompt and completion bodies (subject to your [privacy controls](/features/privacy-controls)), headers, routing decisions, retries, and the full cost breakdown.
:::
## Related [#related]
* [Usage Explorer](/features/usage-explorer) — build custom cost, token, and latency charts.
* [Request Logs](/features/request-logs) — inspect individual requests behind any metric.
* [Privacy Controls](/features/privacy-controls) — what's stored versus what's shown.
* [Managing API Keys](/features/api-keys) — per-key usage and limits.
* [Credits](/features/credits) — balance, top-ups, and plans.
---
# Free-tier Aggregation (/features/free-tier-aggregation)
Every provider hands out a free tier, but each has its own key, endpoint, and quota — so squeezing real capacity out of them means juggling a drawer of keys by hand. Free-tier aggregation collapses that into one OpenAI-compatible endpoint: bring your free keys from Groq, Cerebras, SambaNova, NVIDIA, Mistral, OpenRouter, GitHub Models, Cohere, Z.ai, and more, and each request routes to a key that still has budget — with automatic cooldown when a provider rate-limits you and a clean fall-through when one is exhausted.
## Overview [#overview]
You combine provider branches into a **preset** (a small routing tree) and call it like any other model:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "@presets/duyet-coding",
"messages": [{"role": "user", "content": "Hello"}]
}'
```
Embeddings work the same way:
```bash
curl https://anyrouter.dev/api/v1/embeddings \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{ "model": "@presets/my-embeddings", "input": "..." }'
```
The preset's `kind` must match the endpoint (`chat` vs `embedding`). Mismatches return `400 preset_kind_mismatch`.
## How it works [#how-it-works]
### Key balancing and cooldown [#key-balancing-and-cooldown]
When you send `model: "@presets/"` to `/api/v1/chat/completions` or `/api/v1/embeddings`, the resolver picks a branch, picks a key inside that branch using your per-provider key strategy (round-robin, weighted, fallback, or random — set on the BYOK page), and dispatches the request.
If the chosen key returns `429`, it's placed on a 120-second cooldown (or longer if the provider sends a `Retry-After` header) and the next request automatically tries the next key. If every key in every branch is exhausted, the request gets a `429` — unless you've turned on **Paid fallback**.
### Paid fallback [#paid-fallback]
Each preset has a **Paid fallback** switch. When **off** (default), exhaustion returns `429`. When **on**, the preset appends a synthetic "anyrouter platform" branch at the lowest priority — when every BYOK branch is unavailable, the request is served on platform credits instead. Costs land on the same `cost_usd` column you already see in the dashboard.
### Reading the dashboard [#reading-the-dashboard]
The **Capacity** card at the top of every preset page shows three independent meters:
| Meter | What it measures |
| ---------- | ----------------------------------------------------------------------------------- |
| Tokens | Sum of `daily token budget − tokens used today` across every BYOK key in the preset |
| Requests | Same shape for request counts |
| Platform $ | Spend vs. budget when the preset has a paid-fallback branch |
A status row collapses every key into four dots:
* `●` healthy
* `◐` cooldown (`Retry-After` seconds remaining)
* `○` exhausted (≥95% of daily budget used)
* `✕` invalid (provider rejected the key on the last probe)
A countdown row shows the next reset for each window kind, so you know when capacity comes back.
### Re-probing [#re-probing]
Limits change — providers raise free tiers, you upgrade your plan, your weekly window rolls over. Click **Probe all keys** on the dashboard to re-run every probe concurrently. Each probe has a 3-second budget so the page stays responsive; results land on D1 and the meters refresh on the next view.
You can also re-probe a single key via `POST /api/v1/byok/probe` with `{ "key_id": "byok_..." }`.
## Configure [#configure]
::::steps
:::step{title="Add your provider keys"}
Open **Dashboard → BYOK**, click **Add key**, choose a provider, paste the API key, give it a label, and click **Validate & Add**. AnyRouter validates the key against the provider and shows detected limits inline:
* **Z.ai** — 5-hour token window plus per-minute request rate
* **Anthropic** (Claude OAuth) — 5-hour, 7-day, and per-model windows
* **DeepSeek** — USD credit balance
* **OpenRouter** — USD credit balance and key-level rate limit
* **OpenAI** — daily request and token counts (admin keys only)
* **Groq / Cerebras / SambaNova / NVIDIA / Mistral / Cohere / GitHub Models** — per-minute request and token budget read from response headers
If a provider doesn't expose usage data, the key is saved as **Unvalidated** with an amber badge. You can fill in limits manually under **Advanced** on the same form — but you don't have to.
:::
:::step{title="Build a preset"}
A preset is a routing tree — `Preset → [Branch 1, Branch 2, …] → BYOK keys (auto-derived)`. Each branch is a `(model, upstream)` pair; the same model can appear twice via different upstreams (for example `z-ai/glm-4.7-flash` via your Z.ai key in one branch and the same AnyRouter model id via your OpenRouter key in another).
Open **Dashboard → Presets → New**. For each branch: pick a model, pick the upstream (for example *Z.ai Standard API*, *Z.ai Coding Plan*, *OpenRouter*, or *AnyRouter platform credits*), and set a **weight** (used in `weighted` mode) or **priority** (used in `fallback` mode).
Switch modes on the root:
* **Fallback** — try Branch 1 first, only move to Branch 2 if every key in Branch 1 is cooled down or exhausted.
* **Weighted** — distribute requests across branches by weight (e.g. 70/20/10).
:::
:::step{title="Call the preset"}
Send `model: "@presets/"` to `/api/v1/chat/completions` or `/api/v1/embeddings`, and the resolver handles branch and key selection for you.
:::
::::
## What's not included [#whats-not-included]
A few capabilities are deliberately out of scope:
* **OAuth-subscription tiers** (Claude Code, GitHub Copilot, Cursor) — they're typically tied to subscriber terms that don't allow third-party intermediation.
* **Sticky sessions** — every turn re-resolves the preset; if you need turn-to-turn affinity, pin a single model in your request instead.
* **Background probing** — probes run when you open the dashboard, not on a cron.
---
# Free Tier (/features/free-tier)
`anyrouter/free` is a built-in model id that routes your request to the best free model available at the moment — no provider keys, no credit deduction. There is no per-account daily cap, on any plan.
**Paid models need credits.** New accounts get [5 free requests](/features/credits#free-requests-for-new-accounts) on paid models. After that, a paid model returns `402` `insufficient_balance`. Add credits, add your own key or subscribe.
## Overview [#overview]
| | Free tier (`anyrouter/free`) | Paid models | BYOK |
| ----------------- | -------------------------------- | ------------------------ | ----------- |
| Plan required | Any (signed in) | Any (pay per token) | Any |
| Credits deducted | No | Yes | No |
| BYOK key required | No | No | Yes |
| Daily cap | None | None | None |
| Per-minute cap | 2400 req/min per key, every plan | 600–1800 req/min by plan | 600 req/min |
Per-minute caps are maintained on the [Rate Limits](/features/rate-limits#tiers) page and may change over time.
## How it works [#how-it-works]
### What it includes [#what-it-includes]
* **No BYOK required.** You don't need to add any upstream provider keys.
* **No credit deduction.** Requests to `anyrouter/free` don't deduct from your credit balance — your balance is unaffected.
* **Automatic fallback.** If the first free model is unavailable or rate-limited, AnyRouter falls back to the next candidate automatically.
### No daily cap [#no-daily-cap]
Free-tier requests have no per-account daily cap, on any plan. `anyrouter/free` and all `:free`-suffixed models draw on the same generous limit of **2400 requests per minute per API key**, the same on every plan. Free-model requests do not count toward Go's 5-hour request window.
Responses carry `X-RateLimit-Tier: free-model` and `X-RateLimit-Limit: 2400`. If you do go over, the API returns `429` `rate_limit_exceeded` with `Retry-After` set to the seconds to wait. See [Rate Limits](/features/rate-limits).
### Making a request [#making-a-request]
Use `anyrouter/free` exactly like any other model id:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anyrouter/free",
"messages": [{ "role": "user", "content": "Hello, what model am I talking to?" }]
}'
```
The response includes the resolved model id in `model`, so you can see which free model served the request. You can also see it in [Dashboard → Logs](https://anyrouter.dev/dashboard/logs). See the full routing details and current candidate list at [/model/anyrouter/free](https://anyrouter.dev/model/anyrouter/free).
### Faster first token [#faster-first-token]
On streaming requests, if the first free model stays silent for about 2.5 seconds, `anyrouter/free` starts one more free model in parallel. The first one to send a token answers, the other is cancelled, and only one request counts. See [Parallel upstreams](/features/routing#parallel-upstreams-for-anyrouterfree-and-anyrouterauto) for the response header and how to turn it off.
### `:free` models fall back to `anyrouter/free` [#free-models-fall-back-to-anyrouterfree]
If every route for a `:free` model fails, AnyRouter answers through `anyrouter/free` instead of returning an error. The response then carries `x-anyrouter-fallback: anyrouter/free`, and the `model` field names the free model that actually answered. Pin a different model, or check that header, if you need that exact model.
## Configure [#configure]
If free models aren't enough, you have four ways to grow beyond the free tier:
::::steps
:::step{title="Add credits"}
[Top up your balance](https://anyrouter.dev/dashboard/credits) and call any paid model.
:::
:::step{title="Subscribe to Go, Pro, Pro+, or Max"}
The [Go plan](/features/pricing) ($2/mo) adds $4 monthly credits. [Pro](/features/pricing) ($10/mo) and above add larger monthly credits and higher rate limits. See [Credits & Billing](/features/credits) for how the plan credit works.
:::
:::step{title="Add BYOK keys"}
Configure your own upstream provider keys on the [BYOK page](https://anyrouter.dev/dashboard/byok) and use `anyrouter/byok` for unlimited routing through your own accounts. BYOK requests never deduct credits.
See [Free capacity with your own keys](/guides/free-capacity-with-your-own-keys) for providers with a free tier.
:::
:::step{title="Use specific free models"}
Individual `:free`-suffixed models (for example `meta-llama/llama-3.3-70b:free`) share the same limits but let you pin a specific model.
:::
::::
## Frequently asked questions [#frequently-asked-questions]
:::details{summary="Is there a daily limit on free models?"}
No. `anyrouter/free` and `:free` models have no daily cap on any plan. The only limit is 2400 requests per minute per API key.
:::
:::details{summary="Can I tell which model served my request?"}
Yes. The `model` field in the response body contains the resolved model id. You can also see it in [Dashboard → Logs](https://anyrouter.dev/dashboard/logs).
:::
:::details{summary="Is anyrouter/free available on all plans?"}
Yes. Every signed-in plan can use it with no daily cap. Paid models still need credits.
:::
---
# Latest Model Aliases (/features/latest-model-aliases)
Models get new versions often, and older ones get retired. If your code pins `google/gemini-3.5-flash`, you have to edit it every time a newer Flash ships or the old one goes away. A **latest alias** does that for you: send `google/gemini-flash-latest` and AnyRouter calls the newest Gemini Flash model that is currently available.
## How it works [#how-it-works]
* Each alias points at a model family, for example Gemini Flash or Claude Sonnet.
* AnyRouter picks the newest model in that family that can serve your request right now.
* If that model is unavailable (retired, rate limited, or not reachable with your keys), AnyRouter automatically tries the next newest model in the family.
* The response `model` field tells you which concrete model answered, and you are billed at that model's price.
## Available aliases [#available-aliases]
Aliases appear in the [models catalog](https://anyrouter.dev/models) alongside other models, marked **latest → ** with the model each one currently points to, and each has its own page (for example [/model/google/gemini-flash-latest](https://anyrouter.dev/model/google/gemini-flash-latest)) showing the current model and the family's fallback order. `GET /api/v1/models` also lists them: each alias entry has `alias: true`, `resolves_to` (the current model id), and `family`, and carries that model's context length and pricing. `GET /api/v1/models/` returns the same entry.
| Alias | Family |
| --------------------------------- | ------------------- |
| `google/gemini-flash-latest` | Gemini Flash |
| `google/gemini-flash-lite-latest` | Gemini Flash-Lite |
| `google/gemini-pro-latest` | Gemini Pro |
| `google/gemma-latest` | Gemma |
| `deepseek/deepseek-latest` | DeepSeek (flagship) |
| `deepseek/deepseek-flash-latest` | DeepSeek Flash |
| `anthropic/claude-opus-latest` | Claude Opus |
| `anthropic/claude-sonnet-latest` | Claude Sonnet |
| `anthropic/claude-haiku-latest` | Claude Haiku |
| `openai/gpt-latest` | GPT (flagship) |
| `openai/gpt-mini-latest` | GPT mini |
| `openai/gpt-nano-latest` | GPT nano |
| `qwen/qwen-max-latest` | Qwen Max |
| `qwen/qwen-plus-latest` | Qwen Plus |
| `qwen/qwen-flash-latest` | Qwen Flash |
| `z-ai/glm-latest` | GLM |
| `z-ai/glm-flash-latest` | GLM Flash |
| `moonshotai/kimi-latest` | Kimi |
| `x-ai/grok-latest` | Grok |
## Example [#example]
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/gemini-flash-latest",
"messages": [{ "role": "user", "content": "Hello" }]
}'
```
The response includes the model that served it, for example `"model": "google/gemini-3.8-flash"`.
## When to pin instead [#when-to-pin-instead]
Latest aliases can move to a new model without notice, which may change output style, pricing, or supported parameters. For production workloads that need stable behavior, pin a concrete model id and upgrade on your own schedule. If you send your own `models` fallback list, AnyRouter uses yours instead of the family list.
---
# Local Relay — Serve Models From Your Own Machine (/features/local-relay)
Local relay turns a computer you own into an AnyRouter backend. A small CLI on your machine opens an outbound connection to AnyRouter; the gateway forwards matching chat-completion requests down that connection to a model server running locally — Apple's on-device Foundation Model via `fm serve`, Ollama, LM Studio, or anything else that speaks the OpenAI chat-completions API — and streams the response back. To your application it looks like any other model behind the same API key.
## What it's for [#what-its-for]
* **Apple Foundation Models.** Route to Apple's on-device model straight from your Apple Silicon Mac, through the normal `apple/foundation-model` catalog entry — no separate integration.
* **Any local model server.** Ollama, LM Studio, llama.cpp, vLLM — if it exposes an OpenAI-compatible `/v1/chat/completions` and `/v1/models`, the relay can serve it.
* **$0 billing.** The compute and the model are yours, so requests served by your own device are never billed against your credits — the same policy as BYOK.
* **Donate idle capacity.** Once your device is paired, you can also opt it into the shared pool: when your own capacity is idle, other users' matching requests can route to it, and you earn credits for every one served.
## Quickstart [#quickstart]
::::steps
:::step{title="Install the CLI"}
```
anyr relay start
```
The first run signs you in (or reuses your existing AnyRouter key) and pairs this device automatically — no separate pairing step required.
:::
:::step{title="Start the relay"}
```
anyr relay start
```
You don't need a second terminal for the on-device models. If a model you chose isn't running yet, the relay starts it for you:
* **Apple Foundation Models**: runs `fm serve`, a loopback-only OpenAI-compatible server on `http://127.0.0.1:1976`.
* **PPLX Decider Flash v1.2**: opens the Perplexity Decider for Mac app, which serves `http://127.0.0.1:8080/v1/decisions`. The app's first launch downloads its model (about 5.8 GB) before it answers.
The status screen marks these servers `(managed)`. The relay restarts one that crashes and stops them when you quit it. If you already started a server yourself, it shows as `(external)`. The relay uses it as is and leaves it running when you quit. Add `--no-launch` to use only servers that are already running.
Ollama (`http://localhost:11434`) and LM Studio (`http://localhost:1234`) are detected too. Start them yourself before you start the relay.
The relay then opens an outbound connection to AnyRouter. Nothing needs to be exposed: your device dials out, like a browser, so there's no port forwarding and no public IP.
:::
:::step{title="Call it through the normal API"}
Use your regular `sk-ar-...` key and call `apple/foundation-model` (or your local model's id) exactly like any other model:
```
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "apple/foundation-model", "messages": [{"role": "user", "content": "hi"}]}'
```
:::
::::
## PPLX Decider on your Mac [#pplx-decider-on-your-mac]
`perplexity/pplx-decider-flash-v1.2` (PPLX Decider Flash v1.2) is Perplexity's on-device decision model. It runs in the Decider for Mac app on your own Mac and answers the [Decisions API](/api-reference/systemone), not chat completions. Only your own Mac serves your own requests: the Decider is never offered to the shared pool, even with `--pool`.
**Install.** Get the Decider for Mac app from Perplexity's own documentation, [Decider for Mac](https://docs.perplexity.ai/docs/decisions/decider-for-mac), and put it in `/Applications`. AnyRouter does not host or link a download. The first launch downloads about 5.8 GB before the model answers.
**Start.**
```
anyr relay start
```
The relay opens the Decider app for you if it is installed but not running, and leaves it alone if it is already running. The app serves on `127.0.0.1:8080`; there is nothing to configure. Pass `--no-launch` to use only servers that are already running. Then send a decision request with your normal key and the model id `perplexity/pplx-decider-flash-v1.2`.
**Troubleshoot.** Run:
```
anyr relay doctor
```
The "Decider app" check tells you what to do next:
| Result | Meaning | Fix |
| ----------------------------- | ------------------------------------------ | ---------------------------------------------------------------- |
| Answering on `127.0.0.1:8080` | The Decider is running and served | Nothing to do |
| Installed, not running | The app is in `/Applications` but not open | `anyr relay start` launches it, or open the Decider app yourself |
| Not installed | The app is not in `/Applications` | Install it from the link above |
| Needs a Mac | The Decider only runs on macOS | Serve Ollama or LM Studio, or pass `--target ` |
If the app is still downloading its model, `anyr relay start` does not wait for it; the relay keeps watching and serves the Decider once it answers.
## Donate to the shared pool [#donate-to-the-shared-pool]
Run the relay with `--pool` to opt your device into the shared pool:
```
anyr relay start --pool
```
Once enabled, other users' matching requests can route to your device when your own capacity is idle. Every request it serves earns you credits, the same rebate model as [donating a BYOK key](/features/shared-pool). You can see how many client machines are currently online and which models they're serving on the [local relay page](https://anyrouter.dev/relay) — it's a public, aggregate-only view (no device names or user data).
Manage pool sharing anytime from **[Dashboard → Devices](https://anyrouter.dev/dashboard/devices)**: toggle donation on or off per device, see which models it's advertising, and track requests served and credits earned.
## Honest constraints [#honest-constraints]
* **4096-token context.** Apple's on-device model has a hard context ceiling — shared across system instructions, tool schemas, and the transcript. This is a framework limit, not a marketing number.
* **Your machine has to be online.** If your device is asleep or disconnected, AnyRouter detects it within seconds and fails over to your other configured upstreams for that model — requests don't hang waiting on an offline device.
* **$0 for your own device.** Requests served by your own paired device are never billed against your credits.
## Security [#security]
* Pairing mints a one-time-shown token; only its hash is ever stored. Revoke it anytime from **Dashboard → Devices** — the device loses access immediately.
* The relay only forwards a single, fixed chat-completion path down the connection. It has no access to your files, shell, or anything else on your machine.
* Your device only ever dials out — there's nothing to expose to the internet and nothing for it to scan.
## Frequently asked questions [#frequently-asked-questions]
:::details{summary="Do I need a public IP or open ports?"}
No. The relay only makes an outbound connection, like a browser does. There's nothing to forward and nothing exposed.
:::
:::details{summary="Who can route requests to my device?"}
Only you, unless you opt into the shared pool with `--pool`. Even then, no one can see your device's name or identity — pool routing is anonymous on both sides, and you can turn it off instantly.
:::
:::details{summary="What happens if my device goes offline?"}
AnyRouter detects it within seconds and fails over to your other configured upstreams for that model. Your application sees a normal response from the fallback, not a timeout.
:::
:::details{summary="How is relay usage billed?"}
At $0 for your own device — the model and the compute are yours. If a pool donor's device serves your request instead (only relevant when your own device is offline and the pool has a match), normal model pricing for that model applies.
:::
## Related [#related]
* [Bring Your Own Key (BYOK)](/features/byok) — attach your own provider keys
* [Share a Key to the Shared Pool](/features/shared-pool) — the same credit-earning model for donated provider keys
* [Local relay landing page](https://anyrouter.dev/relay) — live pool availability and quickstart
---
# Models Catalog (/features/models-catalog)
Picking a model shouldn't mean cross-referencing a dozen provider pricing pages. The **Models** page is the single source of truth for models you can call through AnyRouter — what's available with your current keys and settings, what each model can do, how much it costs, and which models your workspace has actually used. Open it from the dashboard sidebar, the public [/models](https://anyrouter.dev/models) catalog, or [/dashboard/models](https://anyrouter.dev/dashboard/models).
By default the list hides models you cannot reach (for example Anthropic Sonnet when you have no Anthropic or OpenRouter BYOK key). Turn on **Show all models** (or add `?all=1` to the URL) to browse the full catalog. The same rule applies to `GET /api/v1/models`.
## Overview [#overview]
Each row in the catalog represents one model and exposes the fields you need to decide without leaving the page:
| Field | What it tells you |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Model id** | The canonical string you pass in the `model` field (for example `openai/gpt-5.4-mini`, `anthropic/claude-sonnet-4-6`, `z-ai/glm-4.7-flash`). This is what matters in code. |
| **Display name** | A human-friendly label rendered alongside the id when one is set. |
| **Owner organization** | The company that trained and publishes the model (OpenAI, Anthropic, Google, Meta, Mistral, Z-AI, and so on), shown as a logo and name so you can scan by vendor. |
| **Provider badge** | The upstream that serves the request. AnyRouter ships a single unified gateway badge, so you don't need to pick a provider to start using a model. |
| **Context window** | Maximum total tokens (prompt plus response) per request. Larger windows let you pass more, but tokens still count toward your bill. |
| **Max output** | Maximum tokens the model can emit in one response. A `max_tokens` set higher is clamped down silently for you. |
| **Pricing** | Input and output cost, in **US dollars per 1 million tokens**. Free-tier models show `Free`. |
Context limits are shown in raw token counts so you can compare exact limits across vendors without doing your own conversion.
## How it works [#how-it-works]
### Pricing [#pricing]
Prices are US dollars per 1M tokens, broken into **Input** (prompt tokens you send) and **Output** (completion tokens returned). A request using 12,000 prompt tokens and 800 output tokens against a `1.00 / 4.00` model costs `12000/1_000_000 * 1.00 + 800/1_000_000 * 4.00 ≈ $0.0152` — the same arithmetic AnyRouter does at billing time, with cache-hit and cache-write rates applied on top when supported (see [Prompt Caching](/features/caching)). Where a model supports differentiated cache rates, those appear as smaller rows under the headline price.
Prices shown are the all-in rate you're billed per token. There are no separate per-request fees on top.
### Capabilities [#capabilities]
Every model exposes a row of capability chips so you know what it supports before you write code:
* **Tools / function calling** — accepts a `tools` array and returns tool calls.
* **Vision** — accepts image content parts (`image_url` or base64 data URLs).
* **Structured output** — honors `response_format: { type: "json_schema", ... }` and returns a conforming payload.
* **Streaming** — can be invoked with `stream: true` to receive server-sent events.
* **Prompt caching** — can cache long prompt prefixes between requests (see [Prompt Caching](/features/caching)).
* **Multilingual** — rated strong on non-English inputs.
A missing capability is grayed out, so you don't have to guess.
### "Active in your workspace" filter [#active-in-your-workspace-filter]
By default the catalog already hides models you cannot call. The **Active in your workspace** filter narrows the remaining list to models you're actually calling. A model counts as **active** when, in the last 30 days, any of these is true:
* A request from one of your API keys was successfully routed to the model.
* A preset in your workspace references the model.
* The model was selected as the default for an API key.
The "last 30 days" window matches the rolling usage window used elsewhere in the dashboard (Usage, Logs, Credits). It's useful for auditing which models a team actually uses, mirroring the **Usage** breakdown, and trimming presets. Toggle it off any time to discover something new — for example a smaller, cheaper variant of a model you already use. It updates within a few minutes of a successful request.
### Privacy badges [#privacy-badges]
Each row includes a **privacy tier** badge for its serving provider:
* **Zero-retention** — the provider doesn't log prompts or completions beyond what's required to serve the request.
* **Short-retention** — limited retention (typically for abuse detection), with no training on your data.
* **Standard** — the provider may retain data longer and may use it for service improvements unless you opt out.
The badges are advisory — they reflect the upstream's public commitments at the time the model was added. If you have hard requirements, use **Privacy controls** to block providers you don't want.
See [Privacy Controls](/features/privacy-controls) for the full provider list, how blocks are enforced, and what happens to requests that resolve to a blocked upstream.
### Aliases and routing [#aliases-and-routing]
The model id in the catalog is the canonical id. AnyRouter also accepts:
* **Aliases** — shorter names that resolve to a canonical id (for example, vendor-shipped shortcuts).
* **AnyRouter virtual aliases** — catalog ids such as `anyrouter/auto`, `anyrouter/latest`, and `anyrouter/free` that resolve at request time to a chain of concrete models. `anyrouter/latest` tries the newest healthy chat models first; see [Smart Routing](/features/routing). Platform routers carry a `members` array in `GET /api/v1/models` and `GET /api/v1/models/{id}`: the real models behind the router, as `{ id, name, owner, release_date, position }`, in the order a request tries them.
* **Presets** — workspace-defined virtual models that bundle a base model with parameters and system instructions, referenced as `@preset/` and combinable with a model override: `openai/gpt-5.4-mini@preset/code-reviewer`.
* **Per-request provider preferences** — pin a request to a specific upstream, sort by price or latency, or set a maximum price via a `provider` object in the request body.
Workspace presets don't appear in the catalog as models — they're listed on the **Presets** page. Built-in `anyrouter/*` aliases do appear, because the id you copy is callable on its own. For the full set of routing rules, see [Smart Routing](/features/routing).
## Configure [#configure]
### Trying a model [#trying-a-model]
Every catalog row has a **Copy** action that copies the model id to your clipboard. A minimal smoke test:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-mini",
"messages": [
{ "role": "user", "content": "Reply with the single word: ready." }
]
}'
```
Swap `openai/gpt-5.4-mini` for any id you copied. The same id works in the OpenAI Python and Node SDKs pointed at `https://anyrouter.dev/api/v1`. For streaming, add `"stream": true` and consume the response as server-sent events. If a model doesn't respond as expected, check **Logs** in the dashboard for the request id — you'll see exactly which upstream served the call and the error code returned.
### Programmatic access [#programmatic-access]
To get the catalog data in code — for a model picker, internal docs, or a cost calculator — call the public models endpoint instead of scraping the dashboard:
```bash
curl https://anyrouter.dev/api/v1/models \
-H "Authorization: Bearer sk-ar-your-key"
```
The response is OpenAI-compatible and includes the model id, owner, context window, pricing, and capability flags for every model the current caller can reach. A typical workflow: browse the catalog to find candidates, hit `/api/v1/models` from your app at runtime, cache the response for a few minutes (the catalog changes rarely), and use the returned `id` strings directly in your requests. Pass `?all=1` when you need the full catalog regardless of keys. See the [Models API reference](/api-reference/models) for the full schema and filters.
## Frequently asked questions [#frequently-asked-questions]
:::details{summary="Why does a model I expect to see not show up?"}
The default list only includes models you can call with the current session or API key. BYOK-only models stay hidden until you add a matching key; privacy blocks and empty local-relay pools also omit rows. Turn on **Show all models** (or call `/api/v1/models?all=1`) to see the full catalog. If a model is still missing there, it may have been removed by its upstream — check **Logs** for recent attempts.
:::
:::details{summary="Do prices in the catalog include AnyRouter fees?"}
Yes. The prices shown are the all-in rate you're billed per token. There are no separate per-request fees on top.
:::
:::details{summary="Can I see when a price changed?"}
Price changes are announced in advance and recorded in your **Credits** transaction history once they take effect. The catalog always reflects the current rate.
:::
## Related [#related]
* [Smart Routing](/features/routing) — how AnyRouter picks an upstream for each model
* [Prompt Caching](/features/caching) — cut input costs on supported models
* [Privacy Controls](/features/privacy-controls) — block providers you can't use
* [Models API reference](/api-reference/models) — list every model from code
---
# Organizations & Workspaces (/features/organizations)
Working solo, a personal account is all you need. But the moment a second person — or a second service — needs to share your keys, credits, presets, or logs, passing a single secret around stops scaling. **Organizations** give a team one shared workspace: shared API keys, one credit pool, common presets, a single usage history, and one billing surface, all gated by roles.
## Overview [#overview]
Every account has a private **personal workspace** and can belong to any number of shared **organization workspaces**. The workspace picker in the dashboard sidebar switches between them, and the active workspace decides which keys, credits, presets, and logs you see.
| Workspace | How many | Shared? | Created |
| ------------ | ----------------------- | ----------------------------------- | ------------------------------ |
| Personal | Exactly one per account | Never | Automatically on first sign-in |
| Organization | As many as you like | With every member (subject to role) | On demand |
You can belong to many organizations at once and hold a different role in each.
If you are the only person who will ever use AnyRouter, stay in your personal workspace. A one-person organization adds management overhead with no benefit.
## How it works [#how-it-works]
### When to create an organization [#when-to-create-an-organization]
Create one when any of these is true:
* **Shared billing.** You want a single invoice and one credit pool instead of expensing individual top-ups.
* **Shared keys.** Multiple people or services issue and rotate keys under one umbrella, with one set of usage limits.
* **Shared logs.** Engineering, ops, and finance all inspect request history, spend, and errors against the same dataset.
* **Shared presets.** A curated set of model presets (system prompts, defaults, routing rules) should stay consistent across teammates.
* **Onboarding and offboarding.** People join and leave; you grant or revoke access without rotating credentials.
### Roles [#roles]
Every member has exactly one role per organization. Each organization must have at least one **owner** at all times.
| Capability | Owner | Admin | Member |
| --------------------------------------- | ----- | ----- | ------ |
| View organization keys | Yes | Yes | Yes |
| Create and revoke organization API keys | Yes | Yes | No |
| View shared credits balance | Yes | Yes | Yes |
| Top up credits or change billing | Yes | No | No |
| View shared logs and usage | Yes | Yes | Yes |
| Create and edit shared presets | Yes | Yes | No |
| Store and rotate BYOK credentials | Yes | Yes | No |
| Invite new members | Yes | Yes | No |
| Change member roles | Yes | Yes | No |
| Remove members | Yes | Yes | No |
| Promote another member to owner | Yes | No | No |
| Transfer ownership | Yes | No | No |
| Delete the organization | Yes | No | No |
* **Owner** — the most authority. Only owners can change billing, transfer ownership, or delete the organization.
* **Admin** — everything an owner can do except billing, ownership transfer, and deletion. Right for senior engineers and team leads who manage day-to-day access.
* **Member** — the default for new invitees. Members use the organization (make requests, browse presets, view logs and credits) but cannot manage members, mint keys, or change configuration.
### What is workspace-scoped vs. account-scoped [#what-is-workspace-scoped-vs-account-scoped]
Switching workspaces reloads the dashboard against that workspace's data. Some things travel with the switch; others are properties of your account.
| Workspace-scoped (changes with the switch) | Account-scoped (never changes) |
| ------------------------------------------------------------------------------------------------ | --------------------------------------------------- |
| API keys (`sk-ar-…`) — personal and org keys stay invisible to each other | Your profile (name, email, avatar, sign-in methods) |
| BYOK credentials — stored per workspace | Authentication (session, MFA, recovery) |
| Presets, including `@preset/` references — same slug can resolve differently per workspace | Notification preferences |
| Credits — personal and org pools are separate | |
| Usage and logs — scoped to the active workspace's keys | |
API requests are authenticated by the **key**, not by the workspace picker. A personal `sk-ar-…` key always charges your personal credits; an organization key always charges that organization — regardless of what the dashboard is showing. The picker only affects what you see.
### Billing [#billing]
Credits live at the **workspace** level. Each organization has its own balance, separate from every member's personal balance and every other organization's.
* **Top-ups.** Only an owner can top up an organization's credits or change billing details. Admins and members see the balance but cannot purchase.
* **Spend.** Any request made with an organization key draws from the organization's pool — members don't need their own credits to use org keys.
* **Seats.** AnyRouter does not currently charge per seat; invite as many teammates as you need at no additional fixed cost. You pay for what you consume. Seat-based pricing is expected on Enterprise plans in the future but does not apply to standard organizations today.
* **Low-balance alerts.** Owners and admins can opt in to low-balance email alerts. Members do not receive billing alerts by default.
* **Invoices and receipts.** Receipts for org top-ups are emailed to the owner who performed the top-up and are visible to all owners and admins in the dashboard.
## Configure [#configure]
### Invite teammates [#invite-teammates]
Invitations are by email. As an owner or admin:
1. Open the organization on the **Organizations** page in your dashboard.
2. Click **Invite member**.
3. Enter the teammate's email and pick a role (Admin or Member).
4. Send the invitation.
The invitee receives a sign-in link. If they already have an AnyRouter account on that address, accepting the invite adds the organization to their workspace picker immediately. If not, they create an account first, then the organization appears.
* **Expiry.** Invitations expire after 7 days. Re-send from the same screen if a teammate misses the window.
* **Resending.** If the email didn't arrive, resend from the same screen — resending refreshes the 7-day expiry.
* **Revoking.** A pending invitation can be revoked any time before acceptance. After acceptance, remove the person as a member instead.
### Switch workspaces [#switch-workspaces]
The workspace picker in the dashboard sidebar lists your personal workspace first, then every organization you belong to. Selecting a different workspace reloads the dashboard against that workspace's data.
### Leave an organization [#leave-an-organization]
Open the **Organizations** page, find the organization, and choose **Leave organization**. Access is removed immediately. Keys you created inside the organization stay with the organization and keep working — the keys belong to the org, not to you.
**Last-owner protection.** AnyRouter refuses to let the last remaining owner leave or downgrade their own role. Before an owner can leave, they must transfer ownership to another member (or promote an admin to owner first). If you are the last owner and want out, either promote another member to owner, or delete the organization — which permanently removes every key, preset, log, and credit balance attached to it. Deletion has no recovery; export anything you need first.
### SSO and enterprise [#sso-and-enterprise]
Enterprise organizations can enable **SAML SSO** so members sign in through your identity provider (Okta, Azure AD, Google Workspace, etc.) instead of a password or social login. SSO is configured by an owner from organization settings and applies to every member. Once enforced, members can no longer use email or social providers for that organization.
* **Domain verification.** Before SSO can be enabled, the organization must verify ownership of one or more email domains via a DNS TXT record. Once verified, AnyRouter routes invitees on those domains into the organization's SSO flow.
* **SCIM provisioning.** Enterprise organizations can also enable SCIM so your IdP creates, updates, and deprovisions members automatically. Removing a user from your IdP removes them from the AnyRouter organization on the next sync.
SSO and SCIM are available on the Enterprise plan. Contact us through the dashboard support link to start.
## Frequently asked questions [#frequently-asked-questions]
:::details{summary="Can I move a key from my personal workspace into an organization?"}
No. Keys belong to the workspace they were issued in. Mint a new organization key and update your clients.
:::
:::details{summary="Will my teammates see the requests I make with my personal key?"}
No. Personal-workspace traffic is private and never appears in any organization's logs.
:::
:::details{summary="Can a member see other members' personal information?"}
Members see the display name, avatar, and role of every other member. They cannot see other members' personal keys, credits, or usage.
:::
:::details{summary="What happens to my organizations if I delete my account?"}
If you are the only owner of an organization, account deletion is blocked until you transfer ownership or delete the organization. Members and admins can delete their accounts normally; the organizations are unaffected.
:::
:::details{summary="Can I rename an organization?"}
Yes. Owners and admins can rename it any time from the **Organizations** page. The change appears for every member on their next page load.
:::
---
# Presets (/features/presets)
Copying the same model, system prompt, and sampling parameters into every request is error-prone — one drifted `temperature` or a stale system prompt and your evals no longer reproduce. **Presets** bundle a default model, system prompt, sampling parameters, and optional routing rules into a named configuration you reference from any request, so the important settings live in one place instead of scattered across your codebase.
## Overview [#overview]
Create a preset once, then reference it in the `model` field with either the classic `@preset/` syntax or the branch-based `@presets/` syntax. The preset's config is merged into each request that names it.
```bash
# Use a preset directly
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "@preset/code-reviewer",
"messages": [{"role": "user", "content": "Review this code: ..."}]
}'
# Override the model while keeping the rest of the preset config
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-mini@preset/code-reviewer",
"messages": [{"role": "user", "content": "Review this code: ..."}]
}'
```
## How it works [#how-it-works]
### Preset config fields [#preset-config-fields]
Each preset has a slug, name, optional description, and a config object with these fields:
| Field | Type | Description |
| ----------------------- | ------------------- | ------------------------------------------------------------------------------------------------------- |
| `model` | string | Default model (e.g. `"z-ai/glm-4.7-flash"`) |
| `models` | string\[] | Fallback model list, tried in order |
| `system` | string | System prompt prepended to messages |
| `temperature` | number | Sampling temperature (0–2) |
| `max_tokens` | number | Maximum response tokens |
| `max_completion_tokens` | number | Max completion tokens |
| `top_p` | number | Nucleus sampling threshold |
| `top_k` | number | Top-K sampling |
| `frequency_penalty` | number | Frequency penalty (-2 to 2) |
| `presence_penalty` | number | Presence penalty (-2 to 2) |
| `repetition_penalty` | number | Repetition penalty |
| `stop` | string \| string\[] | Stop sequences |
| `seed` | number | Reproducibility seed |
| `response_format` | object | Response format (e.g. `{ "type": "json_object" }`) |
| `tools` | array | Tool definitions |
| `tool_choice` | string \| object | Tool selection strategy |
| `provider` | object | Provider routing (`sort` including `"exacto"`, `min_context`, `require_params`, `only`, `ignore`, etc.) |
| `require_params` | string\[] | Hard parameter requirements (for example `["tools"]`). Same meaning as `provider.require_params`. |
| `reasoning` | object | Reasoning config (`effort`, `max_tokens`) |
| `stream_options` | object | Streaming options |
### Merging behavior [#merging-behavior]
When a request uses a preset, the preset's config is merged with the request:
* **Request fields override preset defaults** — any field in the request replaces the preset value. `provider.min_context` takes the stricter (higher) floor, and `require_params` lists are unioned.
* **Model resolution** — if the request uses `@preset/`, that model wins; otherwise the preset's first model is used.
* **System prompt** — prepended to messages only when the request has no existing system message.
* **`anyrouter/auto`** — when the preset's model is `anyrouter/auto`, the stored `provider` floors (exacto, tools, minimum context) apply at member-pick time.
### Reasoning controls on built-in routes [#reasoning-controls-on-built-in-routes]
`anyrouter/auto`, `anyrouter/free`, `anyrouter/cowork`, `anyrouter/hermes`, and `anyrouter/latest` resolve to concrete catalog models at request time. A reasoning control is applied to each resolved route, not to a made-up preset-wide model:
* `reasoning_effort` is the flat Chat-style field. Responses uses nested `reasoning.effort`, with `reasoning.enabled: false` as its endpoint-specific disable form. For flat Chat and converted Messages, `none` is a disable sentinel and is never clamped. Messages uses `thinking.type`; a non-native conversion maps `enabled` to `high` and `disabled` to `none`, while native Anthropic routes keep the Messages body.
* The selected route's `reasoning.levels` / `default` (or its per-upstream override) determines the exact flat value sent upstream. An effort outside that set is clamped to the nearest lower supported level, or the model default for an unknown token. An omitted effort stays omitted; `default` is a clamp fallback, not automatic injection.
* Fallback branches are clamped independently. A route that cannot serve the request may fail or fall through according to the normal upstream policy; it does not inherit another branch's effort list.
* Preset-level `require_params` is a hard candidate constraint where preset candidate selection is used. Preset, key, and request provider lists are unioned at the preference layer, but provider/key requirements are not a universal hard filter on every concrete dispatch; built-in virtual resolution uses tool-related entries (and request tools) to select tool-capable members. `provider.require_parameters` is the separate strict request-parameter switch. `min_context` takes the stricter value, key `ignore` is unioned, and key/request `only` lists intersect. If no `supported_parameters` list is declared, the current router is permissive rather than claiming universal support. The selected route's per-upstream list is the authority; no universal toggle, budget, or interleaved-thinking capability is inferred for a preset.
### Fallback with multiple models [#fallback-with-multiple-models]
Add multiple models to a preset for automatic fallback. The first is primary; if it fails, the router tries the next:
```json
{
"models": ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4-mini"],
"temperature": 0.2
}
```
If `anthropic/claude-sonnet-4-6` is unavailable, AnyRouter automatically falls through to `openai/gpt-5.4-mini`.
### BYOK routes [#byok-routes]
For providers you use through BYOK, a preset can pin a branch to a specific upstream. Reference these with `@presets/` — the selected branch keeps model and upstream together, so a BYOK branch tries your key's upstream instead of first trying managed providers for the same model.
Branch-based presets (`@presets/`) are what you want for BYOK pinning. They carry a `route` with both `model_id` and `upstream_id`, so the request goes to exactly the provider account you intend.
Route `z-ai/glm-4.7-flash` through your Z-AI Standard API key pool:
```json
{
"version": 2,
"kind": "chat",
"mode": "fallback",
"paid_fallback": false,
"branches": [
{
"id": "br_zai_glm_byok",
"weight": 100,
"priority": 1,
"route": {
"model_id": "z-ai/glm-4.7-flash",
"upstream_id": "zai"
}
}
]
}
```
For a Z-AI Coding Plan key, use the same model id with `upstream_id: "zai_coding"`:
```json
{
"version": 2,
"kind": "chat",
"mode": "fallback",
"paid_fallback": false,
"branches": [
{
"id": "br_zai_glm_coding_byok",
"weight": 100,
"priority": 1,
"route": {
"model_id": "z-ai/glm-4.7-flash",
"upstream_id": "zai_coding"
}
}
]
}
```
To route a model through your OpenRouter account, first save an OpenRouter key in **Dashboard → BYOK**. Then create or edit a preset, choose the model, select **OpenRouter** in **BYOK route**, enable the route, optionally enable paid fallback, and save:
```json
{
"version": 2,
"kind": "chat",
"mode": "fallback",
"paid_fallback": false,
"branches": [
{
"id": "br_openrouter_byok",
"weight": 100,
"priority": 1,
"route": {
"model_id": "openai/gpt-5.4-mini",
"upstream_id": "openrouter"
}
}
]
}
```
This branch uses the AnyRouter catalog model id in `model_id` (such as `openai/gpt-5.4-mini`) and pins the route to OpenRouter with `upstream_id: "openrouter"`. Call the preset with `@presets/` from chat completions.
## Configure [#configure]
Create and manage presets two ways:
1. **Dashboard** — visit [`/dashboard/presets`](https://anyrouter.dev/dashboard/presets) to create, edit, and organize presets, including the BYOK-route builder described above.
2. **API** — use the [Presets API](/api-reference/presets) to create them programmatically.
Once saved, reference the preset from any request via `@preset/` (config bundle) or `@presets/` (branch routing).
## Use cases [#use-cases]
* **Consistent eval setups** — pin every eval to a specific preset so results don't drift when upstream providers change.
* **Team templates** — share presets across your org for common tasks (code review, summarization, classification).
* **Fallback handling** — configure multiple models so requests succeed even when a primary provider has an outage.
## Related [#related]
* [Presets API](/api-reference/presets) — create and manage presets programmatically.
* [Routing](/features/routing) — how the router picks and falls back across upstreams.
* [Managing API Keys](/features/api-keys) — the `sk-ar-…` keys presets are called with.
---
# Pricing & Plans (/features/pricing)
Most gateways bill you per seat or bury a per-request fee in the fine print. AnyRouter doesn't. You pick a **plan** for your included credits and limits, then use **pay-as-you-go credits** for anything beyond what the plan covers — and you only ever pay for the tokens you actually use.
## Overview [#overview]
| Feature | Free ($0) | Go ($2/mo) | Pro ($10/mo) | Pro+ ($45/mo) | Max ($100/mo) |
| -------------------------------------- | -------------- | -------------------- | --------------------- | ----------------------- | ------------------------ |
| Monthly credits | — | $4/mo (\~16M tokens) | $20/mo (\~84M tokens) | $100/mo (\~420M tokens) | $300/mo (\~1.26B tokens) |
| Free models (`anyrouter/free`) | No daily cap | No daily cap | No daily cap | No daily cap | No daily cap |
| Decision models (`anyrouter/decision`) | Free, no quota | Free, no quota | Free, no quota | Free, no quota | Free, no quota |
| Shared pool access | Donate a key | ✓ (plan) | ✓ (plan) | ✓ (plan) | ✓ (plan) |
| BYOK — bring your own keys | Unlimited | Unlimited | Unlimited | Unlimited | Unlimited |
| Models | 180+ | 180+ | 180+ | 180+ | 180+ |
| MCP server | ✓ | ✓ | ✓ | ✓ | ✓ |
| CLI (`anyr` / `ar`) | ✓ | ✓ | ✓ | ✓ | ✓ |
| Skills hub | ✓ | ✓ | ✓ | ✓ | ✓ |
| Prompts library | ✓ | ✓ | ✓ | ✓ | ✓ |
| Presets | 5 | 5 | Unlimited | Unlimited | Unlimited |
| Workspaces | 1 | 1 | 3 | 3 | 3 |
| Key pools | 1 | 1 | Unlimited | Unlimited | Unlimited |
| Log retention | 7 days | 7 days | 30 days | 30 days | 30 days |
| Rate limit (paid models) | 600 req/min | 60 req/min | 1200 req/min | 1800 req/min | 1800 req/min |
| Config & skills sync | — | — | ✓ | ✓ | ✓ |
| Usage analytics export | — | — | ✓ | ✓ | ✓ |
| Priority fallback routing | — | — | ✓ | ✓ | ✓ |
| Zero-retention routing (opt-in) | ✓ | ✓ | ✓ | ✓ | ✓ |
| Priority support | — | — | ✓ | ✓ | ✓ |
| **Team** | Coming soon | Coming soon | Coming soon | Coming soon | Coming soon |
**Plan prices.** The price shown ($2 Go, $10 Pro, $45 Pro+, $100 Max) is what AnyRouter nets — your card is charged a little more ($2.63 / $11.05 / $47.89 / $105.79 domestic, more internationally) so the [Polar](https://polar.sh) processing fee (5% + 50¢ of the total charge, +1.5% for non-US cards) comes out of that markup instead of your plan credit. Tax, where it applies, is added on top by Polar at checkout. AnyRouter adds no fee of its own.
**Rate limits.** The per-minute caps above apply to paid-model traffic and are enforced over a rolling 60-second window. Free-tier models (`anyrouter/free` and any `:free` variant) have no daily cap and run at **2400 req/min** per key on every plan (Go's 60 req/min applies to paid models only). See [Rate Limits](/features/rate-limits) for the full table, including the anonymous per-IP cap.
**Shared pool access.** The community shared pool (donated BYOK keys) is open to two groups: anyone who has donated at least one eligible key (and earns up to 8% of the tokens it serves — 8% on Pro/Pro+/Max, 5% on Go), and any paid subscriber (Go, Pro, Pro+, or Max). On Free, you must donate a key to use the pool's free models — or subscribe to Go for $2/mo to unlock it without sharing a key. See [BYOK](/features/byok) for how donation and the pool work.
## How it works [#how-it-works]
### Free [#free]
Sign up and start building immediately — no credit card required:
* **No included credits.** The Free plan carries no monthly credit and no signup bonus.
* **180+ models** to browse; pay for what you use with pay-as-you-go top-ups (no subscription required).
* **BYOK** — connect your own upstream provider keys, unlimited. BYOK requests never deduct credits.
* MCP server, CLI, Skills hub, Prompts library, 5 presets, 1 workspace, and 7-day logs.
* **Free-tier models** via [`anyrouter/free`](/features/free-tier) are included — no daily cap, no credits needed. Paid models return `402` without credits.
* **Decision models** via [`anyrouter/decision`](/api-reference/systemone) are free — no credits, no BYOK key, no plan quota. A fair-use burst cap of 60 requests per minute per key applies.
### Go — $2/mo ($2.63 charged) [#go--2mo-263-charged]
Go is the lightweight paid plan and the cheapest way to get included credit and the shared pool. Free models (`anyrouter/free`) work on every plan and do not need Go. **Get it two ways: pay $2/mo, or donate an eligible provider key to the [shared pool](/features/byok) — donating auto-grants Go for free.**
* **$4 in credits every month** (\~16M tokens on fast models), issued automatically.
* **60 req/min** on paid models, and usage is capped per rolling 5-hour window (3,000 req/5h) — upgrade to Pro, Pro+, or Max for higher per-minute limits and unlimited usage with no window limits.
* **Free-tier models** via [`anyrouter/free`](/features/free-tier) — no daily cap, no credits needed.
* **Decision models** via [`anyrouter/decision`](/api-reference/systemone) — free, no plan quota, fair-use burst cap of 60 requests per minute per key.
* Unlocks the **shared pool** for your own requests without donating (if you paid $2/mo rather than sharing a key).
* Earns **up to 8% credits back** on every token a donated key serves (8% on Pro/Pro+/Max, 5% on Go).
* **1 workspace** and 7-day log retention.
### Pro — $10/mo ($11.05 charged) [#pro--10mo-1105-charged]
Pro adds, on top of everything in Free and Go:
* **$20 in credits every month** (\~84M tokens), issued on activation and each renewal.
* **Unlimited usage** — no 5-hour window limits.
* **3 workspaces**, unlimited key pools, and 30-day log retention.
* **Cloud skills & prompts** that sync across machines, with private bundles.
* Priority fallback routing, usage analytics export, and 1200 req/min.
* **Zero-retention routing** — available on every plan from Privacy settings.
* Priority support.
### Pro+ — $45/mo ($47.89 charged) [#pro--45mo-4789-charged]
Pro+ is the best-value volume tier — everything in Pro plus a larger monthly credit and the highest rate limits:
* **$100 in credits every month** (\~420M tokens on fast models) — five times the credits of Pro.
* **Unlimited usage** — no 5-hour window limits.
* **1800 req/min** — effectively never rate-limited.
* 3 workspaces, 30-day logs, priority fallback, analytics export, and priority support.
### Max — $100/mo ($105.79 charged) [#max--100mo-10579-charged]
Max is the top volume tier — everything in Pro+ plus the largest monthly credit:
* **$300 in credits every month** (\~1.26B tokens on fast models) — three times the credits of Pro+.
* **Unlimited usage** — no 5-hour window limits.
* **1800 req/min** — effectively never rate-limited.
* 3 workspaces, 30-day logs, priority fallback, analytics export, and priority support.
The monthly plan credit (Go $4, Pro $20, Pro+ $100, Max $300) lands in your **monthly credits** bucket, which is always spent before any top-up credits you've purchased. See [Credits & Billing](/features/credits) for how the two buckets work.
### How billing is organized [#how-billing-is-organized]
In the dashboard, billing is split into three surfaces so each concern has its own place:
| Surface | What it's for |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| **Subscription** | Choose and manage your plan (Free, Go, Pro, Pro+, or Max). |
| **Pay as you go** | View your credit balance and usage, top up, and review credits, invoices, and transaction history. |
| **Payment methods** | Manage saved cards for top-ups. Auto-charge — topping up automatically when your balance runs low — is opt-in and coming soon. |
A plan and pay-as-you-go credits are complementary: your subscription provides included monthly credits and higher limits, while pay-as-you-go credits cover anything beyond them. You can use pay-as-you-go credits on the **Free** plan without subscribing at all.
### Credits, top-ups, and BYOK [#credits-top-ups-and-byok]
* **Two buckets.** Your balance is monthly-issued credits plus purchased top-up credits. Monthly credits are spent first; **top-up credits never expire**.
* **Top-ups.** Add credits from the [Credits dashboard](https://anyrouter.dev/dashboard/credits) (minimum $5.00), processed via [Polar](https://polar.sh).
* **BYOK and the shared pool are free to use.** Requests served through your own provider keys never deduct credits, on any plan. Donating a key earns you up to 8% back (8% on Pro/Pro+/Max, 5% on Go); any paid plan (Go+) unlocks the pool without donating.
* **Subscriptions.** Choose Free, Go ($2/mo), Pro ($10/mo), Pro+ ($45/mo), or Max ($100/mo) from the [Credits dashboard](https://anyrouter.dev/dashboard/credits); checkout and renewal run through [Polar](https://polar.sh). The card is charged $2.63 / $11.05 / $47.89 / $105.79 respectively so AnyRouter nets the full plan price after Polar's fee — plus any tax Polar adds for your country.
* **Processing fee.** Polar, our Merchant of Record, charges 5% + 50¢ of the total amount charged (product + tax), and +1.5% more for non-US cards. That fee goes entirely to Polar — AnyRouter keeps none of it, on plans, monthly credits, or top-ups.
Full details on buckets, cost calculation, and transaction history live in [Credits & Billing](/features/credits). For per-tier request caps, see [Rate Limits](/features/rate-limits).
## Configure [#configure]
Manage your plan and credits from the dashboard:
* **[Subscription](https://anyrouter.dev/dashboard/credits)** — pick Free, Go, Pro, Pro+, or Max.
* **[Pay as you go](https://anyrouter.dev/dashboard/credits)** — check your balance and top up.
---
# Privacy & Logging Controls (/features/privacy-controls)
Routing your prompts through a gateway raises an obvious question: what does the gateway keep, and where does it go? AnyRouter answers it plainly. Request and response bodies **are** logged by default, on every plan, so you can inspect and debug real traffic on the Request Logs page — turn it off any time with the **Body logging** switch in Privacy settings. Some providers keep or train on prompts; one switch, zero-data-retention (ZDR) routing, keeps your requests off them. A single dashboard page holds all of it, with your current stance summarized at the top.
## Overview [#overview]
The **Privacy & safety** page holds every retention and routing-policy control for your account. Settings here are **global** — they apply to every key and override anything set on an individual preset. The narrower controls on the [Request Logs](/features/request-logs) page inherit from here; you cannot widen logging on a key beyond what this page allows.
| Control | Default | What it changes |
| --------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| Log request/response bodies | **On** | Whether the full prompt and completion are stored |
| PII redaction | On (when logging is on) | Strips emails, phone numbers, and common secrets from stored bodies |
| Always enforce ZDR | Off | The only routing privacy control. Routes only to providers that guarantee zero data retention; rejects a request no such provider can serve |
| Blocked providers | None | List of upstreams that will never receive traffic from this account |
| Moderation plugin | Off | Scans prompts and completions against a content policy |
All settings persist immediately when toggled — there is no "save" button. Changes propagate to the routing layer within a few seconds.
**Rollout status.** Metadata logging (model, tokens, cost, latency, status) is on for every request. Request/response **body capture** is on by default on every plan as of 27 July 2026 — previously it was suppressed for paid plans, and it is now controlled solely by the **Body logging** switch below. **PII redaction** is still rolling out; toggling it saves your preference and takes effect as the pipeline reaches your region. ZDR routing, blocked providers, and moderation are all live now.
## How it works [#how-it-works]
### Body capture [#body-capture]
The **Log request/response bodies** switch controls whether AnyRouter stores the full payload of every request: the messages array, the model output, tool calls, and attached images.
* **Off (default)** — AnyRouter still stores **metadata**: model id, upstream provider, token counts, latency, status code, timestamp, and the truncated cache key. Enough to answer "what did I spend," "which model is slowest," and "did this hit the cache" — but not enough to replay a conversation or build an evaluation set.
* **On** — request and response are stored alongside the metadata. Open any log entry to see the exact prompt sent and completion returned. This is the mode you want during development, when chasing a prompt-quality regression, or when building an offline eval set.
Tradeoffs to weigh before turning it on:
* **Storage cost** grows roughly linearly with traffic. Large agentic workloads with long tool catalogs can produce gigabytes per day.
* **Sensitive data exposure** — anything in the body lands in your logs. PII redaction (below) handles common cases but is not a substitute for input validation in your application.
* **Caching** is unaffected — prompts are hashed and cached the same way regardless of body logging.
You can flip this on for a debugging session and back off when done. Existing entries are **not** retroactively redacted when you turn the toggle off; they remain visible in Request Logs until they fall outside your plan's retention window.
### PII redaction [#pii-redaction]
When body logging is on, a second switch appears: **PII redaction**, enabled by default. It runs every stored prompt and completion through a redactor before persistence.
Recognized patterns out of the box:
| Pattern | Replaced with |
| ---------------------------------------------------------------------- | ------------------- |
| Email addresses | `[REDACTED_EMAIL]` |
| Phone numbers (NANP, E.164, most international) | `[REDACTED_PHONE]` |
| Credit card numbers (Luhn-valid, 13–19 digits) | `[REDACTED_CC]` |
| API keys and tokens (`sk-`, `sk-ar-`, `ghp_`, `xoxb-`, AWS keys, JWTs) | `[REDACTED_SECRET]` |
| IP addresses (IPv4 and IPv6) | `[REDACTED_IP]` |
| SSN-shaped sequences (`NNN-NN-NNNN`) | `[REDACTED_SSN]` |
Redaction runs **before** the prompt is hashed for storage, so the stored copy is the redacted copy. The original prompt is still sent to the upstream unmodified — redaction is a logging control, not a routing control. To scrub data before it leaves your application, do that client-side.
The redactor is fast (sub-millisecond typically; 1–2 ms on very high-throughput workloads) and never blocks delivery — if it errors, the log entry is dropped and the request still succeeds. It is **conservative on purpose**, erring toward false positives over false negatives. Accounts on plans that expose custom redaction rules can add a regex via the API documented under [Request Logs](/features/request-logs).
### Zero data retention [#zero-data-retention]
**Always enforce ZDR** is the one switch that decides whether providers that keep or train on your prompts can serve you.
* **Off (default)** — every provider can serve your requests, including free tiers and hosts that train on prompts. A model page marks each such host with a **Trains on prompts** badge, the Decisions model picker shows the same badge, and the models list marks the model **May retain**.
* **On** — only providers that guarantee zero data retention receive your requests. A provider that does not say so is treated as not zero-retention and is skipped. Free and community-hosted models are usually not zero-retention, so with ZDR on some models will not be available to you.
If ZDR leaves no provider for the model you asked for, the request returns `403 zdr_required` and names the setting, so you can turn ZDR off or pick another model. If something other than ZDR also empties the list (for example you blocked every provider), you get the usual no-route error instead.
ZDR is the only privacy routing control; there is no separate training or publishing switch. The **Trains on prompts** badge is information only and never blocks a request. Providers that publish prompt-completion pairs to public datasets are not zero-data-retention, so turning **Always enforce ZDR** on is what keeps requests off them. Turn it on for any account handling customer data.
### Blocked providers [#blocked-providers]
The **Provider restrictions** panel lists every upstream AnyRouter can route to. Tap a provider to ban it for this account; tap again to unblock. Blocked providers never receive a single request from your keys, regardless of model, preset, or fallback chain. Common reasons: compliance (DPA covers only specific subprocessors), provider ToS, regional restrictions, quality issues, or a security incident at a provider.
Blocking requires no other changes — the router picks the next eligible upstream. If the only upstream for a model is blocked, requests to that model return `503 no_eligible_provider` with the blocking reason in the error envelope. The block list is global to the account; to block a provider on one key but not another, route that key through a preset that pins specific upstreams (see [Routing](/features/routing)).
### Data residency [#data-residency]
AnyRouter runs on Cloudflare Workers, so entry-point compute is global and requests land at the nearest edge.
| Data | Where it lives |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| In-flight request data | Held in memory at the edge for the request duration, then discarded. Bodies stream to the upstream — not buffered to disk. |
| Request metadata | Cloudflare D1 in the account's primary region (reads replicate globally; writes go to one region). |
| Request bodies (when logging on) | The same D1 instance, encrypted at rest with a per-row salt. |
| API keys | Encrypted at rest in D1 with a per-row salt. Plaintext shown once at creation, never again. |
| Aggregated usage counters | Cloudflare KV for fast credit checks — globally replicated, eventually consistent. |
The upstream provider receives your prompt and is bound by its own residency commitments; pin providers in a specific region via a preset (see [Routing](/features/routing)). If your regime requires that **no** prompt or completion crosses a specific border, the safest configuration is body logging off, a blocked-providers list excluding every region you cannot use, and ZDR enforcement on.
### Retention [#retention]
* **Request logs** — dashboard and API read-access is clamped to the plan window: **7 days** on Free and Go, **30 days** on Pro and Max, **90 days** on Enterprise. The same window applies whether or not bodies are stored. Older rows are not returned; AnyRouter does not run a per-plan deletion job. Only the **size** of each entry depends on the body-logging toggle.
* **Aggregated usage data** (tokens, cost, latency) is retained **365 days** for monthly and quarterly reporting. It is metadata only and never contains prompt or completion text.
Custom windows (e.g. 7 days to minimize exposure, or 90 days for forensics regardless of plan) are available on accounts with a signed DPA; contact support.
### Audit [#audit]
Every change to any switch on this page records **who** (user id and email), **what** (old value, new value, field name), **when** (UTC timestamp to the millisecond), and **where from** (IP and user agent). Audit records live in a separate table, are retained **365 days** regardless of the request-log window, and cannot be modified or deleted through the dashboard.
Who can change settings:
* **Personal workspaces** — only the workspace owner.
* **Organization workspaces** — only members with the `admin` role. Others can view but not toggle.
For a more granular permission model (e.g. a security-officer role), contact support — RBAC extensions are on the roadmap.
## Configure [#configure]
The card at the top of the page summarizes your current stance — whether ZDR is enforced, whether body logging is on, how many providers are blocked, and whether moderation is active. It's read-only, a sanity check you can read without scrolling.
A safe configuration for a production customer-support agent that handles PII and needs body logging for offline evaluation:
1. **Log request/response bodies** — on
2. **PII redaction** — on
3. **Always enforce ZDR** — on
4. **Blocked providers** — any provider not covered by your DPA
5. **Moderation plugin** — on
With this, every request lands at a zero-data-retention provider, is moderated before returning, and lands in your logs with PII stripped, while the audit trail records every settings change for a year. Use an `sk-ar-…` key from the [API Keys](/features/api-keys) page against `https://anyrouter.dev/api/v1/chat/completions`. To verify which upstreams are actually eligible for a given model, inspect real routing decisions on the [Request Logs](/features/request-logs) page after a few requests.
---
# Rate Limits (/features/rate-limits)
Agent loops and burst traffic can hammer an endpoint into a wall of `429`s if the limits aren't clear. AnyRouter applies request-rate limits at two layers — a per-IP cap for anonymous traffic and a per-API-key cap for authenticated traffic — enforced over a sliding **60-second window** using Cloudflare's native rate-limiting bindings. There is no daily quota on any traffic (except the Go plan's per-request-window cap on paid models, below). The [free tier](/features/free-tier) (`anyrouter/free` and `*:free` models) has no daily cap and uses its own generous per-minute limit.
## Overview [#overview]
| Caller | Tier | Limit |
| ----------------------------------------------------------------- | ---------- | --------------------------------------------------------------------- |
| Anonymous (no API key) | per-IP | **300 req / 60s** |
| API key, free-tier model (`anyrouter/free` or `*:free`), any plan | free-model | **2400 req / 60s** |
| API key, plan = `go`, paid model | go | **60 req / 60s** (also capped at 3,000 req per rolling 5-hour window) |
| API key, plan = `free` or `payg`, paid model | paid-model | **600 req / 60s** |
| API key, plan = `pro`, paid model | pro | **1200 req / 60s** |
| API key, plan = `pro_plus` or `max`, paid model | power | **1800 req / 60s** |
| API key, plan = `enterprise` (any model) | enterprise | **2400 req / 60s** |
| API key, `POST /api/v1/decisions` (any model, any plan) | decision | **60 req / 60s** |
Free-model requests use the 2400/min tier on every plan, Go included, and do not count toward Go's 5-hour window. Go's 60/min and 5-hour limits apply to paid models only.
The `X-RateLimit-Tier` value for Pro paid-model traffic is `pro`; for Pro+ and Max it is `power` — legacy identifiers kept for backward compatibility. Account plan names are **Free**, **Go**, **Pro**, **Pro+**, and **Max**. See [Pricing & Plans](/features/pricing).
**Why the split:** free models get a generous per-key limit with no daily cap, so agent loops can run freely. Paid models are billed per token — your spend naturally regulates volume, so we lift the per-minute cap on Pro and above. Go keeps a tighter per-minute and 5-hour window cap on paid models since it's the entry paid tier.
Your account plan (`free` / `go` / `pro` / `pro_plus` / `max` / `enterprise`) determines your rate tier for paid-model traffic. A Free user calling a paid model uses the **paid-model** tier at 600/min; Go is capped at 60/min (plus a 3,000 req/5h window), Pro at 1200/min, and Pro+ / Max at 1800/min. Free-tier models run at the 2400/min free-model tier on every plan, with no daily cap.
## How it works [#how-it-works]
### Free models have no daily cap [#free-models-have-no-daily-cap]
`anyrouter/free` and any model suffixed with `:free` have no per-account daily cap and do not use Go's 5-hour window. They share one limit of **2400 requests per minute per API key** on every plan. See [Free Tier](/features/free-tier).
### Decisions: free, no plan quota, fair-use burst cap [#decisions-free-no-plan-quota-fair-use-burst-cap]
[Decision models](/api-reference/systemone) through `anyrouter/decision` are free on every plan, Free included. They need no credits and no BYOK key, and they have no daily or plan quota. "Unlimited" means exactly that: there is no plan quota.
Every request to `POST /api/v1/decisions` (or `/api/v1/systemone`) uses its own per-key **fair-use burst cap of 60 requests per minute**, on every plan. That cap stops one runaway script from flooding the shared decision models. It has its own counter, so decision traffic never uses up your chat rate limit, and it does not count toward Go's 5-hour window. A tripped cap returns `429 rate_limit_exceeded` with `Retry-After: 60` and `X-RateLimit-Tier: decision`.
### Response headers [#response-headers]
Every API response — including `GET /api/v1/health`, `GET /api/v1/models`,
`GET /api/v1/credits`, error bodies, and successful completions — carries:
| Header | Meaning |
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `X-RateLimit-Limit` | Per-window request cap for the tier you hit |
| `X-RateLimit-Window` | Window length in seconds (always `60`) |
| `X-RateLimit-Tier` | One of `ip`, `free-model`, `paid-model`, `go`, `pro`, `power`, `enterprise`, `decision`, or `starter-window` (Go's 5-hour window cap — see below) |
| `RateLimit-Limit` | IETF draft header — same number as `X-RateLimit-Limit` |
| `RateLimit-Policy` | IETF draft policy, e.g. `300;w=60` (cap `;` window seconds) |
The per-minute tier does not emit `X-RateLimit-Remaining` / `X-RateLimit-Reset` —
the counter is not exposed by the edge limiter. Go's 5-hour window on paid models does
emit them, along with the matching `RateLimit-Remaining` / `RateLimit-Reset`
(window `18000`, tier `starter-window`).
When you hit the cap, the response is **HTTP 429** with a `Retry-After: 60` header and a body of `{"error": {"code": "rate_limit_exceeded", "message": "Rate limit exceeded for your API key — …"}}`.
### 429 vs 403 — don't confuse them [#429-vs-403--dont-confuse-them]
AnyRouter returns `429` for two distinct rate-limit scenarios, each with its own `error.code`: `rate_limit_exceeded` (per-key cap) and `ip_rate_limit_exceeded` (anonymous IP cap). If you see a different status:
| Status | `error.code` | Source | What it means |
| ------ | ------------------------ | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `429` | `rate_limit_exceeded` | AnyRouter | You hit the per-key limit. Back off `Retry-After` seconds. |
| `429` | `ip_rate_limit_exceeded` | AnyRouter | Anonymous IP hit 300/min. Sign in or send an API key. |
| `403` | `upstream_403` | Upstream provider | The model provider denied the call — geo block, policy violation, provider-side abuse heuristic. Not retryable via simple backoff. |
| `401` | `upstream_401` | Upstream provider | BYOK credential rejected upstream. Check your key. |
| `401` | `invalid_api_key` | AnyRouter | Your AnyRouter API key is wrong or revoked. |
| `402` | `insufficient_balance` | AnyRouter | Your credit balance is empty. Top up at [Credits](https://anyrouter.dev/dashboard/credits). |
| `502` | `upstream_5xx` | Upstream provider | Provider had a transient failure. Retry with jitter. |
`error.metadata.upstream_backend` identifies the specific provider when the status is `upstream_*`.
## Configure [#configure]
### Handling 429 in your client [#handling-429-in-your-client]
Check `error.code` before deciding how to retry:
* `rate_limit_exceeded` / `ip_rate_limit_exceeded` — `Retry-After` is **60 seconds**. Back off and retry.
```ts
async function callWithBackoff(body: object): Promise {
for (let attempt = 0; attempt < 5; attempt++) {
const res = await fetch("https://anyrouter.dev/api/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${ANYROUTER_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
})
if (res.status !== 429) return res
const wait = Number(res.headers.get("Retry-After") ?? 60)
const jitter = Math.random() * 1000
await new Promise((r) => setTimeout(r, wait * 1000 + jitter))
}
throw new Error("Rate-limit retries exhausted")
}
```
**Do not** treat a 429 as a permanent credential failure. Many agent loops mark keys as "exhausted" on 429 and stop using them — that's a client bug, not an AnyRouter behaviour. For per-minute 429s, the same key works again after the 60-second window.
### Agent loops count every call [#agent-loops-count-every-call]
A single user task that triggers an agent loop can issue many HTTP requests:
* 1 main `chat/completions` call
* 1 more call per tool round-trip (one for each model turn after a tool result)
* Optional auxiliary calls — title generation, conversation compaction, summarisation
Each is metered separately. A 5-tool task with title-gen + compaction is \~8 requests. At the 600/min paid-model tier you can sustain \~75 such tasks per minute; at the 2400/min free-model tier, \~300.
If your agent's natural request rate exceeds the tier:
1. Call paid models (auto-promotes you to 600/min).
2. Upgrade to enterprise for 2400/min.
3. Add jitter and parallelism caps in your loop.
### Need more headroom? [#need-more-headroom]
Open a request at `/contact` describing your workload (sustained req/min, burst pattern). Enterprise tiers are negotiated.
---
# Request Logs (/features/request-logs)
When a user reports "the bot replied with nonsense at 3pm," aggregate charts can't help — you need the exact request. **Request Logs** gives you a per-call view of everything that flows through AnyRouter: latency, tokens, cost, the upstream that served it, and (when privacy permits) the full request and response body. Enough to debug a single complaint, attribute spend to a key, or spot something unusual — without shipping your own logging pipeline.
## Overview [#overview]
Open the dashboard at [`/dashboard/logs`](https://anyrouter.dev/dashboard/logs). Each row is one request — chat completion, embedding, or streaming call. The table has two tabs: the flat **request list** and a rolled-up **Sessions** view. An activity chart above the table mirrors your current filters.
Every field a row shows:
| Field | Meaning |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------- |
| **Time** | When AnyRouter received the request, in your local timezone. Hover for the full timestamp. |
| **Model** | The model id the client asked for (e.g. `anthropic/claude-sonnet-4-6`). Click to open the catalog page. |
| **Provider** | The upstream that actually served the request — useful when a model has multiple upstreams and AnyRouter routed or failed over. |
| **App** | The app name reported via attribution headers, if any. See [App Attribution](/features/app-attribution). |
| **Key** | The API key (by name or last six characters of the prefix) that made the request. `sk-ar-…` prefixes are masked to the suffix. |
| **Tokens** | `input → output` counts. A lightning icon appears when part of the input was served from a prompt cache. |
| **TTFT** | Time-to-first-token, in ms. For non-streamed requests this equals total latency. |
| **Latency** | End-to-end latency from AnyRouter's perspective — upstream time, fallback attempts, and streaming wall time. |
| **Speed** | Tokens per second on generation, `output_tokens / (latency − TTFT)`. |
| **Cost** | What AnyRouter charged your account, in USD. Hover for the input / output / cached breakdown. |
The leftmost status icon encodes the outcome:
* **Success** — the request returned `2xx` from the chosen upstream.
* **Cached** — served from prompt cache or a cached completion (when available on that model).
* **Fallback** — AnyRouter retried at least one upstream before a healthy one returned. Still counts as success; expand the row to see which providers were tried.
* **Error** — every configured upstream returned an error envelope. Hover for the upstream error code and short message.
## How it works [#how-it-works]
### Filtering [#filtering]
The filter bar narrows the view. All filters compose, and the URL updates as you change them so any view is bookmarkable and shareable.
* **Model** — substring match against the model id (`gpt-`, `claude-`, `llama-`).
* **Key** — pick a specific key. The dropdown lists every key, including disabled ones, so you can audit a rotated key.
* **Status** — `success`, `cached`, `fallback`, or `error`. Combine with a time range to triage incidents.
* **Date range** — quick presets (`Last 30 minutes` through `Last 90 days`) or a custom `From` / `To` window (sent to the API in ISO-8601).
* **Request ID** — paste a request id (the `X-Request-Id` response-header value) to jump to one row. When set, all other filters are ignored — so a support ticket with just an id always resolves.
The activity chart above the table mirrors the current filters. Click any bar to drill into that 24-hour slice; use the grouping menu to pivot by request type, model, provider, or status.
### Sessions [#sessions]
Multi-turn agents and chat apps make many requests that belong to one logical conversation. Tag them with a **session id** and AnyRouter groups them for you. Send `session_id` with each request, either way:
* **Request body** — a top-level `session_id` field.
* **Header** — `x-session-id`.
If both are present, the body field wins. A session id can be any string up to 256 characters — a UUID, your own conversation id, or `user_42:thread_7`. Use the same value across every request in a conversation.
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"session_id": "thread_7f3a",
"messages": [{ "role": "user", "content": "Continue our conversation…" }]
}'
```
The same works for `/messages`, `/responses`, and `/embeddings`, and via the `x-session-id` header on any of them.
The **Sessions** tab shows one row per session id, with request count (and an error count when any failed), the distinct models used, rolled-up **Cost** and **Tokens**, and a **Last seen** time. Click a session row to drop into the flat list filtered to that session (`?session_id=…`). The selected tab and any session filter live in the URL, and every detail dialog shows a **Session** link when the request carried a session id.
### Detail view [#detail-view]
Clicking a row (or the three-dot menu) opens the **Generation details** dialog:
* **KPI grid** — provider latency, throughput, cost, token counts, and any fallback attempts.
* **Overview** — model id, canonical upstream model id, status, and creation time.
* **Request** — app attribution, AnyRouter request id, the upstream generation id when returned, the API key id, the finish reason (`stop`, `length`, `tool_calls`, …), whether streamed, and the API type (`chat`, `responses`, `embeddings`).
* **Error** — for failed requests, the structured error code and message returned to your client.
* **Provider Responses** — a timeline bar per upstream attempt: provider id, HTTP status, latency, and generation phase. The fastest way to see where a slow request spent its time.
* **Request & Response bodies** — when available, the raw JSON proxied to and from the upstream, captured via Cloudflare AI Gateway.
* **Generation Data** — the full row as raw JSON, with a copy button — handy for support tickets and bug reports.
### Body capture and redaction [#body-capture-and-redaction]
Capturing the request and response body is opt-in and controlled from [Privacy Controls](/features/privacy-controls). When body logging is disabled for a workspace, the **Request & Response** panel still appears but shows "No payload captured" — every other field is unaffected.
When body capture is enabled, the following are redacted before storage:
* **System prompts** — replaced with a `[redacted system prompt]` marker so personas and proprietary instructions never leave your account.
* **Tool outputs** — `tool` role messages and the `content` of tool result blocks (they often contain database rows, PII, or internal URLs).
* **Bearer tokens and `Authorization` headers** — stripped from any captured headers.
User messages, assistant replies, and tool *call* arguments are kept verbatim — the parts you almost always need when debugging.
### Streaming requests [#streaming-requests]
Streaming requests (`stream: true`) appear a few seconds after the stream closes, not when the first token arrives. The row shows:
* **TTFT** — ms from AnyRouter receiving the request to the first token reaching the client. The number to watch for perceived latency.
* **Latency** — wall-clock time from request start to stream close. Can be many seconds even when TTFT is small.
* **Speed** — `output_tokens / (latency − TTFT)`, how fast the upstream produced tokens once it started.
If a stream is cancelled by the client (tab closed, request aborted), the row records the partial token count and a `streamed: true` flag. Status is `success` if tokens were delivered, `error` if the upstream failed before producing any.
### Reading error rows [#reading-error-rows]
Error rows expose the upstream failure as a structured envelope. The detail view shows:
* **Code** — a stable string mapped from the upstream (`upstream_unavailable`, `context_length_exceeded`, `rate_limited`, `invalid_request`, …).
* **Message** — the upstream's human-readable message, trimmed.
* **Request ID** — AnyRouter's request id, the same value in the `X-Request-Id` response header and the JSON error body's `error.request_id` field.
* **cfRay** — the Cloudflare ray id, returned alongside `request_id` for requests that hit our edge.
When you escalate a failed request to support, include **both** the `request_id` and the `cfRay`. Together they let us pull the exact edge trace and the exact upstream trace. If a request failed across every upstream, the **Provider Responses** timeline usually tells you whether the cause was upstream rate limits, a provider outage, or a request the model rejected.
### Retention [#retention]
Dashboard and API **read-access is clamped to the plan window**. Rows older than that window are not returned from the dashboard or [`/api/v1/logs`](https://anyrouter.dev/docs/api-reference/overview), even if you omit a start date. AnyRouter does not automatically delete those rows.
| Plan | Visible log rows and captured bodies (dashboard and API) |
| ---------- | -------------------------------------------------------- |
| Free, Go | 7 days |
| Pro, Max | 30 days |
| Enterprise | 90 days |
Aggregated charts and per-key usage totals are kept indefinitely regardless of plan, so historical spend stays accurate after individual rows fall outside the access window. Export rows you need to keep via the logs API before they drop out of view.
## Configure [#configure]
Request Logs works out of the box — no setup needed to see rows. Two optional steps unlock the richest view:
1. **Enable body capture** from [Privacy Controls](/features/privacy-controls) to populate the **Request & Response** panel (subject to the redaction rules above).
2. **Send a `session_id`** (body field or `x-session-id` header) on each request to group multi-turn conversations under the **Sessions** tab.
## Use cases [#use-cases]
* **Debugging a user complaint.** Paste the request id from your app logs into the **Request ID** filter and open the row's captured response. No request id? Filter by the user's key and a tight time range — the finish reason often tells you immediately whether the model hit a length limit, refused, or generated what it returned.
* **Finding the most expensive requests.** Set the range to `Last 7 days`, pick the noisy key, and sort by **Cost**. The KPI grid's input/output/cached split usually reveals a system prompt that should be cached, a runaway tool loop, or an output cap that's too high.
* **Spotting prompt injection.** Filter by `status: error` and skim the messages — refusal codes (`content_policy_violation`, `safety_filter`) cluster around injection attempts. For successes that look off, open the body view and check for markers like "ignore previous instructions."
---
# Response Caching (/features/response-caching)
Prompt caching (see [Prompt Caching](/features/caching)) reduces the cost of a cache-*read* on a provider that still runs inference. Response caching goes a step further: when a request is byte-for-byte identical to one AnyRouter has already answered recently, the cached **response** is served directly — no upstream call at all, and no charge.
## When it helps [#when-it-helps]
Response caching pays off any time you send the exact same request more than once:
| Use case | Why it caches well |
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| Deterministic requests (`temperature: 0`) | Same input always produces the same output, so a cached response is indistinguishable from a fresh one |
| Embeddings | Embedding a given input is a pure function — the vector never changes |
| Repeated identical prompts | Health checks, smoke tests, and demo flows that resend the same payload |
| Development and debugging | Iterating on your own code around a fixed prompt without re-paying for every run |
If your requests vary even slightly (different `messages`, `temperature`, or any other field), each one is treated as distinct and caching has no effect.
## Automatic default [#automatic-default]
AnyRouter automatically applies a short cache window to requests it can safely reuse:
* Deterministic chat/completions requests (`temperature: 0` or unset default), embeddings, and speech requests are cached for **about 5 minutes** by default.
* Caching is **never** applied to zero-data-retention (ZDR) workspaces.
* Caching is skipped whenever a request explicitly opts out (see `skipCache` below).
You don't need to do anything to benefit from the default — it's a safety-net optimization, not a requirement.
## Controlling it yourself [#controlling-it-yourself]
For more control, set the cache TTL or opt a request out entirely.
### With the SDK [#with-the-sdk]
```typescript
import { AnyRouter } from "@anyr/sdk"
const client = new AnyRouter()
// Cache this exact response for 10 minutes.
const completion = await client.chat.completions.create(
{
model: "z-ai/glm-4.7-flash",
messages: [{ role: "user", content: "What is the capital of France?" }],
},
{ cacheTtl: 600 },
)
// Force a fresh upstream call, bypassing any cache.
await client.chat.completions.create(
{ model: "z-ai/glm-4.7-flash", messages: [{ role: "user", content: "Latest news?" }] },
{ skipCache: true },
)
```
The same options are available as `providerOptions.anyrouter` when using `@anyr/ai-sdk-provider`:
```typescript
import { anyrouter } from "@anyr/ai-sdk-provider"
import { generateText } from "ai"
const { text } = await generateText({
model: anyrouter("z-ai/glm-4.7-flash"),
prompt: "What is the capital of France?",
providerOptions: {
anyrouter: { cacheTtl: 600 },
},
})
```
### With raw headers [#with-raw-headers]
Any HTTP client can control caching directly with request headers:
| Header | Value | Effect |
| ------------------- | ------------------- | -------------------------------------------------------------- |
| `cf-aig-cache-ttl` | seconds, e.g. `600` | Cache this response for the given duration |
| `cf-aig-skip-cache` | `true` | Never cache this request, even if a default policy would apply |
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-H "cf-aig-cache-ttl: 600" \
-d '{
"model": "z-ai/glm-4.7-flash",
"messages": [{ "role": "user", "content": "What is the capital of France?" }]
}'
```
## TTL bounds [#ttl-bounds]
`cf-aig-cache-ttl` accepts a minimum of **60 seconds** and a maximum of **1 month** (2,678,400 seconds). Values outside that range are clamped by the gateway.
---
# Smart Routing (/features/routing)
Pinning your app to a single provider means one outage or rate-limit spike takes you down with it. Every request to AnyRouter instead flows through a routing layer that picks the best upstream for the requested model — deterministic by default, respectful of health and cooldown state, and tunable per request when you need reproducibility or cost and latency controls.
## Overview [#overview]
You can select a model in three ways:
| Method | Example |
| -------------------- | ------------------------------------------ |
| Direct model | `anthropic/claude-sonnet-4-6` |
| Preset reference | `@preset/code-reviewer` |
| Preset with override | `openai/gpt-5.4-mini@preset/code-reviewer` |
See [Presets](/features/presets) for full documentation on the preset syntax.
## How it works [#how-it-works]
### How a model maps to an upstream [#how-a-model-maps-to-an-upstream]
AnyRouter maintains a list of **upstream candidates** for every model id. Each candidate can include routing metadata such as priority, price, latency, and throughput. On each request, AnyRouter builds the candidate set, applies your request-level provider preferences, skips cooled-down backends, and dispatches to the highest-priority remaining candidate. If that upstream fails with a retryable error, AnyRouter walks the remaining ordered candidates as fallbacks — unless you disabled fallbacks for that request.
### Request-level provider preferences [#request-level-provider-preferences]
Steer routing directly in the request body with the `provider` object:
```json
{
"model": "openai/gpt-5.4-mini",
"provider": {
"only": ["OpenAI", "Groq"],
"order": ["Groq", "OpenAI"],
"sort": "latency",
"allow_fallbacks": true,
"max_price": {
"prompt": "1.00",
"completion": "2.00"
},
"preferred_max_latency": 300,
"preferred_min_throughput": 40
}
}
```
Supported controls:
* `only` limits the candidate set to the named providers.
* `ignore` removes named providers from the candidate set.
* `order` pins a preferred backend order before the default route order.
* `sort` reorders candidates by `price`, `latency`, `throughput`, or `exacto`.
* `require_params` (for example `["tools"]` or `["response_format"]`) keeps only backends that support those parameters. Distinct from boolean `require_parameters`.
* `min_context` is a token floor. `anyrouter/auto` drops members below it.
* `max_price.prompt` and `max_price.completion` filter out backends above your budget.
* `preferred_max_latency` drops candidates that exceed your latency target.
* `preferred_min_throughput` drops candidates that fall below your throughput target.
* `allow_fallbacks: false` disables retrying the next backend after the first candidate fails.
Provider names are normalized, so values like `OpenAI`, `openai`, `Z.AI`, and `Moonshot AI` map to the correct backend ids automatically.
### Failover [#failover]
A candidate is marked **unhealthy** for a short window when it returns a 5xx status, times out (`timeout_ms` exceeded), or exhausts its rate-limit budget. Unhealthy candidates are skipped until the cooldown expires. Requests that hit a fallback are still recorded with the final backend id, so you can see when failover happened.
### Forcing a specific upstream [#forcing-a-specific-upstream]
Set the `X-AnyRouter-Provider` header on any request to pin it to a single candidate:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-your-key" \
-H "X-AnyRouter-Provider: openrouter" \
-H "Content-Type: application/json" \
-d '{"model": "openai/gpt-5.4-mini", "messages": [{"role": "user", "content": "hi"}]}'
```
Useful for:
* **Reproducibility** — pin to a specific provider so your eval results don't silently drift when the router fails over.
* **BYOK tests** — verify a specific BYOK credential is working end-to-end.
* **Outage debugging** — route around a failing provider without waiting for the health cooldown.
If the forced provider is unhealthy, the request fails immediately. Header pinning is stricter than request-body preferences and bypasses normal fallback selection.
### Model aliases [#model-aliases]
Some models have multiple slugs that resolve to the same upstream, for compatibility with other gateways. For example, `anthropic/claude-sonnet-4-6` and `claude-sonnet-4-6` both route to the same backend. Canonical slugs are `provider/model`; bare slugs are resolved by lookup.
AnyRouter also ships built-in virtual aliases under `anyrouter/*`. They appear in the [models catalog](/features/models-catalog) and work on chat completions, messages, and responses:
| Alias | What it routes to |
| --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `anyrouter/auto` | Top usage + top stable platform models (free-tier mixed with credit-billed), health-aware failover. Honors `provider.sort: "exacto"`, `min_context`, `require_params: ["tools"]`, and `require_params: ["response_format"]`. `anyrouter/auto[1m]` / `[500k]` are min-context shortcuts, not separate listing ids. |
| `anyrouter/latest` | Tries the **newest** live chat models first: up to 20 models ordered by release date (newest first), then falls back to older ones. Models with poor recent success are skipped. Preview and alpha-named models are included only when they are actually used and healthy. A request may try up to 12 upstream hops. The response `model` field and the `X-AnyRouter-Model` header name the real model that answered. |
| `anyrouter/free` | Curated $0 platform models (Go plan). |
| `anyrouter/hermes` / `anyrouter/cowork` | Per-provider tool-calling chains, billed to credits. |
| `anyrouter/byok` / `anyrouter/coding` / `anyrouter/agent` | Chains built from your own provider keys. |
`anyrouter/latest` follows release dates, not traffic rank: a newer healthy model is always tried before an older one. An idle preview SKU with no usage never qualifies. Pin a concrete id when you need a specific generation.
Retired listing ids can keep working on chat completions after the SKU is removed from the catalog. The request is rewritten to a configured fallback (today `stealth/ox-alpha` and `stealth/ox-alpha[1m]` route to `anyrouter/auto`). The old model page stays up and says the model has been removed.
### Parallel upstreams for `anyrouter/free` and `anyrouter/auto` [#parallel-upstreams-for-anyrouterfree-and-anyrouterauto]
Some upstreams accept a request and then stay silent. To keep the first token fast, `anyrouter/free` and `anyrouter/auto` can race a second upstream:
* If the first upstream sends no usable token after a short delay (about 2.5 seconds), AnyRouter starts one more upstream: a different provider or model from the same alias.
* The first upstream to send a real token wins. The other one is cancelled.
* You are billed only for the upstream that answered.
* It applies to streaming requests only, fires at most once per request, and is skipped for very long prompts.
When a second upstream was started, the response carries a header naming the winner and the delay used:
```
x-anyrouter-hedge: won=;delay=
```
No header means only one upstream ran. The response `model` field always names the model that answered.
**Turn it off.** Go to [Dashboard → Settings](https://anyrouter.dev/dashboard/settings) → **Routing** and switch off **Parallel upstreams (faster first token)**. The same setting is `parallelHedgeEnabled` on `PUT /api/v1/user/preferences`; send `{"parallelHedgeEnabled": false}` to turn it off.
With it off, members are still tried one after another when one fails.
### Tracing and request ids [#tracing-and-request-ids]
Inference endpoints accept an optional request-body `trace` object:
```json
{
"trace": {
"trace_id": "4f8c2b9670b44b49a2e71e7fd5c0b1d3",
"trace_name": "checkout-flow",
"span_name": "summarize-cart"
}
}
```
When present, AnyRouter keeps that trace id attached to internal routing steps and forwards trace correlation metadata to AI Gateway-backed upstream calls. Every response also includes an `X-Request-ID` header so support can locate the exact request lifecycle.
### Rate-limit headers [#rate-limit-headers]
Every response includes rate-limit metadata for **both** the AnyRouter key and the upstream candidate that served the request:
```text
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 599
X-RateLimit-Reset: 1760000060
X-Upstream-RateLimit-Remaining: 998
```
Use these to back off gracefully — the router starts failing over if the selected candidate's upstream limit is close to exhaustion, but clients that respect the header get smoother behavior.
---
# Share a Key to the Shared Pool (/features/shared-pool)
The shared pool lets the community route requests through **donated** provider keys instead of AnyRouter's managed capacity. If you hold a provider account with quota you don't always use — free-tier limits, prepaid credits, a coding plan — you can donate that key to the pool and earn credits every time it serves someone else's request. This guide walks you through creating an account and sharing your first key.
## What the shared pool is [#what-the-shared-pool-is]
* **Community-served requests.** When you donate a key, AnyRouter can route other users' matching requests through your provider account, exactly as if you'd made the call yourself.
* **You earn credits for what your key serves.** Every request served through your donated key adds to a pending balance: **8% on Pro and Max, 5% on Go (or with no plan)** of the notional token cost. If a request costs $0.01 in tokens, a Pro donor earns $0.0008. On paid models you set a price for, you receive **what the caller paid minus a 10% platform fee** instead (see the FAQ). Credits are capped at $1 per key per UTC day; a key that hits the cap pauses until the next day.
* **Opt-in, per key.** Donating is a per-key toggle. A key you haven't flipped on is never shared.
* **Withdraw instantly.** Turn the toggle off and the key stops serving the pool immediately — there's no lock-in.
* **Your account comes first.** You always keep first call on your own key; the pool only uses spare capacity.
* **Eligibility is about routing, not your provider's terms.** A provider shows the donate option once AnyRouter has somewhere to route its traffic — that's not a claim your provider's Terms of Service permit sharing your key. You're responsible for confirming that before you donate.
## Share your first key [#share-your-first-key]
::::steps
:::step{title="Create an account"}
Sign up at **[anyrouter.dev](https://anyrouter.dev)**. It's free — no credit card, no credits required to get started.
:::
:::step{title="Add a provider key"}
Open **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**. In the **Available** grid, click **Add key** on the provider you want to donate, paste your provider API key, and save. AnyRouter validates the key against the provider before storing it. For step-by-step help generating a key at each provider, see the [BYOK Provider Setup Guide](/features/byok-providers).
:::
:::step{title="Toggle Donate to pool"}
On an eligible key, flip **Donate to pool** on. AnyRouter verifies the key is still live before enrolling it — you'll see a "Key verified" confirmation once the check passes (takes a moment). Your key joins the community pool right away and starts earning up to 8% of the token cost on every request it serves. This toggle only appears for providers AnyRouter can route to — review your provider's terms before donating.
:::
:::step{title="Track your earnings"}
Watch requests served and credits earned in your dashboard. Every served request adds to a pending balance — claim it into spendable balance whenever you like, or turn on **auto-claim** on the key to sweep it in automatically.
:::
::::
## Live pool stats [#live-pool-stats]
See what the pool is doing right now at **[/pool](https://anyrouter.dev/pool)**. The page shows aggregate activity — providers available, requests served, credits paid out — so you can gauge demand before donating. It's public and **aggregate-only**: no per-user data, no key details, and no request contents are ever shown.
To donate a key, visit **[/pool](https://anyrouter.dev/pool)**.
## Frequently asked questions [#frequently-asked-questions]
:::details{summary="Which providers can I donate?"}
Any provider AnyRouter has a working upstream for — that's an operational compatibility list, not a claim that your provider's Terms of Service permit sharing your key. Eligible providers show a **Donate to pool** toggle on their key in the BYOK dashboard; you're responsible for reviewing your own provider's terms before donating.
:::
:::details{summary="Can I set what callers pay?"}
On **All models** coverage every paid model your key covers is listed with its price. Each one starts at the provider's catalog price, and you have three choices per key:
* **Leave a model empty.** Callers pay the provider's catalog price for it. This is the default.
* **Type a price.** A flat USD price per 1M tokens for that model, from $0 up to 10% over its catalog price. It applies to input and output tokens alike.
* **Runtime dynamic.** Models you left empty are charged what the provider reports for each request, never more than the catalog price. If the provider reports no cost for a request, the catalog price applies.
Callers pay from their AnyRouter credits. The donate dialog shows an estimate and charges nothing. You can change or clear prices later from the same key.
What you receive depends on how the request was priced:
* **A price you typed, or runtime dynamic:** what the caller paid for that request, minus AnyRouter's 10% platform fee. At a $1.00 price you receive $0.90. A $0 price earns nothing.
* **A model left at the catalog price:** the usual reward on the catalog price (5% or 8%, by plan).
The dialog shows the price, the fee and your share for each model you price.
:::
:::details{summary="How much do I earn, and when?"}
You earn a share of the notional token cost of every request the pool routes through your key — **up to 8%**: 8% on the Pro and Max plans, 5% on Go or with no plan. The rate follows your current plan, so upgrading lifts it on your next served request. Rewards are capped at $1 per key per UTC day. On models you price, you receive what the caller paid minus a 10% platform fee instead of the percentage; that counts toward the same $1 daily cap. Every served request adds to a pending balance; claim it into spendable balance whenever you like, or turn on auto-claim to sweep it in automatically.
:::
:::details{summary="I donated before the platform fee. What changes for me?"}
Nothing until you choose. A donation made before these terms keeps the percentage reward on every model, including models you priced. To switch to receiving what callers pay minus the 10% platform fee on your priced models, open the key's pool settings in the dashboard, select **Review terms** and accept the new terms. Your key, prices and settings stay as they are.
You are not credited on requests you send through your own donated key, on failed requests, or when the caller's payment did not go through.
:::
:::details{summary="How do I stop donating?"}
Turn the **Donate to pool** toggle off. Your key leaves the pool instantly and goes back to serving only your own requests. You can re-enable it anytime.
If it was your last donated key, donation-backed Go pauses and this month's $4 credit is clawed back — re-donating in the same month restores the unspent remainder ($4 minus what you already used).
:::
:::details{summary="What happens if my donated key stops working?"}
We monitor every donated key continuously. What we pause depends on what failed.
**The whole key is paused** when the problem is the key or its account:
* the provider rejects the key (an auth error three times in a row),
* the provider account is out of credit,
* the key hit its spending limit, or its daily credit cap.
A paused key leaves the pool. If it was your only donated key and you were on the donation-backed Go plan, that plan is revoked on the next sync. You'll get an email with the details, and you can get it back by donating a new key or subscribing directly. Re-donating in the same month restores the unspent remainder of the $4 credit ($4 minus what you already used).
**Only one model is paused** when the problem is one model on your key: three server errors, "model not found" replies or timeouts in a row on that model, or a rate limit. Your key stays shared and keeps serving every other model. The dashboard shows "N models paused on this key", with each model, why it paused, and when it will be retried.
**How it recovers:**
* A paused model is retried on its own after a short wait: about a minute after a rate limit, five minutes after repeated errors. If it fails again, the wait doubles, up to 15 minutes for rate limits and 6 hours for errors. Click **Resume** to retry it right away.
* A key paused for an auth error comes back as soon as a check passes: click **Test** on the key, or wait for the next automatic check.
* A key out of credit or over its limit stays paused until you fix the account (for example, top it up) and resume it from the dashboard.
* A passing **Test** also lifts the pause on the model it tested.
:::
:::details{summary="Is my key safe?"}
Yes. Every key is **encrypted at rest with a per-row salt**, is write-only in the UI (shown only as an alias and masked identifier after save), and is **never logged** — it's stripped from request and response logs. Your key is only ever sent to the upstream provider it's scoped to. Pool stats are aggregate-only and never expose your key or usage.
:::
:::details{summary="Do I still get first call on my own key?"}
Always. Your own requests take priority; the pool only ever uses spare capacity on your key.
:::
:::details{summary="What does Free via Pool mean on a model?"}
The model is free because the shared pool serves it with donated keys at no charge to you. The label shows for everyone, but the pool itself is open to Go subscribers and to anyone with an active donated key. On the Free plan, a request to that model returns an error that points here. Subscribe to Go for $2 a month, or donate a working key, and the same request routes through the pool.
:::
---
# Tool Use (/features/tool-use)
A language model on its own can't check the weather, query your database, or book a meeting — it can only produce text. **Tool use** (also called function calling) closes that gap: you describe named functions, the model decides which to call and with what arguments, your code runs them, and you feed the results back for a final answer. AnyRouter normalizes tool use to the OpenAI schema, so every tool-capable model in the catalog works through the same request shape — swap the `model` id and your code is unchanged.
## Overview [#overview]
The loop has four steps: define tools, send them with your request, run whatever the model calls, and return the results as `tool` messages. The model then composes a natural-language answer from those results.
```typescript
const tools = [
{
type: "function",
function: {
name: "get_weather",
description: "Return the current weather for a city.",
parameters: {
type: "object",
properties: {
city: { type: "string", description: "City name" },
unit: { type: "string", enum: ["celsius", "fahrenheit"] },
},
required: ["city"],
},
},
},
]
```
## How it works [#how-it-works]
### Sending tools with a request [#sending-tools-with-a-request]
Pass your tools alongside the messages. `tool_choice: "auto"` lets the model decide whether to call one.
```typescript
const response = await client.chat.completions.create({
model: "openai/gpt-5.4-mini",
messages: [{ role: "user", content: "What's the weather in Tokyo?" }],
tools,
tool_choice: "auto",
})
```
### Handling tool calls [#handling-tool-calls]
When the model decides to call a tool, the response carries `tool_calls` instead of plain content, and `finish_reason` is `tool_calls`:
```json
{
"choices": [
{
"message": {
"role": "assistant",
"content": null,
"tool_calls": [
{
"id": "call_01ABC",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Tokyo\",\"unit\":\"celsius\"}"
}
}
]
},
"finish_reason": "tool_calls"
}
]
}
```
Run the tool, then send the result back as a `tool` role message. Include the original user message and the assistant's tool-call message so the model has full context:
```typescript
const toolCall = response.choices[0].message.tool_calls![0]
const args = JSON.parse(toolCall.function.arguments)
const weather = await getWeather(args.city, args.unit)
const followup = await client.chat.completions.create({
model: "openai/gpt-5.4-mini",
messages: [
{ role: "user", content: "What's the weather in Tokyo?" },
response.choices[0].message,
{
role: "tool",
tool_call_id: toolCall.id,
content: JSON.stringify(weather),
},
],
tools,
})
```
The model sees the tool result and composes a natural-language answer.
### Forcing or disabling a tool [#forcing-or-disabling-a-tool]
`tool_choice` controls whether and which tool the model may call:
```typescript
// Force a specific function
tool_choice: { type: "function", function: { name: "get_weather" } }
```
Use `"none"` to disable tool use entirely and force plain-text output, or `"auto"` (the default when tools are present) to let the model decide.
### Parallel tool calls [#parallel-tool-calls]
Modern models can emit **multiple** tool calls in a single turn. Iterate over the full `tool_calls` array, run them in parallel, and include one `tool` message per call in your follow-up request.
```typescript
const calls = response.choices[0].message.tool_calls ?? []
const results = await Promise.all(
calls.map(async (call) => ({
role: "tool" as const,
tool_call_id: call.id,
content: JSON.stringify(await runTool(call)),
})),
)
```
Always match every `tool_call_id` with exactly one `tool` message in the follow-up. A missing tool result leaves the model waiting on a call it already made and usually produces an error or a degraded answer.
### Structured outputs [#structured-outputs]
When you want JSON back without an actual side effect, use `response_format` instead of tools:
```typescript
response_format: { type: "json_object" }
```
For guaranteed conformance, provide a JSON schema:
```typescript
response_format: {
type: "json_schema",
json_schema: {
name: "weather_report",
schema: { /* JSON Schema */ },
strict: true,
},
}
```
## Configure [#configure]
Tool use needs no dashboard setup — it's part of every request. To adopt it:
1. **Define your tools** as an array of `{ type: "function", function: { name, description, parameters } }` objects, using JSON Schema for `parameters`.
2. **Send them** with `tools` and a `tool_choice` on your chat completion.
3. **Loop** on `finish_reason: "tool_calls"`: run each call, append a `tool` message per `tool_call_id`, and re-send until the model returns a normal completion.
4. **Swap models** freely — any tool-capable model in the [catalog](https://anyrouter.dev/models) accepts the same request shape.
## Related [#related]
* [Presets](/features/presets) — bundle `tools` and `tool_choice` into a reusable config.
* [Request Logs](/features/request-logs) — inspect tool-call arguments and results per request.
* [API Reference](/api-reference/overview) — the full chat completions schema.
---
# Usage Explorer (/features/usage-explorer)
"Which model is burning the most spend this week?" "Did latency regress after I switched keys?" "How does weekend traffic compare to weekdays?" Answering questions like these normally means building your own analytics pipeline. **Usage Explorer** is that pipeline, already built — an ad-hoc charting surface over your AnyRouter activity where you group, filter, and compare in a few clicks.
## Overview [#overview]
Open Usage Explorer at [`/dashboard/usage`](https://anyrouter.dev/dashboard/usage). Every view is URL-shareable — paste a link in Slack and your teammate lands on the exact same chart, filters and all.
A chart is built from four building blocks:
| Block | What it controls |
| --------------- | --------------------------------------------------- |
| **Dimension** | What rows are grouped by (one series per group) |
| **Metric** | What number goes on the y-axis |
| **Granularity** | The width of each bucket on the x-axis |
| **Filters** | Which requests are counted, applied before grouping |
## How it works [#how-it-works]
### Dimensions [#dimensions]
A dimension controls how rows are grouped. Pick one at a time.
| Dimension | What it shows | Use it when |
| -------------- | -------------------------------------------------------------------------- | ----------------------------------------------------- |
| `time` | One bar/line per bucket (hour, day, week) | You want a trend — "is my traffic growing" |
| `model` | One series per model id (e.g. `openai/gpt-5.4`) | You want to attribute spend to specific models |
| `key` | One series per API key | You're isolating cost or errors to a single key |
| `organization` | One series per org or workspace | You run multi-team billing and need a P\&L per team |
| `route` | One series per API path (`/chat/completions`, `/responses`, `/embeddings`) | You want to see how Responses vs. Chat traffic splits |
Group-by dimensions stack: pick `model` while filtering to a single key, and you see model mix **for that key only**.
### Metrics [#metrics]
The metric controls the y-axis. Switch metrics without losing your other filters.
| Metric | Unit | Notes |
| ----------------- | ------- | ---------------------------------------------------------- |
| Cost | USD | Pass-through provider cost. No AnyRouter markup. |
| Prompt tokens | tokens | Input side — includes cached prompt tokens. |
| Completion tokens | tokens | Output side — what the model actually generated. |
| Requests | count | One row per request, regardless of size. |
| p50 latency | ms | Median end-to-end latency, server-measured. |
| p95 latency | ms | 95th percentile — how slow your slow requests are. |
| p99 latency | ms | 99th percentile — tail latency. Watch this for SLOs. |
| Error rate | percent | Non-2xx responses divided by total requests in the bucket. |
| Cache hit ratio | percent | Cached prompt tokens divided by total prompt tokens. |
Percentile latency is computed **per bucket**, not over the whole window. A p95 bar for "Monday 14:00" is the 95th percentile of requests in that one hour, so it reacts quickly to incidents instead of being smoothed across a full day.
### Granularity [#granularity]
Granularity decides how wide each bucket is on the x-axis.
| Granularity | Range it covers well | Most useful for |
| ----------- | -------------------- | ---------------------------------------------------------- |
| Hour | Last 24–72 hours | Debugging a spike or incident — "what happened around 3pm" |
| Day | Last 7–60 days | Weekly cost reviews, day-over-day comparisons |
| Week | Last 60+ days | Quarterly trends, capacity planning, churn analysis |
Lower granularity (hour) is noisier but catches short events. Higher granularity (week) smooths noise out and is the right choice for a clean trend line.
### Filters [#filters]
Filters narrow the dataset **before** grouping. Every filter is AND-composed — a request must match all active filters to be counted.
* **Date range** — a start and end, or quick-presets (`Last 24h`, `Last 7d`, `Last 30d`, `Month to date`).
* **Model** — one or more model ids, for head-to-head comparisons.
* **API key** — one or more keys, by the `sk-ar-…` prefix shown in the dashboard.
* **Organization / workspace** — when you belong to multiple workspaces.
* **Route** — `/chat/completions`, `/responses`, `/embeddings`, `/messages`.
* **Status** — only `2xx`, only errors, or both.
* **Cached** — only cached or only uncached requests.
Every filter, dimension, metric, and granularity is encoded in the URL. A link like:
```
https://anyrouter.dev/dashboard/usage?granularity=day&dimension=cost&group_by=model&from=2026-04-01&to=2026-04-30&models=openai/gpt-5.4,anthropic/claude-sonnet-4-6
```
reproduces the exact chart. Bookmark it, drop it into an incident timeline, or send it to a teammate — no extra setup.
**Comparing two keys or two models side-by-side.** Set `group_by=key` (or `group_by=model`), then add both ids to the corresponding filter. The chart renders one series per group so you can read them off the same axis — the fastest way to A/B a prompt change across two keys, or to compare cost-efficiency between, say, `openai/gpt-5.4` and `anthropic/claude-sonnet-4-6` on identical traffic.
### Reading the charts [#reading-the-charts]
* **Bars vs. lines.** Bars suit discrete buckets (cost per day); lines suit trends across many buckets (requests per hour over a week). The chart picker switches without rebuilding the query.
* **Stacked vs. grouped.** Grouping by `model` or `key` stacks by default, so total height stays the overall metric. Switch to grouped (side-by-side) when comparing series matters more than reading the total.
* **Empty buckets.** Zero-value buckets stay on the x-axis so traffic gaps are visible. A missing bar means **no requests**, not missing data.
## Configure [#configure]
### Export to CSV [#export-to-csv]
Chart-level CSV export — one row per `(bucket, group)` pair, matching exactly what's on screen — is coming soon.
Need the data today? The individual requests behind any chart can already be exported as CSV via the API: `GET /api/v1/logs?format=csv`, which accepts the same date range, model, and key filters. See [Request Logs](/features/request-logs) for the full filter reference.
### Drill down to individual requests [#drill-down-to-individual-requests]
Usage Explorer is the aggregate view. To see the individual requests behind a bar — "show me every failed request in this 1-hour bucket" — jump to [Request Logs](/features/request-logs). Clicking a bar filters it straight into Logs.
## Frequently asked questions [#frequently-asked-questions]
:::details{summary="How is cost computed?"}
Cost is `prompt_tokens × input_price + completion_tokens × output_price`, using the per-model prices on each [model detail page](https://anyrouter.dev/models). Cache reads are billed at the upstream provider's cache-read rate. AnyRouter does not mark up cost — the dashboard number is what you actually pay.
:::
:::details{summary="What does cached mean here?"}
A request counts as **cached** when the upstream reports cached prompt tokens in `usage.prompt_tokens_details.cached_tokens` — either an explicit Anthropic-style breakpoint cache or OpenAI's automatic prefix caching. See [Prompt Caching](/features/caching). The `Cache hit ratio` metric is `cached_tokens / prompt_tokens`; `0.85` means 85% of your input tokens were cache reads.
:::
:::details{summary="Why doesn't the chart total match my invoice?"}
The chart shows **request-level cost** billed at request time. Your invoice may bundle in adjustments (credits applied, free-tier usage, refunds) that don't appear per-request but are reflected in [Credits](https://anyrouter.dev/dashboard/credits).
:::
:::details{summary="My chart is empty — what's wrong?"}
Check three things in order: (1) **Date range** — the default is `Last 30 days`; widen it if you only started today. (2) **Filters** — they AND together, so a misclicked model filter is the usual culprit; clear and re-add one at a time. (3) **Auth** — viewing a workspace you can't access returns zero rows, not an error; check the workspace switcher.
:::
---
# Authentication (/getting-started/authentication)
AnyRouter uses bearer tokens. Every request carries an `Authorization: Bearer ` header, and one AnyRouter key reaches every model in the catalog — you never send an upstream provider key from your app.
## Key format [#key-format]
AnyRouter-issued keys are prefixed with `sk-ar-`:
```text
sk-ar-v1-abc123def456ghi789jkl012mno345pqr678stu901vwx
```
The prefix lets you tell AnyRouter keys apart from upstream provider keys in logs, git diffs, and secret scanners.
## Sending the header [#sending-the-header]
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{"model": "openai/gpt-5.4-mini", "messages": [{"role": "user", "content": "hi"}]}'
```
The Anthropic Messages passthrough at `/api/v1/messages` accepts `x-api-key` instead of the bearer header — see the [Messages API reference](/api-reference/messages).
## Creating keys [#creating-keys]
Open the [API Keys dashboard](https://anyrouter.dev/dashboard/keys) and click **Create key**. You can set:
* **Name** — a human label (e.g. `production-backend`, `ci-smoketest`).
* **Allowed models** — optionally restrict the key to specific model ids. `anyrouter/auto` and `anyrouter/free` then route only to listed models in their own pool; call any other listed model directly.
* **Allowed endpoints** — optionally restrict which API endpoints the key can call.
* **Expiry** — an optional hard expiration date.
* **Rate limit override** — cap requests per minute on this key.
* **Spend limit** — optionally cap credits available to that key.
The full key is shown **once**. Copy it immediately and store it in your secret manager — AnyRouter only retains the prefix after the dialog closes.
Never commit keys to git. Use environment variables, a secret manager, or runtime injection. If a key leaks, rotate it immediately from the dashboard.
## Rotating keys [#rotating-keys]
Because AnyRouter keys are opaque and independent from upstream provider keys, rotation is zero-downtime — there's no need to coordinate with Anthropic, OpenAI, or any other provider.
::::steps
:::step{title="Create a replacement key"}
Create a new key with the same restrictions and limits as the one you're replacing.
:::
:::step{title="Deploy with the new key"}
Roll your application forward with the new key in its secret store.
:::
:::step{title="Verify traffic moved"}
Confirm requests now flow through the new key in the [usage dashboard](https://anyrouter.dev/dashboard/usage).
:::
:::step{title="Delete the old key"}
Once no traffic remains on the old key, delete it from the dashboard.
:::
::::
## BYOK (bring your own key) [#byok-bring-your-own-key]
If you prefer to pay providers directly, attach your own provider credentials to AnyRouter under **Dashboard → BYOK**. Requests routed through your BYOK credentials are billed to your upstream account and reported separately from AnyRouter credits.
BYOK is ideal for:
* Enterprises with existing upstream volume discounts.
* Teams that need data residency guarantees tied to a specific provider region.
* Users on provider-specific free tiers they want to preserve.
### Base URL override [#base-url-override]
Some providers, such as private OpenAI-compatible gateways, support custom endpoint URLs. Azure AI Foundry has its own resource-endpoint field in the key form (see [Azure](/features/byok-providers/azure)). If a provider supports this, you'll see a **Base URL** field in the BYOK configuration dialog. Enter your custom endpoint to route requests through your own deployment. Preconfigured Gateway routes — such as OpenRouter, DeepSeek, xAI, and the Z.ai Standard/Coding Plan options — use their listed provider route instead of an arbitrary base URL override.
## Key restrictions [#key-restrictions]
| Setting | Purpose |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `allowed_models` | Limit the key to a fixed set of model ids. |
| `allowed_endpoints` | Limit the key to specific API paths. Keys created in the dashboard grant the inference endpoints and leave management endpoints (keys, credits, logs, usage) off — turn them on in **Permissions** when you need them. Chat Completions, Messages, and Responses are one grant: a key allowed to call any of those three may call the others. |
| `rate_limit` | Set requests per minute for that key. |
| `limit` + `limit_reset` | Cap credit usage for the key on a daily, weekly, or monthly cycle. |
| `expires_at` | Hard-expire the key at an ISO 8601 timestamp. |
If you leave these fields unset, the key inherits the default account behavior.
:::details{summary="I get a 401 Unauthorized"}
Check that your key starts with `sk-ar-` and is sent as `Authorization: Bearer `. If the key was recently deleted or expired, create a new one from the [dashboard](https://anyrouter.dev/dashboard/keys). For the Messages endpoint, use the `x-api-key` header instead.
:::
---
# Quickstart (/getting-started/quickstart)
AnyRouter is an OpenAI- and Anthropic-compatible LLM gateway on Cloudflare. One API key reaches hundreds of models, with automatic failover, free models, bring-your-own-key at $0 markup, and a shared key Pool. This page gets you from zero to your first response in under five minutes.
Three things stay the same no matter how you call AnyRouter:
| Setting | Value |
| -------- | ------------------------------------------- |
| Base URL | `https://anyrouter.dev/api/v1` |
| API key | your `sk-ar-v1-…` inference key |
| Model id | `provider/model`, e.g. `z-ai/glm-4.7-flash` |
## Make your first request [#make-your-first-request]
::::steps
:::step{title="Create an API key"}
Sign in to the [dashboard](https://anyrouter.dev/dashboard/keys) and create a new key — no card required. AnyRouter inference keys are prefixed with `sk-ar-v1-` so you can tell them apart from upstream provider keys.
A new account starts at **$0 balance** with no included credits. Before your first paid-model call, either [top up](https://anyrouter.dev/dashboard/credits) or [subscribe to Go](/features/pricing) ($2/mo, includes $4/mo credits) — or donate a provider key to unlock Go for free. See [Credits & Billing](/features/credits#getting-your-first-credits) for all the ways to get started.
:::
:::step{title="Set your key as an environment variable"}
```bash
export ANYROUTER_API_KEY="sk-ar-v1-your-key"
```
:::
:::step{title="Send a chat completion"}
The most direct call is a plain HTTP request to `/api/v1/chat/completions` — no SDK required, so it works from any language that can make an HTTP call.
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "z-ai/glm-4.7-flash",
"messages": [{"role": "user", "content": "Hello!"}]
}'
```
:::
::::
Keys are scoped per organization. Create a separate key for every environment (dev, staging, prod) so you can rotate them independently.
To stream tokens as they arrive, add `"stream": true` to the request body — the response becomes a stream of [server-sent events](/guides/streaming).
## Call it from your stack [#call-it-from-your-stack]
The same request works from any OpenAI-compatible client. Pick whichever fits your project.
AnyRouter is a drop-in replacement for the OpenAI API. Point `base_url` at AnyRouter, pass your key, and use a `provider/model` id — no other code changes.
```python
from openai import OpenAI
client = OpenAI(
base_url="https://anyrouter.dev/api/v1",
api_key="sk-ar-v1-your-key",
)
response = client.chat.completions.create(
model="z-ai/glm-4.7-flash",
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)
```
```typescript
import OpenAI from "openai"
const client = new OpenAI({
baseURL: "https://anyrouter.dev/api/v1",
apiKey: "sk-ar-v1-your-key",
})
const response = await client.chat.completions.create({
model: "z-ai/glm-4.7-flash",
messages: [{ role: "user", content: "Hello!" }],
})
console.log(response.choices[0].message.content)
```
Install [`@anyr/sdk`](https://www.npmjs.com/package/@anyr/sdk) and call `chat.completions.create`. The client reads `ANYROUTER_API_KEY` when you omit the key, and targets `https://anyrouter.dev/api/v1` by default. Attribution headers are sent automatically so usage shows up in [rankings and analytics](/features/app-attribution).
```bash
npm install @anyr/sdk
```
```bash
export ANYROUTER_API_KEY="sk-ar-v1-your-key"
```
```ts
import { AnyRouter } from "@anyr/sdk"
// Reads the key from ANYROUTER_API_KEY when omitted.
const client = new AnyRouter()
const completion = await client.chat.completions.create({
model: "z-ai/glm-4.7-flash",
messages: [{ role: "user", content: "Hello!" }],
})
console.log(completion.choices[0].message.content)
```
See the [SDK guide](/sdk) and [Chat Completions API](/api-reference/chat-completions) reference for the full request and response shape.
Any OpenAI-compatible framework — the Vercel AI SDK, LangChain, LlamaIndex — works the same way: set the base URL, supply your key, and reference models by their `provider/model` id.
```typescript
import { createOpenAI } from "@ai-sdk/openai"
const anyrouter = createOpenAI({
baseURL: "https://anyrouter.dev/api/v1",
apiKey: process.env.ANYROUTER_API_KEY,
})
const model = anyrouter("anthropic/claude-sonnet-4-6")
```
## Or launch a coding agent [#or-launch-a-coding-agent]
Install the native CLI (Linux, macOS, Windows — no Node required), sign in once, and spawn Claude Code / Codex / OpenCode / Grok / Pi through the gateway:
```bash
curl -fsSL https://anyrouter.dev/setup.sh | bash
anyr auth login
anyr claude --install --ok
anyr usage
```
Full reference: [AnyRouter CLI](/cli). For a Herdr fleet loop with Grok Bot, see [Grok Bot, Herdr, and Anyr CLI](/guides/grok-bot-herdr-anyr).
## Choose a model [#choose-a-model]
Model ids follow the `provider/model` convention. Browse the full list on the [models page](https://anyrouter.dev/models) or fetch it via the API:
```bash
curl https://anyrouter.dev/api/v1/models \
-H "Authorization: Bearer sk-ar-v1-your-key"
```
A few popular choices:
| Model id | Provider | Notes |
| ----------------------------- | --------- | ----------------------------------- |
| `z-ai/glm-4.7-flash` | Z-AI | Fast, low-cost — good first request |
| `anthropic/claude-sonnet-4-6` | Anthropic | 1M context, strong coding |
| `anthropic/claude-haiku-4-5` | Anthropic | Fast and cheap |
| `google/gemini-3.1-pro` | Google | Multimodal, long context |
## How AnyRouter routes a request [#how-anyrouter-routes-a-request]
Every request flows through three stages — key auth, model selection, and upstream dispatch — with automatic fallback if the first provider is unavailable:
---
# AI Agent Integration (/guides/ai-agent-integration)
Paste this brief into a coding agent (Claude Code, Cursor, Codex, Aider, Hermes, …) when you want it to add AnyRouter to an existing codebase. AnyRouter is OpenAI-compatible, so the change is almost always the same: new base URL, new key, new model id.
## Before you start [#before-you-start]
* An AnyRouter API key (prefix `sk-ar-`). Create one in the [dashboard](https://anyrouter.dev/dashboard/keys).
* Store the key in the project's normal secret manager. Do not commit it.
## The contract [#the-contract]
| Setting | Value |
| -------------------- | --------------------------------------------------- |
| Base URL | `https://anyrouter.dev/api/v1` |
| API key env var | `ANYROUTER_API_KEY` (prefix `sk-ar-`) |
| Default chat model | `z-ai/glm-4.7-flash` |
| Cheapest / fastest | `openai/gpt-5.4-nano`, `anthropic/claude-haiku-4-5` |
| High-quality | `anthropic/claude-sonnet-4-6`, `openai/gpt-5.4` |
| Main endpoint | `POST /chat/completions` (OpenAI-compatible) |
| Anthropic-native | `POST /messages` for `anthropic/*` models |
| Modern agent surface | `POST /responses` (typed output items + tools) |
## Set it up [#set-it-up]
::::steps
:::step{title="Paste this prompt into your agent"}
```text
Integrate AnyRouter as the LLM provider for this project.
API: OpenAI-compatible
Base: https://anyrouter.dev/api/v1
Key: process.env.ANYROUTER_API_KEY (prefix sk-ar-)
Model: process.env.ANYROUTER_MODEL (fallback z-ai/glm-4.7-flash)
Rules:
1. Reuse the project's existing LLM client if one exists.
If not, add one small module that exports a configured OpenAI client.
2. Never hardcode the key.
3. Keep request/response shapes compatible with the current feature.
4. Preserve existing UI and behavior — only swap the LLM source.
5. If the project uses Anthropic Messages directly:
- keep `anthropic/*` calls on /messages, OR
- migrate them to /chat/completions for cross-provider routing.
6. Add a smoke test: one short prompt that prints the response.
```
:::
:::step{title="Add the client module"}
The agent adds (or reuses) a small client, using the language snippet below.
:::
:::step{title="Run the smoke test"}
Confirm the integration with the curl call under [Verify](#verify).
:::
::::
## Client module [#client-module]
Use whichever language matches your project. Streaming uses the same client — set `stream: true` and iterate.
```bash
npm install openai
```
```typescript
import OpenAI from "openai"
export const anyrouter = new OpenAI({
baseURL: "https://anyrouter.dev/api/v1",
apiKey: process.env.ANYROUTER_API_KEY,
})
export const MODEL = process.env.ANYROUTER_MODEL ?? "z-ai/glm-4.7-flash"
export async function generate(prompt: string) {
const r = await anyrouter.chat.completions.create({
model: MODEL,
messages: [{ role: "user", content: prompt }],
})
return r.choices[0]?.message.content ?? ""
}
```
```bash
pip install openai
```
```python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://anyrouter.dev/api/v1",
api_key=os.environ["ANYROUTER_API_KEY"],
)
r = client.chat.completions.create(
model=os.getenv("ANYROUTER_MODEL", "z-ai/glm-4.7-flash"),
messages=[{"role": "user", "content": "Reply with: anyrouter-ok"}],
)
print(r.choices[0].message.content)
```
## Verify [#verify]
Run the smoke test. A reply of `anyrouter-ok` confirms the integration is live.
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "z-ai/glm-4.7-flash",
"messages": [{"role":"user","content":"Reply with: anyrouter-ok"}],
"max_tokens": 20
}'
```
The integration is done when:
* Calls hit `https://anyrouter.dev/api/v1`
* The key comes from `ANYROUTER_API_KEY` (no inline literal)
* Model ids use the `provider/model` form
* Streaming still streams if it streamed before
* Errors flow through the project's existing error path
## Optional extras [#optional-extras]
:::details{summary="Attribute usage to your app"}
Set attribution headers to identify your app in the dashboard.
```typescript
new OpenAI({
baseURL: "https://anyrouter.dev/api/v1",
apiKey: process.env.ANYROUTER_API_KEY,
defaultHeaders: {
"X-AnyRouter-Title": "My App",
"X-AnyRouter-Source": "web-app",
"X-AnyRouter-Version": process.env.APP_VERSION ?? "dev",
},
})
```
:::
:::details{summary="Set routing preferences"}
Add a `provider` block to the request body for price caps, latency sort, or a fixed upstream order.
```json
{
"provider": {
"sort": "latency",
"max_price": { "prompt": "2.00", "completion": "8.00" },
"allow_fallbacks": true
}
}
```
:::
## Related [#related]
* [Quickstart](/getting-started/quickstart) — first request in 2 minutes
* [Migrate from OpenAI](/guides/migrate-from-openai) — swap an existing OpenAI app
* [Chat Completions API](/api-reference/chat-completions) — the main endpoint
* [Responses API](/api-reference/responses) — typed output items and tools
* [App Attribution](/features/app-attribution) — how the attribution headers work
---
# AnyRouter vs OpenRouter (/guides/anyrouter-vs-openrouter)
If you searched "OpenRouter alternative" and landed here, the short version is this: OpenRouter is a mature credits marketplace that sells you access to models through one endpoint. AnyRouter lets you **bring your own API keys, paid subscriptions, or both — and run coding agents through them with a single command.** Pick OpenRouter when you want to pay one vendor for access to a wide catalog; pick AnyRouter when you already have keys or subscriptions and want them pooled, portable, and resilient behind the agent you already use.
This page is an honest, side-by-side breakdown so you can decide which one fits.
## The one-command difference [#the-one-command-difference]
OpenRouter gives you one base URL and one API key. To use it with a coding agent, you still hand-edit the agent's config — set the base URL, set the key, pick a model, and repeat on every machine.
```bash
# With OpenRouter, you configure the agent yourself
export OPENAI_BASE_URL=https://openrouter.ai/api/v1
export OPENAI_API_KEY=sk-or-your-key
# then edit your agent's settings file...
```
```bash
curl -fsSL https://anyrouter.dev/setup.sh | bash
anyr claude # Claude Code
anyr codex # Codex
anyr grok # Grok Build
```
The AnyRouter launcher points the agent at any model in the catalog, injects your key and presets for the session, and wipes them when it exits — so your machine stays clean and the same setup works on every laptop, server, and CI runner. OpenRouter has no equivalent launcher.
## Capability comparison [#capability-comparison]
| Capability | AnyRouter | OpenRouter |
| ------------------------------------------------ | --------- | --------------------------------------------- |
| One-command agent launcher (`anyr …`) | ✓ | ✗ |
| BYO keys + subscriptions pooled behind one URL | ✓ | ✗ |
| Config, keys & skills follow you across machines | ✓ | ✗ |
| Auto-failover across **your** pooled keys/subs | ✓ | ✗ |
| Auto-failover across providers | ✓ | ✓ |
| OpenAI-compatible endpoint | ✓ | ✓ |
| Public model catalog / credits marketplace | Growing | ✓ (larger) |
| Leaves your machine clean on exit | ✓ | Partial — no launcher, so nothing to clean up |
A few of those rows deserve a closer look.
## Where AnyRouter differs [#where-anyrouter-differs]
* **One-command agent launcher.** `curl | bash` installs a native `anyr` binary; `anyr claude` and `anyr codex` wrap the agent for you. First run signs you in (browser or device code) and later runs go straight into the tool. OpenRouter has no launcher — you wire the agent up yourself.
* **Config that follows you.** Your API keys, presets, and skills live in your account. When you run the CLI, they're injected on demand for that session and removed when it ends. The same configuration is available on every machine you sign into. OpenRouter hands you a single key and endpoint; it doesn't sync agent config, presets, or skills across machines.
* **Never-fail routing.** When an upstream returns `401`, `429`, or `5xx`, AnyRouter automatically retries against a weighted pool of **your own keys and subscriptions** — so your agent keeps going instead of dying mid-task. OpenRouter fails over across providers it controls, but not across keys or subscriptions you brought yourself.
* **Key + subscription pooling (BYOK+).** You can pool your own API keys *and* paid provider subscriptions behind a single URL, then route across all of them. This is the core thing OpenRouter doesn't do: they sell access to their credits, and you can't bring your own keys or subs into the routing layer.
## Pricing & fees [#pricing--fees]
Fees on credit purchases and BYOK:
| Fee | AnyRouter | OpenRouter |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| Credit top-up fee | Card processor's 5% + $0.50 at checkout; AnyRouter keeps **$0** of it | 5.5% on Standard (min $0.80), 8% on Business |
| BYOK usage fee | **0%**, always | No fee up to $25,000/month of list-price inference, then 5% |
| Subscription | Free; Go $2/mo ($4/mo credits); Pro $10/mo ($20/mo credits); Pro+ $45/mo ($100/mo credits); Max $100/mo ($300/mo credits) | Pay-as-you-go credits only, no subscription tier |
If you route through your own provider keys, AnyRouter never takes a cut, at any volume. OpenRouter's BYOK fee applies only above its monthly allowance, so light BYOK usage is free on both; heavy BYOK usage is where the gap shows. On a card purchase the two fees are close in size. The difference is who keeps it. (OpenRouter figures read from its [pricing page](https://openrouter.ai/pricing), [FAQ](https://openrouter.ai/docs/faq) and [BYOK docs](https://openrouter.ai/docs/guides/overview/auth/byok) on October 16, 2026. Check them before you decide.)
## Where OpenRouter wins [#where-openrouter-wins]
We're not going to pretend otherwise:
* **Bigger public model catalog.** OpenRouter aggregates a very wide list of providers and models, and their credits marketplace is mature.
* **Large existing user base.** It's well-known, well-documented, and has been around longer.
* **Direct provider relationships and pay-per-use credits.** If you don't want to manage your own API keys or subscriptions, OpenRouter lets you buy access directly with no setup.
If your priority is "one vendor, pay as you go, don't touch keys," OpenRouter is the right call. If you already pay for keys or subscriptions and want them pooled, portable, and resilient behind your coding agent, that's AnyRouter's lane.
## When to choose each [#when-to-choose-each]
* Already have API keys or paid subscriptions you want to pool and route across.
* Run coding agents (Claude Code, Codex, OpenCode, Grok Build) and want them configured with one command.
* Need the same setup on multiple laptops, servers, or CI runners.
* Are tired of an agent dying mid-task because a single key hit a rate limit.
* Want to buy model access from one vendor without managing your own keys.
* Need a specific model or provider that's only available through their marketplace.
* Prefer a mature, well-established routing service with a large user base.
Coming from OpenRouter? The migration is mostly a base-URL and key change — see [Migrate from OpenRouter](/guides/migrate-from-openrouter) for the step-by-step, including an agent prompt that does the swap for you.
## Related [#related]
* [Migrate from OpenRouter](/guides/migrate-from-openrouter) — the drop-in base-URL and key swap.
* [OpenRouter alternative](https://anyrouter.dev/blog/openrouter-alternative) — what to compare before you switch.
* [OpenRouter alternatives 2026](https://anyrouter.dev/blog/openrouter-alternative-anyrouter) — verifiable differences, no invented rankings.
* [CLI reference](/cli) — install nothing and launch an agent with one command.
* [Unify AI subscriptions](/scenario-examples/unify-ai-subscriptions) — bring your keys and subscriptions into one preset.
---
# Get a provider API key (/guides/byok-provider-keys)
With [Bring Your Own Key (BYOK)](/features/byok) you route requests through your own accounts at the provider's own prices — AnyRouter adds **$0 markup**, and your keys are **encrypted at rest** and never shown back to anyone.
Adding any provider takes three steps:
1. **Choose a provider** from the list below.
2. **Create a key** in that provider's console (each links straight to its API-keys page).
3. **Add it to AnyRouter** at [anyrouter.dev/dashboard/byok](https://anyrouter.dev/dashboard/byok) → pick the provider → **Add key** → paste → **Save**.
When you save, AnyRouter runs a quick live check against the provider before storing the key. If the key is wrong or the account has no credit, the error shows up right away and nothing is saved.
> Most providers show a new key **only once**. Copy it immediately and keep it somewhere safe.
***
## Frontier & popular providers [#frontier--popular-providers]
### OpenAI [#openai]
Sign up at [platform.openai.com](https://platform.openai.com), then open **[platform.openai.com/api-keys](https://platform.openai.com/api-keys)** and click **Create new secret key**. Keys start with `sk-`. Paid usage only — add a payment method first.
### Anthropic [#anthropic]
Create an account at [platform.claude.com](https://platform.claude.com), then go to **[platform.claude.com/settings/keys](https://platform.claude.com/settings/keys)** and click **Create Key**. Keys start with `sk-ant-`. Paid usage (some accounts get starter credits).
### Google AI Studio (Gemini) [#google-ai-studio-gemini]
Sign in with a Google account at [aistudio.google.com](https://aistudio.google.com), then open **[aistudio.google.com/apikey](https://aistudio.google.com/apikey)** and click **Create API key**. Keys start with `AIza`. **Free tier available.**
### xAI (Grok) [#xai-grok]
Sign up at [x.ai](https://x.ai), then create a key in the console at **[console.x.ai](https://console.x.ai)** under **API Keys**. Keys start with `xai-`. Paid usage.
### DeepSeek [#deepseek]
Register at [platform.deepseek.com](https://platform.deepseek.com), then open **[platform.deepseek.com/api\_keys](https://platform.deepseek.com/api_keys)** and click **Create API key**. Keys start with `sk-`. Paid usage.
### Mistral [#mistral]
Sign up at [console.mistral.ai](https://console.mistral.ai), then go to **[console.mistral.ai/api-keys](https://console.mistral.ai/api-keys)** and create a key. **Free tier available** for experimentation.
***
## Fast-inference providers [#fast-inference-providers]
### Groq [#groq]
Sign up at [console.groq.com](https://console.groq.com), then open **[console.groq.com/keys](https://console.groq.com/keys)** and click **Create API Key**. Keys start with `gsk_`. **Free tier available.**
### Cerebras [#cerebras]
Create an account at [cloud.cerebras.ai](https://cloud.cerebras.ai), then create a key from the dashboard under **API Keys**. **Free tier available.**
### SambaNova [#sambanova]
Sign up at [cloud.sambanova.ai](https://cloud.sambanova.ai), then open **[cloud.sambanova.ai/apis](https://cloud.sambanova.ai/apis)** and generate a key. Free credits to get started.
### NVIDIA NIM [#nvidia-nim]
Sign in at [build.nvidia.com](https://build.nvidia.com), pick a model, and click **Get API Key**. Keys start with `nvapi-`. **Free tier available.**
### DeepInfra [#deepinfra]
Register at [deepinfra.com](https://deepinfra.com), then open **[deepinfra.com/dash/api\_keys](https://deepinfra.com/dash/api_keys)** and create a token.
### Together [#together]
Sign up at [together.ai](https://together.ai), then open **[api.together.ai/settings/api-keys](https://api.together.ai/settings/api-keys)** and click **Create key**.
### Fireworks [#fireworks]
Sign up at [fireworks.ai](https://fireworks.ai), then open **[app.fireworks.ai/settings/users/api-keys](https://app.fireworks.ai/settings/users/api-keys)** and click **Create API key**.
### Baseten [#baseten]
Register at [baseten.co](https://baseten.co), then open **[app.baseten.co/settings/api\_keys](https://app.baseten.co/settings/api_keys)** and click **Create API key**.
### GMICloud [#gmicloud]
Sign in at **[console.gmicloud.ai](https://console.gmicloud.ai)** and create a key under your organization settings.
### Novita AI [#novita-ai]
Sign up at [novita.ai](https://novita.ai), then open **[novita.ai/settings/key-management](https://novita.ai/settings/key-management)** and click **Add New Key**.
### Cohere [#cohere]
Sign up at [cohere.com](https://cohere.com), then open **[dashboard.cohere.com/api-keys](https://dashboard.cohere.com/api-keys)**. A free trial key is created automatically; click **Create API Key** for more. **Free trial tier available.**
### Venice [#venice]
Sign up at [venice.ai](https://venice.ai), then open **[venice.ai/settings/api](https://venice.ai/settings/api)** and click **Generate New API Key**.
### SiliconFlow [#siliconflow]
Register at [cloud.siliconflow.com](https://cloud.siliconflow.com), then open **[cloud.siliconflow.com/account/ak](https://cloud.siliconflow.com/account/ak)** and click **Create API Key**.
### Ollama Cloud [#ollama-cloud]
Sign in at [ollama.com](https://ollama.com), then open **[ollama.com/settings/keys](https://ollama.com/settings/keys)** and create a key. **Free tier available.**
***
## Chinese frontier labs [#chinese-frontier-labs]
### Z.ai (GLM) [#zai-glm]
Sign up at [z.ai](https://z.ai), then open **[z.ai/manage-apikey/apikey-list](https://z.ai/manage-apikey/apikey-list)** and create a key. Use the same page's switcher for a **Coding Plan** key if you have a GLM Coding subscription.
### Moonshot AI (Kimi) [#moonshot-ai-kimi]
Register at [platform.moonshot.ai](https://platform.moonshot.ai), then open **[platform.moonshot.ai/console/api-keys](https://platform.moonshot.ai/console/api-keys)** and click **Create API Key**.
### Qwen (Alibaba Model Studio) [#qwen-alibaba-model-studio]
Sign in to Alibaba Cloud Model Studio at [modelstudio.console.alibabacloud.com](https://modelstudio.console.alibabacloud.com), then open **API Keys** in the sidebar and click **Create API Key**. Note keys are region-specific (International vs China endpoints).
### StepFun [#stepfun]
Sign up at [platform.stepfun.ai](https://platform.stepfun.ai), then open **[platform.stepfun.ai/interface-key](https://platform.stepfun.ai/interface-key)** to create a key.
### LongCat (Meituan) [#longcat-meituan]
Register at [longcat.chat/platform](https://longcat.chat/platform), then open the **API Keys** page and click **Create API Key**. Keys start with `sk-`.
***
## Aggregators & routers [#aggregators--routers]
### OpenRouter [#openrouter]
Sign up at [openrouter.ai](https://openrouter.ai), then open **[openrouter.ai/keys](https://openrouter.ai/keys)** and create a key. Keys start with `sk-or-`. **Free tier available** for select models.
Hitting limits on `:free` models? See [Unblock free-model rate limits with your own OpenRouter key](/guides/unblock-free-model-rate-limits-openrouter-key).
### ZenMux [#zenmux]
Sign up at [zenmux.ai](https://zenmux.ai), then open **[zenmux.ai/settings/keys](https://zenmux.ai/settings/keys)** and create a key. **Free tier available.**
### OrcaRouter [#orcarouter]
Sign up at **[orcarouter.ai](https://www.orcarouter.ai)** and create a key in your account settings. Keys start with `sk-orca-`.
### OpenCode Zen [#opencode-zen]
Sign in at **[opencode.ai/auth](https://opencode.ai/auth)** to get a key. **Free tier available.** (The "Go" variant uses the same login page.)
### Nous Research [#nous-research]
Sign in at **[portal.nousresearch.com](https://portal.nousresearch.com)** and create a key from the portal.
### Blackbox AI [#blackbox-ai]
Sign in at [app.blackbox.ai](https://app.blackbox.ai), then create a key from **[app.blackbox.ai/dashboard](https://app.blackbox.ai/dashboard)**. Keys start with `bb_`.
### Poolside [#poolside]
Poolside access is granted through your Poolside account. Once you have access, create a key in the Poolside console and add it below.
***
## Advanced: cloud provider keys [#advanced-cloud-provider-keys]
### Google Vertex AI [#google-vertex-ai]
Vertex uses a Google Cloud **service account** rather than a simple key. In the [Google Cloud console](https://console.cloud.google.com), create a service account with Vertex AI access, download its **JSON key**, and paste the full JSON (with an optional region field) into the AnyRouter key field.
***
## Add your key to AnyRouter [#add-your-key-to-anyrouter]
Once you have a key from any provider above:
1. Go to **[anyrouter.dev/dashboard/byok](https://anyrouter.dev/dashboard/byok)**.
2. Find the provider under **Available** and click **Add key**.
3. Paste the key into the **API key** field and click **Save**.
AnyRouter validates the key live before storing it, encrypts it at rest, and starts routing that provider's models through your account at list price.
---
# Cherry Studio (/guides/cherry-studio)
[Cherry Studio](https://cherry-ai.com) is a cross-platform desktop AI client for Windows, macOS, and Linux. It supports 300+ AI models, multi-model chat, knowledge bases, code agents, and image generation. Since it speaks the OpenAI-compatible wire protocol, pointing it at AnyRouter is a settings-only change — and every chat then routes through one gateway.
## Why route through AnyRouter [#why-route-through-anyrouter]
* **One key.** A single `sk-ar-` key unlocks every model in the AnyRouter catalog — no separate keys per provider.
* **Audit logs.** Every Cherry Studio request is recorded in your dashboard with cost, latency, and token usage.
* **Free model swaps.** Change the model in Cherry Studio without rotating keys.
## Set it up [#set-it-up]
### Step 1 — Create an AnyRouter API key [#step-1--create-an-anyrouter-api-key]
Go to your [dashboard](https://anyrouter.dev/dashboard/keys) and create a new API key. It will be prefixed with `sk-ar-`.
### Step 2 — Add AnyRouter as a custom provider [#step-2--add-anyrouter-as-a-custom-provider]
Open Cherry Studio and navigate to **Settings → Model Services**. Scroll to the bottom of the provider list and click **Add Provider** (or the `+` button). Fill in the fields:
| Field | Value |
| ----------------- | ------------------------------ |
| **Provider Name** | AnyRouter |
| **Provider Type** | OpenAI Compatible |
| **API Address** | `https://anyrouter.dev/api/v1` |
| **API Key** | your `sk-ar-…` key |
### Step 3 — Fetch available models [#step-3--fetch-available-models]
After saving the provider, click **Get Model List** (or the sync/refresh icon next to the provider). Cherry Studio will call `GET /api/v1/models` and populate the full AnyRouter catalog automatically.
### Step 4 — Start chatting [#step-4--start-chatting]
Select any AnyRouter model from the model picker and start a conversation. Your requests are billed against your AnyRouter credits and appear in your request logs.
## Verify [#verify]
After your first message, open your [request logs](https://anyrouter.dev/dashboard/logs). The call should appear within a second or two with the model name, token counts, and cost — confirmation Cherry Studio is routing through AnyRouter.
## Recommended models [#recommended-models]
Use the model picker to browse all available models. A few starting points:
* `google/gemini-3.1-flash` — fast, cheap, large context
* `anthropic/claude-sonnet-4-6` — best quality for long conversations and documents
* `openai/gpt-5.4-mini` — strong general-purpose model, good balance of speed and quality
* `google/gemini-3.5-flash` — great for image understanding and multimodal tasks
Browse the full catalog at [anyrouter.dev/models](https://anyrouter.dev/models).
## Troubleshooting [#troubleshooting]
**401 Unauthorized** — verify your `sk-ar-` key is active and has credits. Check the [dashboard](https://anyrouter.dev/dashboard/keys).
**Model not found** — click the model sync button to refresh the model list. If a specific model is missing, confirm it is listed at `GET /api/v1/models`.
**Slow responses** — try a faster model such as `google/gemini-3.1-flash-lite`. Check [latency overhead](/guides/latency-overhead) for benchmarks.
## Related [#related]
* [Cherry Studio on GitHub](https://github.com/CherryHQ/cherry-studio)
* [Chat Completions API](/api-reference/chat-completions)
* [Smart Routing](/features/routing)
* [Models catalog](https://anyrouter.dev/models)
---
# Claude Code with AnyRouter — free models, one API key (/guides/claude-code)
Point [Claude Code](https://claude.com/product/claude-code) at AnyRouter and every session runs through one gateway: unified billing, request audit logs, and a single `sk-ar-` key that reaches every model AnyRouter serves. It's a config-only change — no code, no rebuild.
## Why route through AnyRouter [#why-route-through-anyrouter]
* **Drop-in.** Zero code changes. The fastest path is one command; the manual path is two environment variables.
* **One key.** A single AnyRouter API key unlocks every Anthropic model and more.
* **Audit logs.** Every request lands in your dashboard with cost, latency, and token usage.
* **Swap models freely.** Run Claude Code on Anthropic models, or on GLM-5.2 with your own Z-AI key (BYOK).
This is the narrative "use Claude Code with AnyRouter" guide. For the full command reference — flags, profiles, `--dry-run`, and the login flow — see the canonical **[Claude Code CLI setup](/cli/claude-code)**.
## Set it up [#set-it-up]
One command handles everything:
```bash
curl -fsSL https://anyrouter.dev/setup.sh | bash
anyr claude
```
The first run sets you up once: paste an `sk-ar-` key, or log in at anyrouter.dev (a key is generated for you). Your config is saved and reused, and the same key also powers `anyr codex`.
Full options (`--model`, `--effort`, profiles, `--dry-run`) are in the **[CLI reference](/cli/claude-code)**.
Claude Code reads `ANTHROPIC_BASE_URL` and `ANTHROPIC_API_KEY` from the environment. Point them at AnyRouter:
```bash
export ANTHROPIC_BASE_URL="https://anyrouter.dev/api"
export ANTHROPIC_API_KEY="sk-ar-your-anyrouter-key"
```
Create the key in your [dashboard](https://anyrouter.dev/dashboard/keys) first — every AnyRouter key starts with `sk-ar-`. Drop these two lines into `~/.bashrc` or `~/.zshrc` so they persist across shells, then run `claude` as usual.
Prefer to keep a separate Anthropic key for other work? Put the env vars in a wrapper script instead of your shell rc — see the wrapper recipe in the **[CLI reference](/cli/claude-code)**.
## GLM-5.2 with your own Z-AI key (BYOK) [#glm-52-with-your-own-z-ai-key-byok]
Prefer Z-AI's GLM-5.2 as your coding agent? Bring your own Z-AI key and AnyRouter routes Claude Code to it — you pay Z-AI directly (or use your Z-AI Coding Plan subscription), and AnyRouter adds nothing to the per-token cost.
GLM-5.2 has a **1M-token context**, a **128K output window**, and controllable **reasoning effort**, so it's a strong drop-in for long-horizon coding sessions.
### Step 1 — Add your Z-AI key [#step-1--add-your-z-ai-key]
Open [Settings → BYOK](https://anyrouter.dev/dashboard/byok) and paste your Z-AI API key. If you have a **Z-AI Coding Plan**, paste that key — AnyRouter routes it through the Coding Plan endpoint automatically. Your key is encrypted at rest and only used to sign your own requests.
### Step 2 — Point Claude Code at GLM-5.2 [#step-2--point-claude-code-at-glm-52]
The CLI is the simplest path — it picks up your AnyRouter key and just takes a model:
```bash
anyr claude --model "z-ai/glm-5.2"
```
Prefer the manual env-var route? Keep the same two vars as above, then select the model per session:
```bash
export ANTHROPIC_BASE_URL="https://anyrouter.dev/api"
export ANTHROPIC_API_KEY="sk-ar-your-anyrouter-key"
# Interactive session on GLM-5.2
claude --model "z-ai/glm-5.2" "Refactor this module and add tests"
```
### Reasoning effort [#reasoning-effort]
GLM-5.2 supports controllable thinking. AnyRouter maps the standard `reasoning_effort` levels onto Z-AI's thinking control: `low`/`medium`/`high`/`max` enable thinking, while `none`/`minimal` disable it. Use Claude Code's `/effort` command (or the CLI `--effort` flag) to set it per session:
```bash
anyr claude --model "z-ai/glm-5.2" --effort high "Plan a migration"
```
### 1M context [#1m-context]
To use GLM-5.2's full 1M-token window in Claude Code, raise the auto-compact threshold so Claude Code keeps the long context instead of summarizing early:
```bash
export CLAUDE_CODE_AUTO_COMPACT_WINDOW=1000000
```
## Free models and auto routing [#free-models-and-auto-routing]
Set the model to an AnyRouter router id instead of a fixed model:
| Model id | What it does |
| ---------------- | -------------------------------------------------------------------------------------------- |
| `anyrouter/free` | Routes to the best free model available right now, with automatic fallback. No credits used. |
| `anyrouter/auto` | Picks from top models (free and paid) with health-aware failover. Paid members use credits. |
```bash
anyr claude --model anyrouter/free
# or, with the manual env vars set:
claude --model anyrouter/free
```
`anyrouter/free` needs no provider keys. It has no daily cap on any plan. Details: [Free Tier](/features/free-tier).
If `anyrouter/auto` or `anyrouter/free` returns `400 no_eligible_models`, your privacy or routing settings (for example Zero Data Retention or blocked providers) excluded every candidate — relax them in the dashboard or pick a specific model.
## Bring your own keys [#bring-your-own-keys]
Already pay for a provider? Add its key in AnyRouter and Claude Code uses it through the same base URL. BYOK requests are billed by your provider. See [Use your own provider keys](/guides/byok-provider-keys) and [BYOK](/features/byok).
## MCP tools [#mcp-tools]
`anyr claude` connects the AnyRouter MCP gateway for you when **Auto-connect in coding agents** is on in the [dashboard](https://anyrouter.dev/dashboard/mcp). It uses the same key the session launches with, so there is nothing to paste. To turn it off, pass `--no-mcp` on a launch, run `anyr mcp auto off`, or switch it off in the dashboard.
## Verify [#verify]
Run any prompt, then confirm the request routed through AnyRouter:
```bash
anyr claude "Explain the architecture of this project"
```
Open your [request logs](https://anyrouter.dev/dashboard/logs) — the call should appear within a second or two, with the model name, token counts, and cost. If it's there, you're routing through AnyRouter.
## Troubleshooting [#troubleshooting]
**401 or 403** — confirm the key is active in your [dashboard](https://anyrouter.dev/dashboard/keys) and that `ANTHROPIC_API_KEY` (or your saved CLI config) holds a valid `sk-ar-` key.
**Model not found** — make sure the model id exactly matches an AnyRouter id; browse [Messages-compatible models](https://anyrouter.dev/models?endpoint=messages). For GLM-5.2 via BYOK, confirm your Z-AI key is saved under [Settings → BYOK](https://anyrouter.dev/dashboard/byok).
**Long agent runs time out** — bump the SDK timeout for long-horizon flows: `export API_TIMEOUT_MS=3000000`.
## Related [#related]
* [Free Tier](/features/free-tier)
* [Cline](/guides/cline) · [Kilo Code](/guides/kilo-code) · [Continue](/guides/continue)
* [Claude Code CLI setup](/cli/claude-code) — canonical command reference
* [Grok Bot, Herdr, and Anyr CLI](/guides/grok-bot-herdr-anyr) — fleet coding with Herdr spaces
* [Codex](/guides/codex)
* [OpenCode](/guides/opencode)
* [Supported models](https://anyrouter.dev/models?endpoint=messages)
* [Messages API](/api-reference/messages)
---
# AnyRouter CLI (/guides/cli)
This page has moved. See the **[AnyRouter CLI documentation](/cli)** for the full reference, including:
* [Overview](/cli) — what the CLI does and how it works
* [Claude Code setup](/cli/claude-code)
* [Codex setup](/cli/codex)
* [OpenCode setup](/cli/opencode)
* [Configuration reference](/cli/configuration) — `config.yaml` fields, JSON schema, all CLI flags
* [Login with AnyRouter](/cli/login) — auto browser sign-in, device code, `--paste` / `--key`
---
# Cline with AnyRouter — free models, one API key (/guides/cline)
[Cline](https://cline.bot) has a built-in **OpenAI Compatible** provider, so it can talk to AnyRouter with no plugin and no code. You get free models through `anyrouter/free`, every catalog model behind one `sk-ar-` key, and a log row for each request.
## Set it up [#set-it-up]
::::steps
:::step{title="Create an AnyRouter key"}
Open [Dashboard → Keys](https://anyrouter.dev/dashboard/keys) and create a key. Every AnyRouter inference key starts with `sk-ar-`.
:::
:::step{title="Add AnyRouter as an OpenAI Compatible provider"}
In Cline, click the gear icon to open settings, then set:
| Field | Value |
| ---------------- | ------------------------------ |
| **API Provider** | `OpenAI Compatible` |
| **Base URL** | `https://anyrouter.dev/api/v1` |
| **API Key** | your `sk-ar-` key |
| **Model** | `anyrouter/free` |
:::
:::step{title="Send a message and check the logs"}
Ask Cline anything, then open your [request logs](https://anyrouter.dev/dashboard/logs). The request appears with the model that answered, tokens, and cost.
:::
::::
Cline is an agent that calls tools. Pick a model that supports tool calling — browse [Chat Completions models](https://anyrouter.dev/models?endpoint=chat-completions). Under Cline's model configuration you can also set the context window and max output tokens to match the model's catalog page.
## Which model id to use [#which-model-id-to-use]
| Model id | What it does |
| ---------------- | -------------------------------------------------------------------------------------------------------- |
| `anyrouter/free` | Routes to the best free model available right now, with automatic fallback. No credits used. |
| `anyrouter/auto` | Picks from top models (free and paid) with health-aware failover. Paid members use credits. |
| `owner/model` | A specific model, for example `z-ai/glm-5.2`. Copy ids from the [catalog](https://anyrouter.dev/models). |
## Free models [#free-models]
`anyrouter/free` costs nothing and needs no provider keys. It has no daily cap on any plan. Details: [Free Tier](/features/free-tier).
## Bring your own keys [#bring-your-own-keys]
Already pay for a provider? Add its key in AnyRouter and Cline uses it through the same base URL. BYOK requests are billed by your provider. See [Use your own provider keys](/guides/byok-provider-keys) and [BYOK](/features/byok).
## Troubleshooting [#troubleshooting]
**401** — the API Key field does not hold an active `sk-ar-` key. Check it in [Dashboard → Keys](https://anyrouter.dev/dashboard/keys).
**404 or model not found** — the Model field must match an AnyRouter id exactly, including the `owner/` prefix.
**400 `no_eligible_models` on `anyrouter/auto` or `anyrouter/free`** — your privacy or routing settings (for example Zero Data Retention or blocked providers) excluded every candidate. Relax them in the dashboard, or pick a specific model.
## Related [#related]
* [AnyRouter CLI](/cli)
* [Kilo Code](/guides/kilo-code) · [Continue](/guides/continue) · [OpenCode](/guides/opencode) · [Claude Code](/guides/claude-code) · [Codex](/guides/codex)
* [AnyRouter MCP with Cline](/mcp/cline)
* [Free Tier](/features/free-tier)
---
# Codex with AnyRouter — free models, one API key (/guides/codex)
Point [Codex](https://developers.openai.com/codex/cli) at AnyRouter and every run flows through one gateway: unified billing, request audit logs, and a single `sk-ar-` key that reaches every model AnyRouter serves. Codex speaks the OpenAI-compatible wire protocol, so this is a config-only change.
## Why route through AnyRouter [#why-route-through-anyrouter]
* **One key** for every model Codex can call — no per-provider keys.
* **Audit logs** with cost, latency, and tokens on every request.
* **Swap models freely** without rotating credentials.
This is the narrative "use Codex with AnyRouter" guide. For the full command reference — flags, profiles, `--dry-run`, and the login flow — see the canonical **[Codex CLI setup](/cli/codex)**.
## Set it up [#set-it-up]
One command handles everything:
```bash
curl -fsSL https://anyrouter.dev/setup.sh | bash
anyr codex
```
The first run sets you up once: paste an `sk-ar-` key, or log in at anyrouter.dev (a key is generated for you). Your config is saved to `~/.anyrouter/config.yaml` and reused next time — and the same key also powers `anyr claude` for Claude Code.
Anything after `--` forwards straight to Codex:
```bash
anyr codex --model auto --effort xhigh -- exec
```
See the **[CLI reference](/cli/codex)** for all options.
Codex reads `OPENAI_BASE_URL` and `OPENAI_API_KEY`. Point them at AnyRouter's OpenAI-compatible endpoint:
```bash
export OPENAI_BASE_URL="https://anyrouter.dev/api/v1"
export OPENAI_API_KEY="sk-ar-your-anyrouter-key"
```
Create the key in your [dashboard](https://anyrouter.dev/dashboard/keys) first — every AnyRouter key starts with `sk-ar-`. Every Codex request now routes through AnyRouter, billed against your credits and recorded in your audit logs.
## Free models and auto routing [#free-models-and-auto-routing]
Set the model to an AnyRouter router id instead of a fixed model:
| Model id | What it does |
| ---------------- | -------------------------------------------------------------------------------------------- |
| `anyrouter/free` | Routes to the best free model available right now, with automatic fallback. No credits used. |
| `anyrouter/auto` | Picks from top models (free and paid) with health-aware failover. Paid members use credits. |
```bash
anyr codex --model anyrouter/free
```
`anyrouter/free` needs no provider keys. It has no daily cap on any plan. Details: [Free Tier](/features/free-tier).
If `anyrouter/auto` or `anyrouter/free` returns `400 no_eligible_models`, your privacy or routing settings (for example Zero Data Retention or blocked providers) excluded every candidate — relax them in the dashboard or pick a specific model.
## Bring your own keys [#bring-your-own-keys]
Already pay for a provider? Add its key in AnyRouter and Codex uses it through the same base URL. BYOK requests are billed by your provider. See [Use your own provider keys](/guides/byok-provider-keys) and [BYOK](/features/byok).
## MCP tools [#mcp-tools]
`anyr codex` connects the AnyRouter MCP gateway for you when **Auto-connect in coding agents** is on in the [dashboard](https://anyrouter.dev/dashboard/mcp). It uses the same key the session launches with, so there is nothing to paste. To turn it off, pass `--no-mcp` on a launch, run `anyr mcp auto off`, or switch it off in the dashboard.
## Verify [#verify]
Run any Codex command, then open your [request logs](https://anyrouter.dev/dashboard/logs). The call should appear within a second or two, with the model name, token counts, and cost. If it's there, you're routing through AnyRouter.
## Troubleshooting [#troubleshooting]
**401 or 403** — confirm the key is active in your [dashboard](https://anyrouter.dev/dashboard/keys) and that `OPENAI_API_KEY` (or your saved CLI config) holds a valid `sk-ar-` key.
**Model not found** — make sure the model id exactly matches an AnyRouter id; browse [Chat-Completions-compatible models](https://anyrouter.dev/models?endpoint=chat-completions).
## Related [#related]
* [Free Tier](/features/free-tier)
* [Cline](/guides/cline) · [Kilo Code](/guides/kilo-code) · [Continue](/guides/continue)
* [Codex CLI setup](/cli/codex) — canonical command reference
* [Claude Code](/guides/claude-code)
* [OpenCode](/guides/opencode)
* [Supported models](https://anyrouter.dev/models?endpoint=chat-completions)
* [Chat Completions API](/api-reference/chat-completions)
---
# Continue with AnyRouter — free models, one API key (/guides/continue)
[Continue](https://continue.dev) talks to any OpenAI-compatible API through its `openai` provider and an `apiBase` override. Point it at AnyRouter and you get free models through `anyrouter/free`, every catalog model behind one `sk-ar-` key, and a log row for each request.
## Set it up [#set-it-up]
::::steps
:::step{title="Create an AnyRouter key"}
Open [Dashboard → Keys](https://anyrouter.dev/dashboard/keys) and create a key. Every AnyRouter inference key starts with `sk-ar-`.
:::
:::step{title="Add AnyRouter models to config.yaml"}
Edit `~/.continue/config.yaml` (Windows: `%USERPROFILE%\.continue\config.yaml`) and add models with `provider: openai` and AnyRouter's base URL:
```yaml
name: My Config
version: 0.0.1
schema: v1
models:
- name: AnyRouter Free
provider: openai
model: anyrouter/free
apiBase: https://anyrouter.dev/api/v1
apiKey: ${{ secrets.ANYROUTER_API_KEY }}
roles:
- chat
- edit
- apply
- name: AnyRouter Auto
provider: openai
model: anyrouter/auto
apiBase: https://anyrouter.dev/api/v1
apiKey: ${{ secrets.ANYROUTER_API_KEY }}
```
Store your `sk-ar-` key as the `ANYROUTER_API_KEY` secret rather than pasting it into the file. For a local `config.yaml`, add `ANYROUTER_API_KEY=sk-ar-...` to `~/.continue/.env`; on Continue Hub, add it as a secret.
:::
:::step{title="Pick the model and check the logs"}
Choose **AnyRouter Free** in Continue's model picker, send a message, then open your [request logs](https://anyrouter.dev/dashboard/logs). The request appears with the model that answered, tokens, and cost.
:::
::::
Don't give `anyrouter/free` the `autocomplete` role. Continue sends a request on almost every keystroke, which would flood the free-model rate limit in minutes; keep autocomplete on a separate fast model.
## Which model id to use [#which-model-id-to-use]
| Model id | What it does |
| ---------------- | -------------------------------------------------------------------------------------------------------- |
| `anyrouter/free` | Routes to the best free model available right now, with automatic fallback. No credits used. |
| `anyrouter/auto` | Picks from top models (free and paid) with health-aware failover. Paid members use credits. |
| `owner/model` | A specific model, for example `z-ai/glm-5.2`. Copy ids from the [catalog](https://anyrouter.dev/models). |
## Free models [#free-models]
`anyrouter/free` costs nothing and needs no provider keys. It has no daily cap on any plan. Details: [Free Tier](/features/free-tier).
## Bring your own keys [#bring-your-own-keys]
Already pay for a provider? Add its key in AnyRouter and Continue uses it through the same `apiBase`. BYOK requests are billed by your provider. See [Use your own provider keys](/guides/byok-provider-keys) and [BYOK](/features/byok).
## Troubleshooting [#troubleshooting]
**401** — `apiKey` does not resolve to an active `sk-ar-` key. Check the secret name and the key in [Dashboard → Keys](https://anyrouter.dev/dashboard/keys).
**404 or model not found** — `model` must match an AnyRouter id exactly, including the `owner/` prefix, and `apiBase` must end in `/api/v1`.
**400 `no_eligible_models` on `anyrouter/auto` or `anyrouter/free`** — your privacy or routing settings (for example Zero Data Retention or blocked providers) excluded every candidate. Relax them in the dashboard, or pick a specific model.
## Related [#related]
* [AnyRouter CLI](/cli)
* [Cline](/guides/cline) · [Kilo Code](/guides/kilo-code) · [OpenCode](/guides/opencode) · [Claude Code](/guides/claude-code) · [Codex](/guides/codex)
* [Free Tier](/features/free-tier)
---
# curl (/guides/curl)
[curl](https://curl.se) transfers data with URLs and is the fastest way to test the API by hand, script it, or wire it into CI. AnyRouter is fully OpenAI-compatible, so any curl command that works against OpenAI works here by changing the URL and key.
## Before you start [#before-you-start]
* An AnyRouter API key (prefix `sk-ar-`). Create one in the [dashboard](https://anyrouter.dev/dashboard/keys).
* Export it once so the examples below can reference it:
```bash
export ANYROUTER_API_KEY="sk-ar-your-key-here"
```
## Make your first call [#make-your-first-call]
::::steps
:::step{title="Send a chat request"}
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "google/gemini-3.5-flash",
"messages": [{"role": "user", "content": "Hello!"}]
}'
```
:::
:::step{title="Stream the response"}
Add `"stream": true` and `--no-buffer` to receive server-sent events as the response generates.
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
--no-buffer \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"messages": [{"role": "user", "content": "Count to 10, slowly."}],
"stream": true
}'
```
:::
::::
## More recipes [#more-recipes]
:::details{summary="List available models"}
```bash
curl https://anyrouter.dev/api/v1/models \
-H "Authorization: Bearer $ANYROUTER_API_KEY"
```
:::
:::details{summary="Check your credits"}
```bash
curl https://anyrouter.dev/api/v1/credits \
-H "Authorization: Bearer $ANYROUTER_API_KEY"
```
:::
:::details{summary="Health check (no auth required)"}
```bash
curl -A "my-status-probe/1.0" https://anyrouter.dev/api/v1/health
```
:::
:::details{summary="Multi-turn conversation"}
Pass the full `messages` array to continue a conversation.
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-mini",
"messages": [
{"role": "user", "content": "What is AnyRouter?"},
{"role": "assistant", "content": "AnyRouter is an AI model gateway that lets you access many models through one API."},
{"role": "user", "content": "How do I get started?"}
]
}'
```
:::
:::details{summary="Attribute usage to your app"}
Set `X-Title` and `X-Source` to appear in [app rankings](https://anyrouter.dev/data/apps) and attribute usage to your project.
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Title: My Script" \
-H "X-Source: my-script" \
-d '{
"model": "google/gemini-3.5-flash",
"messages": [{"role": "user", "content": "Summarize this text."}]
}'
```
See [app attribution](/features/app-attribution) for how these headers work.
:::
:::details{summary="Pipe a prompt from a file"}
```bash
cat prompt.txt | jq -Rsc '{model: "anthropic/claude-sonnet-4-6", messages: [{role: "user", content: .}]}' \
| curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d @-
```
:::
## Verify [#verify]
A `200` response with a JSON body containing a `choices` array means the call succeeded. If you get a `401`, re-check that `$ANYROUTER_API_KEY` is exported and starts with `sk-ar-`.
## Related [#related]
* [Chat Completions API](/api-reference/chat-completions) — the full request schema
* [Streaming](/guides/streaming) — how server-sent events work
* [Errors](/guides/errors) — status codes and fixes
* [App attribution](/features/app-attribution) — the `X-Title` / `X-Source` headers
---
# Dashboard Tour (/guides/dashboard-tour)
This tour walks through every page in the AnyRouter dashboard at `https://anyrouter.dev/dashboard` in the order a team usually needs them. It is split into three paths:
* **Day-1 path** — go from sign-up to a working API call and a verified log entry.
* **Week-1 path** — understand spend, lock in defaults, and tighten privacy.
* **Scaling path** — add teammates, bring your own keys, browse models, and govern usage with per-key limits.
Each step explains *when* a team typically arrives at it, lists *what you'll do*, and links to the dedicated docs page for deeper reading.
If you only have ten minutes, do the Day-1 path. Everything else can wait until you have real traffic to look at.
## Quick map [#quick-map]
| Stage | Pages, in order |
| ------- | ------------------------------------------------------ |
| Day 1 | Settings → Keys → Quickstart → Logs |
| Week 1 | Usage Explorer → Credits → Presets → Privacy |
| Scaling | Organizations → BYOK → Models catalog → Per-key limits |
Jump to a section:
* [Day-1 path](#day-1-path)
* [Week-1 path](#week-1-path)
* [Scaling path](#scaling-path)
* [What next](#what-next)
***
## Day-1 path [#day-1-path]
The goal of Day 1 is simple: make one real API call from your laptop, see it land in the logs, and confirm credits were deducted. Four pages get you there.
### 1. Settings — confirm the account is yours [#1-settings--confirm-the-account-is-yours]
This is the first place to land after signing in. The Settings page is where you check that the account email, display name, and default workspace match what you expect, and where you enable two-factor authentication before you generate any keys.
Teams skip this step at their peril — a missed 2FA setup on Day 1 becomes a much harder migration once dozens of keys exist.
**What you'll do**
* Confirm the email shown matches the address you signed up with.
* Enable two-factor authentication.
* Set your timezone so logs and usage charts render in local time.
* Pick a default workspace if you belong to more than one.
Read more: [Account settings](/features/account-settings).
### 2. Keys — mint your first API key [#2-keys--mint-your-first-api-key]
Once the account is set up, head to the Keys page and create your first API key. Every AnyRouter key starts with the `sk-ar-` prefix and is shown in full only once — copy it into a password manager or your local `.env` file before closing the dialog.
For a first key, leave the default scopes alone. You can come back later to add per-key rate limits, expiry dates, and IP allowlists once you understand your traffic.
**What you'll do**
* Click **New key**, name it something like `laptop-dev`.
* Copy the `sk-ar-...` value into your local `.env` (it is shown once).
* Leave rate limits and expiry blank for now.
* Note the key's last-four suffix shown in the table — that is how you'll identify it in logs.
Read more: [API keys](/features/api-keys).
### 3. Quickstart — run the curl command [#3-quickstart--run-the-curl-command]
With a key in hand, the Quickstart page in the docs gives you a copy-pasteable `curl` command that hits `https://anyrouter.dev/api/v1/chat/completions`. Run it from your terminal with `Authorization: Bearer sk-ar-...` and you should see a streamed JSON response within a second or two.
If anything fails, the error envelope returned includes `requestId` and `cfRay` — keep those handy, they make support tickets resolvable in minutes instead of hours.
**What you'll do**
* Export your key: `export ANYROUTER_API_KEY=sk-ar-...`.
* Paste the `curl` snippet from the Quickstart and hit enter.
* Confirm you get a 200 response with a `choices[0].message.content` field.
* If you see a non-200, copy the `requestId` from the response body.
Read more: [Quickstart](/getting-started/quickstart).
### 4. Logs — verify the call landed [#4-logs--verify-the-call-landed]
The final Day-1 stop is the Logs page (sometimes called Request Logs). Within a few seconds of your `curl`, a row should appear with the model name, token counts, cost, and the last four characters of your key.
Click the row to expand it. You'll see the full request and response (subject to your privacy settings — see the Week-1 path), latency breakdown, and which upstream served the call. This is where you'll spend most of your debugging time over the next few weeks.
**What you'll do**
* Filter by your key's last-four suffix to find your test call.
* Click the row to expand request and response payloads.
* Note the `cost_credits` and `tokens_in` / `tokens_out` fields.
* Bookmark this page — you will come back to it.
Read more: [Request logs](/features/request-logs).
At this point, Day 1 is done. You have a working integration. Everything below is about operating it well.
***
## Week-1 path [#week-1-path]
Once real traffic is flowing, the questions change. *How much am I spending? On which models? Are my defaults sensible? What data is being retained?* The Week-1 path answers those.
### 5. Usage Explorer — see where the money goes [#5-usage-explorer--see-where-the-money-goes]
The Usage Explorer is the dashboard's analytics surface. Stack-by-day charts break down spend, tokens, and request counts by model, by key, by app attribution tag, and by status. Filter to the last 7 days the first time you open it — that is enough to spot the model that quietly costs ten times the others.
Teams typically discover three things here in the first week: one model is doing most of the work, one key is doing most of the traffic, and one app or environment is responsible for most of the cost. Use that to inform the rest of the Week-1 path.
**What you'll do**
* Set the date range to the last 7 days.
* Group by model to see which models dominate spend.
* Group by key to see which integrations are loudest.
* Export a CSV if you want to share numbers with your team.
Read more: [Usage Explorer](/features/usage-explorer).
### 6. Credits — top up and set alerts [#6-credits--top-up-and-set-alerts]
The Credits page splits your balance into two cards — **monthly credits** (issued by AnyRouter: your plan's monthly credit — Go $4, Pro $20, Pro+ $100, Max $300 — plus referral or promotional bonuses; the Free plan includes none) and **top-up credits** (the credits you purchase, which never expire) — alongside a combined summary showing your total balance, total spend, and a burn-rate estimate. Usage is charged to your monthly credits first, so your purchased top-ups last longer. If your Week-1 spend chart shows you'll run out before payday, this is where to top up manually. It is also where you enable low-balance email alerts so you don't get surprised on a Sunday.
Automatic top-up when your balance runs low is coming soon — for now, use manual top-ups and alerts with enough runway.
**What you'll do**
* Check your current balance and the seven-day burn rate.
* Enable low-balance email alerts at a threshold that gives you 3+ days of runway.
* Top up manually when alerts fire (auto-refill is not yet available).
* Note your invoice email if you need PDF receipts.
Read more: [Credits and billing](/features/credits).
### 7. Presets — codify your defaults [#7-presets--codify-your-defaults]
A Preset is a saved bundle of model, system prompt, sampling parameters, and tool definitions. Once you know which model and which system prompt your team keeps reaching for, save it as a Preset and call it by name (`@preset/my-team-summarizer`) instead of repeating the same body fields in every request.
Presets pay for themselves the first time you need to switch models across an entire codebase — change the preset, every caller follows.
**What you'll do**
* Identify the model + system prompt combo your team uses most.
* Save it as a Preset with a memorable slug.
* Switch one app over to the preset and confirm responses still look right.
* Bookmark the Preset page; you'll add more as patterns emerge.
Read more: [Presets](/features/presets).
### 8. Privacy — decide what gets stored [#8-privacy--decide-what-gets-stored]
The Privacy Controls page is where you decide how much of each request and response is retained for logs, analytics, and debugging. Defaults are conservative, but if you process regulated data (PHI, payment info, EU personal data) tighten them on Day 1 of Week 1, not later.
The two settings most teams change are *log payloads* (full vs. metadata-only) and *retention window* (default vs. 7 days). For zero-retention modes, expect debugging to get harder — that trade is the point.
**What you'll do**
* Read the current policy summary on the page.
* Decide whether to keep full payloads, metadata only, or nothing.
* Set a retention window that matches your compliance posture.
* If you toggle to metadata-only, run a test call and confirm Logs reflects the change.
Read more: [Privacy controls](/features/privacy-controls).
***
## Scaling path [#scaling-path]
By the time you reach this path, you have traffic, you have a budget, and you have at least one teammate asking for access. The Scaling path is about going from a single-user account to a governed team.
### 9. Organizations — bring teammates in [#9-organizations--bring-teammates-in]
An Organization is a shared workspace with its own credit balance, keys, presets, and logs. Create one, invite teammates by email, and assign roles (`owner`, `admin`, `member`). Personal accounts and Organizations coexist; you can be in several at once and switch between them from the dashboard header.
The most common Week-2 mistake is sharing a personal API key over Slack. Don't. Create an Organization, invite the teammate, let them mint their own key under the org.
**What you'll do**
* Create an Organization and name it after your team or product.
* Invite teammates and pick roles deliberately (only `owner` can change billing or top up credits).
* Move shared Presets into the Organization workspace.
* Have each teammate mint their own `sk-ar-...` key inside the Organization.
Read more: [Organizations](/features/organizations).
### 10. BYOK — bring your own provider keys [#10-byok--bring-your-own-provider-keys]
Bring-Your-Own-Key (BYOK) lets you attach your own credentials for upstream providers (OpenAI, Anthropic, xAI, Z-AI, and others) to your AnyRouter account. Traffic still flows through `https://anyrouter.dev`, but billing for those calls hits your upstream account directly — useful for committed-spend deals, enterprise contracts, or compliance setups that require a direct relationship with the provider.
BYOK is not for everyone. If you don't already have a contract with the upstream, the default routing is cheaper and simpler. Read the page before you wire anything up.
**What you'll do**
* Decide which providers you have direct contracts with.
* Add upstream keys on the BYOK page (encrypted at rest with per-row salt).
* Route a single model through BYOK as a test and watch the invoice on the provider side.
* Document which models go through BYOK vs. shared infrastructure.
Read more: [BYOK](/features/byok).
### 11. Models catalog — see what's available [#11-models-catalog--see-whats-available]
The Models catalog is the canonical list of every model AnyRouter exposes, with pricing per million tokens, context window, supported features (tools, vision, JSON mode), and a status badge for upstream health. Open it whenever someone asks *"can we use model X?"* — it is faster than searching the docs.
You can also reach each model's individual page (for example `https://anyrouter.dev/model/openai/gpt-5.4`) and the raw markdown at `/model/openai/gpt-5.4.md` if you want a machine-readable spec for an agent.
**What you'll do**
* Filter by capability (vision, tools, JSON mode) to find candidates.
* Compare prices per million input vs. output tokens.
* Check the status badge before promoting a model into production.
* Bookmark the catalog for ad-hoc model lookups.
Read more: [Models catalog](/features/models-catalog).
### 12. Per-key rate limits — govern the busy keys [#12-per-key-rate-limits--govern-the-busy-keys]
Once Usage Explorer has shown you which keys are loudest, the last scaling step is to put per-key rate limits on them. The Keys page is also where you set per-key rate limits (requests-per-minute, tokens-per-minute, or daily spend ceilings). This is how you stop a runaway batch job from burning the whole org's credits in an afternoon.
A good default for production keys: cap daily spend at 2x your expected daily usage, set a generous RPM, and leave TPM unlimited unless you've seen a problem. Tighten only when you have data.
**What you'll do**
* Open the Keys page and click the key you want to limit.
* Set a daily spend cap based on observed usage from Usage Explorer.
* Set a requests-per-minute ceiling that protects against runaway loops.
* Add an expiry date if the key is for a short-lived project.
Read more: [API keys](/features/api-keys) (rate limits section).
***
## What next [#what-next]
You've toured every dashboard page. The natural next step is the API reference — once you know *what* the dashboard shows, you'll want to know *what* the API surface exposes so your code can do the same things.
* [API overview](/api-reference/overview) — every endpoint, with example requests and responses.
* [Chat completions](/api-reference/chat-completions) — the workhorse endpoint.
* [Responses API](/api-reference/responses) — stateful, tool-friendly successor.
* [Errors](/guides/errors) — error envelope shape, `requestId`, and `cfRay` for support.
If you're integrating from an existing provider, the migration guides cut the work down by hours:
* [Migrate from OpenAI](/guides/migrate-from-openai)
* [Migrate from Anthropic](/guides/migrate-from-anthropic)
* [Migrate to the Responses API](/guides/migrate-to-responses)
Welcome aboard.
---
# Errors (/guides/errors)
AnyRouter uses standard HTTP status codes and wraps every error in a consistent envelope. Read the status code to decide whether to retry, and the `code` field to branch on the specific failure.
```json
{
"error": {
"message": "Human-readable message",
"code": "machine_code",
"metadata": {
"type": "error_category"
}
}
}
```
## Status codes [#status-codes]
| Status | Type | Retry? | Description |
| ------ | --------------------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400` | `invalid_request_error` | No | Malformed request — fix and re-send. |
| `401` | `authentication_error` | No | Missing or invalid API key. |
| `403` | `permission_error` | No | Key lacks the required scope, or (`code: ip_not_allowed`) the client IP is not on the workspace [IP allowlist](/guides/security-settings#ip-allowlist). |
| `404` | `not_found_error` | No | Model or resource does not exist. |
| `409` | `conflict_error` | No | Conflicting state (e.g. duplicate key name). |
| `413` | `request_too_large` | No | Input exceeds the model's context window. |
| `422` | `invalid_request_error` | No | Request shape is valid but semantically wrong. |
| `429` | `rate_limit_error` | Yes, with backoff | Rate limit hit — honor `Retry-After`. |
| `499` | `client_closed_request` | No | Client disconnected before response. |
| `500` | `internal_server_error` | Yes | AnyRouter internal issue. |
| `502` | `upstream_error` | Yes | Upstream provider returned a 5xx. |
| `502` | `upstream_error` (`code: empty_stream`) | Yes | The provider answered with an empty stream. See [Empty stream](#empty-stream). |
| `503` | `service_unavailable` | Yes | No healthy upstream available. |
| `504` | `upstream_timeout` | Yes | Upstream provider timed out. |
Never retry `4xx` errors other than `429`. A `401` will keep returning `401` until you fix the auth header — retrying just burns rate-limit budget.
## Empty stream [#empty-stream]
A `502` with `type: upstream_error` and `code: empty_stream` means the provider ended the response without returning any content. It can occur on `/chat/completions`, `/responses` and `/messages`.
Where other providers are available for the model, AnyRouter has already tried them before returning this error, so the failure is not caused by your request. It is retryable: re-send the request with backoff. If it keeps happening for one model, try a different model or provider.
## Retry retryable errors [#retry-retryable-errors]
For retryable errors (`429`, `5xx`), use exponential backoff with jitter, and honor the `Retry-After` header when present:
```typescript
async function withRetry(fn: () => Promise, attempts = 5): Promise {
for (let i = 0; i < attempts; i++) {
try {
return await fn()
} catch (err) {
if (i === attempts - 1) throw err
if (!isRetryable(err)) throw err
const base = Math.min(1000 * 2 ** i, 30_000)
const jitter = Math.random() * 250
await new Promise((r) => setTimeout(r, base + jitter))
}
}
throw new Error("unreachable")
}
```
## Common error codes [#common-error-codes]
The `code` field names the exact failure. Expand each for what it means and how to fix it.
:::details{summary="authentication_required"}
The route requires a signed-in session or bearer key. Add an `Authorization: Bearer` header with a valid key.
:::
:::details{summary="missing_api_key"}
The `Authorization` header was required but missing. Send `Authorization: Bearer sk-ar-your-key`.
:::
:::details{summary="invalid_api_key"}
The supplied bearer key was invalid, revoked, or inactive. Create a fresh key in the [dashboard](https://anyrouter.dev/dashboard/keys).
:::
:::details{summary="insufficient_balance"}
The account has run out of credits for a paid-model request, and any [free requests](/features/credits#free-requests-for-new-accounts) for new accounts are used up. Add [credits](https://anyrouter.dev/dashboard/credits), [add your own provider key](https://anyrouter.dev/dashboard/byok), [subscribe](https://anyrouter.dev/pricing), or [donate a key to the shared Pool](https://anyrouter.dev/dashboard/byok/new?donate=1). The response lists these in `error.actions`. Free-tier models do not use this code.
:::
## When you see 403 [#when-you-see-403]
A `403` is a permission gate — retry the same request will not help; you must change something on the account, the key, or the model. The ones that come up most:
:::details{summary="model_not_allowed"}
Your key's allow-list excludes the requested model (per-key routing preferences), or the model is disabled for your account. Edit the key's allow-list in the [dashboard](https://anyrouter.dev/dashboard/keys) or pick another `provider/model` from the [catalog](https://anyrouter.dev/models).
:::
:::details{summary="permission_error"}
Your key lacks the scope for this endpoint (e.g. a management key on `/chat/completions`, or an inference key on `/keys`). Mint a key with the right group in the [dashboard](https://anyrouter.dev/dashboard/keys).
:::
:::details{summary="zdr_required"}
Zero data retention (ZDR) is on, for your account, key, preset or request, and it excludes every upstream that serves this model, so there is no route left to send the request to. Turn ZDR off in [Privacy settings](https://anyrouter.dev/dashboard/privacy) (or remove it from the key, preset or request), or pick a model that has a ZDR route. If the message says your privacy settings could not be read, nothing changed on your side: retry in a moment.
:::
:::details{summary="model_not_found"}
The requested model slug doesn't exist in the catalog. Use a full `provider/model` id from the [catalog](https://anyrouter.dev/models).
:::
:::details{summary="model_not_yet_available"}
The model is announced and listed as **Coming soon** in the [catalog](https://anyrouter.dev/models), but it cannot serve requests yet (404). Use a live model until its page shows pricing and providers.
:::
:::details{summary="context_length_exceeded"}
Input is longer than the model supports. Shorten the prompt or choose a model with a larger context window.
:::
:::details{summary="rate_limit_exceeded"}
Per-key or per-IP rate limit exceeded (`rate_limit_exceeded`, `ip_rate_limit_exceeded`). Back off and honor the `Retry-After` header.
:::
:::details{summary="upstream_unavailable"}
All upstream providers for this model are down. Retry, or route to a different model.
:::
:::details{summary="content_policy"}
An upstream refused the request due to a content-policy filter. Adjust the prompt.
:::
## Debugging [#debugging]
Every inference response includes an `X-Request-ID` header (also sent under the canonical name `X-AnyRouter-Trace-Id` — the two carry the same value). Include it when reporting bugs — it lets us trace your exact request lifecycle across auth, rate-limit, routing, and upstream dispatch.
```bash
curl -i https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{"model": "openai/gpt-5.4-mini", "messages": [{"role": "user", "content": "hi"}]}'
# HTTP/2 200
# x-request-id: req_01HQ...
```
Send a request-body `trace` object on Chat Completions or Responses and AnyRouter carries your `trace_id` through the internal step logs and AI Gateway correlation metadata — useful for lining up your app traces with AnyRouter routing behavior.
---
# Free capacity with your own keys (/guides/free-capacity-with-your-own-keys)
Several providers give out free API usage. Add their keys as BYOK keys and AnyRouter uses them before the shared pool or your credits. Requests served through your own key cost nothing on AnyRouter and don't use the shared free pool. See [Free Tier](/features/free-tier).
Each provider sets its own free limits and can change them at any time. Check the provider's site for current numbers.
## Providers with a free tier [#providers-with-a-free-tier]
| Provider | Key starts with | Create a key | Setup page |
| ---------------- | --------------- | ------------------------------------------------------------------ | ------------------------------------------------------------- |
| OpenRouter | `sk-or-` | [openrouter.ai/keys](https://openrouter.ai/keys) | [OpenRouter](/features/byok-providers/openrouter) |
| NVIDIA NIM | `nvapi-` | [build.nvidia.com](https://build.nvidia.com) | [NVIDIA NIM](/features/byok-providers/nvidia) |
| Google AI Studio | `AIza` | [aistudio.google.com/apikey](https://aistudio.google.com/apikey) | [Google AI Studio](/features/byok-providers/google-ai-studio) |
| Groq | `gsk_` | [console.groq.com/keys](https://console.groq.com/keys) | [Groq](/features/byok-providers/groq) |
| Cerebras | `csk-` | [cloud.cerebras.ai](https://cloud.cerebras.ai) | [Cerebras](/features/byok-providers/cerebras) |
| Mistral | — | [console.mistral.ai/api-keys](https://console.mistral.ai/api-keys) | [Mistral](/features/byok-providers/mistral) |
| Ollama Cloud | — | [ollama.com/settings/keys](https://ollama.com/settings/keys) | [Ollama Cloud](/features/byok-providers/ollama) |
| OpenCode Zen | — | [opencode.ai/auth](https://opencode.ai/auth) | [OpenCode Zen](/features/byok-providers/opencode-zen) |
| ZenMux | — | [zenmux.ai/settings/keys](https://zenmux.ai/settings/keys) | [ZenMux](/features/byok-providers/zenmux) |
For every provider, the last step is the same: open **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**, find the provider under **Available**, click **Add key**, paste the key, and click **Save**. AnyRouter checks the key with the provider before it saves it.
## Per-provider steps [#per-provider-steps]
**OpenRouter.** Create a key at [openrouter.ai/keys](https://openrouter.ai/keys). `:free` models stay free on your OpenRouter account, and paid models bill it. Full walkthrough: [Unblock free-model rate limits with your own OpenRouter key](/guides/unblock-free-model-rate-limits-openrouter-key).
**NVIDIA NIM.** Sign in at [build.nvidia.com](https://build.nvidia.com), open any model, and click **Get API Key**. Add it as **NVIDIA NIM**.
**Google AI Studio.** Sign in with a Google account, open [aistudio.google.com/apikey](https://aistudio.google.com/apikey), and click **Create API key**. Add it as **Google AI Studio**.
**Groq.** Open [console.groq.com/keys](https://console.groq.com/keys) and click **Create API Key**. Copy it right away; it's shown once.
**Cerebras.** Sign in at [cloud.cerebras.ai](https://cloud.cerebras.ai) and click **Create API Key** under **API Keys**.
**Mistral.** In the [Mistral Console](https://console.mistral.ai/api-keys), open **API keys** and click **Create new key**.
**Ollama Cloud.** Create a key at [ollama.com/settings/keys](https://ollama.com/settings/keys).
**OpenCode Zen.** Sign in at [opencode.ai/auth](https://opencode.ai/auth), open **API Keys**, and create a key.
**ZenMux.** Create a key at [zenmux.ai/settings/keys](https://zenmux.ai/settings/keys).
## Use them together [#use-them-together]
* **Call any model your keys reach** with `anyrouter/byok`. It routes across every key you've added and never spends credits. See [Auto-routing models](/features/byok#auto-routing-models).
* **Combine free tiers in one preset** so each request goes to a key that still has quota, with a cooldown when a provider rate limits you. See [Free-tier Aggregation](/features/free-tier-aggregation).
* **Share spare quota.** Turn on **Donate to pool** on a key to let others use it and earn credits back. Check the provider's terms first. See [Share a Key to the Shared Pool](/features/shared-pool).
For every provider AnyRouter supports, see [Get a provider API key](/guides/byok-provider-keys).
---
# Grok Bot, Herdr, and Anyr CLI (/guides/grok-bot-herdr-anyr)
Use this loop when you want a live coding agent on a repo checkout. Grok Bot assigns the goal. Herdr shows the box so you can watch or attach. Anyr CLI runs Claude Code in that space.
## What this is [#what-this-is]
Three pieces, one job.
* **Grok Bot** (fleet manager, often `duyetbot`) assigns a goal and collects the PR URL plus smoke result.
* **Herdr** is the box. Open a space on the repo checkout, then watch or attach the live terminal over Tailscale.
* **Anyr CLI** launches Claude Code through AnyRouter in that space.
```bash
anyr claude --model "poolside/laguna-s-2.1" --yolo
```
`--yolo` is unattended (auto-approve). Pin only this documented model id unless the goal names another catalog id. `minimax/m3[1m]` is a valid alternate for the same loop.


## When to use it [#when-to-use-it]
Use this loop when any of these are true.
* Cursor cloud usage or on-demand is blocked.
* You want a live terminal you can attach over Tailscale.
* You are running more than one repo space at once.
Keep Grok Build out of this loop. Launch with `anyr claude`, not `anyr grok`. If a cloud agent comes back on grok-4.6 Fast, turn Fast off. Never Fast.

## Before you start [#before-you-start]
* Install Anyr CLI and sign in. See [AnyRouter CLI](/cli) and [anyr claude](/cli/claude-code).
* Have a Herdr space (or create one) on the repo checkout, for example `/workspace/`.
* Pull the tip of the branch you will work on before you launch the agent.
## How to run it [#how-to-run-it]
::::steps
:::step{title="Pull the tip"}
In the repo checkout, update the branch you will work on. Do this before you open a new space so the agent does not start from a stale tree.
:::
:::step{title="Open a Herdr space"}
Open or create a Herdr space pointed at that checkout. Attach when you want to watch the terminal.
:::
:::step{title="Launch Anyr CLI"}
From the space:
```bash
anyr claude --model "poolside/laguna-s-2.1" --yolo
```
Install Claude Code first if it is missing: `anyr claude --install --ok`.
:::
:::step{title="Assign the goal from Grok Bot"}
Paste the prompt in [Example prompt](#example-prompt). Fill in the goal and the smoke command. Rules stay as written. Squash-merge the PR. Never merge a release-please PR.
:::
:::step{title="Smoke, then deploy from local"}
Run the smoke named in the prompt (`anyr update`, `curl` health, or Playwright). After squash-merge, deploy from the local checkout (`pnpm ops deploy` or that repo's deploy command). Report the PR URL and the smoke result back to duyetbot.
:::
::::
## Example prompt [#example-prompt]
Copy this into Grok Bot. Replace the goal and the smoke line.
```
Use Herdr + Anyr CLI for coding (Cursor cloud may be exhausted).
1) Open/create a Herdr space on the repo checkout (e.g. /workspace/).
2) Run: anyr claude --model "poolside/laguna-s-2.1" --yolo
3) Goal:
4) Rules: grok-4.6 Fast off if cloud returns; never Fast; never Grok Build; squash-merge (never release-please); after merge deploy from local (pnpm ops deploy / repo deploy); /poteto-mode + in-repo verify-* skill when present.
5) Smoke:
6) Report PR URL + smoke result back to duyetbot.
```
This page documents `poolside/laguna-s-2.1` as the example model id for the loop; `minimax/m3` is an alternate. Both route through non-cloudflare backends. Avoid `stealth/ox-alpha` for this loop: its upstreams are flaky and intermittently 401, which surfaces as transient auth errors rather than clean model availability. Browse other Messages-compatible ids at [anyrouter.dev/models?endpoint=messages](https://anyrouter.dev/models?endpoint=messages).
## Related [#related]
* [anyr claude](/cli/claude-code) — launcher flags, login, and `--dry-run`
* [AnyRouter CLI](/cli) — install, targets, and shared config
* [Claude Code](/guides/claude-code) — why route Claude Code through AnyRouter
* [Run Claude Code against AnyRouter](/scenario-examples/run-claude-code-on-anyrouter)
---
# Hermes Agent (/guides/hermes-agent)
[Hermes Agent](https://hermes-agent.nousresearch.com) ([source on GitHub](https://github.com/NousResearch/hermes-agent)) by Nous Research is an autonomous coding agent that runs in your terminal and on the messaging platforms you already use (Telegram, Discord, Slack, Signal, SMS, WhatsApp, …), with persistent memory and scheduled tasks. AnyRouter publishes a **first-class model-provider plugin** for it, so pointing Hermes at AnyRouter is a plugin install plus one API key — and every call then flows through one gateway with unified billing.
## Why route through AnyRouter [#why-route-through-anyrouter]
* **One key.** The same `sk-ar-` key reaches every model Hermes can run.
* **A model list the agent can actually drive.** The plugin filters the picker down to routes AnyRouter advertises as tool-capable, so you never select a model Hermes can't call tools on.
* **Audit logs.** Every Hermes call lands in your dashboard with cost, latency, and tokens.
* **Free model swaps.** Change `model.default` to any AnyRouter id without rotating keys.
## Install the provider plugin [#install-the-provider-plugin]
The plugin is in the [Hermes plugin catalog](https://hermes-agent.nousresearch.com/docs/plugins/anyrouter) under the name `anyrouter`:
```bash
hermes plugins install anyrouter
hermes plugins enable anyrouter
```
Installing a catalog entry only puts the plugin on disk — [like any plugin](https://hermes-agent.nousresearch.com/docs/user-guide/features/plugins) it must be enabled before it loads, so run the second line. `hermes plugins install anyrouter --enable` does both in one step.
A catalog install checks out one exact commit that a Hermes maintainer reviewed, not a moving branch tip, so a later commit pushed to the plugin repo cannot change what you installed. Confirm it's live:
```bash
hermes plugins list # anyrouter → enabled
hermes doctor # provider health check
```
**Other install paths.** The catalog is the reviewed one and the one to prefer. If you'd rather install from git, the plugin lives in a subdirectory of its repo — Hermes takes that in the *identifier*, not in a flag:
```bash
hermes plugins install duyet/hermes-provider-anyrouter/anyrouter
# equivalently, an explicit fragment, or a GitHub browser URL:
hermes plugins install https://github.com/duyet/hermes-provider-anyrouter#anyrouter
hermes plugins install https://github.com/duyet/hermes-provider-anyrouter/tree/master/anyrouter
```
You can also install the [plugin repository](https://github.com/duyet/hermes-provider-anyrouter) as a pip package — Hermes discovers it through the `hermes_agent.plugins` entry point:
```bash
pip install git+https://github.com/duyet/hermes-provider-anyrouter.git
```
## Add your key [#add-your-key]
The plugin reads its credential from `~/.hermes/.env`:
```bash
ANYROUTER_API_KEY=sk-ar-your-anyrouter-key
```
Get a key at [anyrouter.dev](https://anyrouter.dev). The same key also works against the OpenAI-compatible endpoint `https://anyrouter.dev/api/v1`.
Keep secrets in `~/.hermes/.env` and everything else in `~/.hermes/config.yaml`. You can also read and write config from the CLI without opening a file — `hermes config get KEY` prints a resolved value, `hermes config set KEY VAL` sets one, and `hermes config check` reports missing options.
## Pick a model [#pick-a-model]
```bash
hermes model # interactive picker → anyrouter
hermes --provider anyrouter -m anyrouter/hermes # or pin both for one run
```
`hermes model` opens the provider-and-model picker; `hermes --provider / -m` are the per-run overrides. `--provider anyrouter` works because the plugin registers `anyrouter` in Hermes's provider registry — the flag accepts any registered provider name, not only the bundled ones.
To make it the default, write it to `~/.hermes/config.yaml`:
```yaml
model:
provider: anyrouter
default: anyrouter/hermes
```
**Take `anyrouter/hermes` as the default for agentic work.** It is an AnyRouter routing meta-model, *not* a Nous Research model: it ranks the strongest tool-calling models and routes to the best available one, falling back automatically when a route is unavailable. It is billed to your AnyRouter credits at each underlying model's rate, so no BYOK keys are required.
To switch mid-session, use the `/model` slash command. Hermes applies the switch immediately but resets the prompt cache on the next message, so prefer switching early or in a fresh session.
Other ids in the catalog, all reachable from the same key:
| Model | Notes |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `anyrouter/hermes` | Auto-routes the top tool-calling models, one best per upstream provider, with fallback — the agentic default |
| `anyrouter/auto` | Top-usage and top stable platform models, mixing free-tier and credit-billed routes. Tools are optional here, so for agent loops stay on `anyrouter/hermes` |
| `anyrouter/latest` | The latest stable production chat model actually serving traffic |
| `x-ai/grok-4.7` | Direct vendor route |
| `moonshotai/kimi-k3` | 1M context, agentic |
| `deepseek/deepseek-v4-flash` | Fast, reasoning-capable |
`hermes model` reads its list fresh from the AnyRouter catalog, so newly served tool-capable routes show up without reinstalling anything. To see the full catalog yourself:
```bash
curl https://anyrouter.dev/api/v1/models -H "Authorization: Bearer sk-ar-your-key"
```
## What the plugin adds [#what-the-plugin-adds]
* **Tool-capable picker filtering.** AnyRouter's `/api/v1/models` annotates every route with a `capabilities` list. The plugin keeps only the routes advertising tool calling, so the picker never offers a model the agent can't drive. If the catalog is unreachable, or you point `ANYROUTER_BASE_URL` at a relay whose catalog isn't AnyRouter's to interpret, it falls back to the generic `/v1/models` list.
* **Sticky session grouping.** Each request carries `extra_body.session_id`, which groups a conversation together in your [request logs](https://anyrouter.dev/dashboard/logs).
* **Routing preferences.** `extra_body.provider` carries request-level preferences. See [Route by provider](#route-by-provider).
* **Reasoning passthrough.** `extra_body.reasoning` forwards Hermes's reasoning control; AnyRouter translates it to each upstream's native thinking control.
* **Meta-models surfaced.** `anyrouter/hermes` and friends appear alongside plain `vendor/model` routes.
* **No attribution unless you ask.** App-attribution headers are opt-in only.
## Route by provider [#route-by-provider]
AnyRouter can route each request across its upstream providers — sort by price/throughput/latency, pin or exclude providers, enforce data policies. With the plugin installed, Hermes's own `provider_routing` block is the lever, because the plugin forwards those preferences as `extra_body.provider` on every request:
```yaml
provider_routing:
sort: throughput # price (default) | throughput | latency
# only: [anthropic] # only use these providers
# ignore: [deepinfra] # skip these providers
# order: [anthropic, google] # try providers in this order
# require_parameters: true # only providers supporting all request params
# data_collection: deny # exclude providers that may store/train on data
```
See [Provider Routing](/guides/provider-routing) for the full field list — including `zdr`, `max_price`, throughput/latency percentile thresholds, and the `:nitro` / `:floor` model-id shortcuts.
## Optional settings [#optional-settings]
Add these to `~/.hermes/.env` alongside the key.
### `ANYROUTER_BASE_URL` [#anyrouter_base_url]
Overrides the default `https://anyrouter.dev/api/v1` endpoint — useful when you sit behind a proxy or a self-hosted relay. The picker stops filtering by AnyRouter's `capabilities` field in that case, since the catalog behind a custom URL isn't ours to interpret.
### `ANYROUTER_APP_ATTRIBUTION=1` [#anyrouter_app_attribution1]
Opt in to app-attribution headers, which credit Hermes traffic in AnyRouter's public app rankings. Off by default; nothing is tagged unless you set it.
## Use AnyRouter from inside Hermes [#use-anyrouter-from-inside-hermes]
Beyond inference, AnyRouter ships an [MCP server](https://anyrouter.dev/docs/mcp) at `https://anyrouter.dev/api/v1/mcp` — browse models, check credits, and manage keys from inside the agent. Hermes reads MCP servers from the `mcp_servers` key in `~/.hermes/config.yaml`; a remote HTTP server takes a `url`, plus `headers` or `auth: oauth`. Pick whichever auth path fits:
```yaml
mcp_servers:
anyrouter:
url: https://anyrouter.dev/api/v1/mcp
auth: oauth
```
Hermes opens a browser on the first connection and handles discovery, token exchange, and refresh. Pick a permission level at the consent page and manage or revoke tokens any time from the [MCP dashboard](https://anyrouter.dev/dashboard/mcp).
For headless agents, CI jobs, and embedded automations, use a bearer token instead. AnyRouter accepts two key types, and the narrower one is the better default:
* **LLM API key** (`sk-ar-v1-*`) from [/dashboard/keys](https://anyrouter.dev/dashboard/keys) — grants `list_models` only.
* **Management API key** (`ak_*`) from [/dashboard/settings#keys](https://anyrouter.dev/dashboard/settings#keys) — grants whichever scopes you assign.
Whichever you pick goes in `~/.hermes/.env`, and the header interpolates it:
```yaml
mcp_servers:
anyrouter:
url: https://anyrouter.dev/api/v1/mcp
headers:
Authorization: "Bearer ${ANYROUTER_MCP_KEY}"
```
Smoke test before wiring it into an agent:
```bash
curl https://anyrouter.dev/api/v1/mcp \
-H "Authorization: Bearer ak_your_management_key" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}'
```
After editing MCP config, run `/reload-mcp` in a session to pick up the change. AnyRouter's MCP endpoint is remote HTTP only — it exposes no stdio server. Full reference: [AnyRouter MCP with other clients](/mcp/other-clients).
## Verify [#verify]
Send a message in `hermes chat`, then open your [request logs](https://anyrouter.dev/dashboard/logs). The call should appear within a second or two with the model name, token counts, and cost — confirmation Hermes is routing through AnyRouter.
## Troubleshooting [#troubleshooting]
**`unknown provider: anyrouter`** — the plugin isn't active. Run `hermes plugins list` and check it says enabled; a provider registered by a plugin is picked up by new sessions, so start a fresh one after enabling.
**`Model 'x' not found`** — the picker only lists routes AnyRouter currently serves with tool-calling support. Run `hermes model` to see the live list.
**429 (rate limits)** — Hermes runs tight loops with subagents and skills, which can burst beyond a single key's per-minute budget. If you see `429 Too Many Requests`, back off and retry — the same key works again next window. Do **not** treat `429` as a permanent failure. See [Rate Limits](/features/rate-limits).
**402 on the first call** — the key has no credit left, or an upstream AnyRouter routes to is out of capacity. Check your balance in the dashboard, or pick another model.
**Nothing is tagged as an app** — expected. Attribution is opt-in; set `ANYROUTER_APP_ATTRIBUTION=1` if you want Hermes traffic credited in the public app rankings.
## Related [#related]
* [Hermes Agent on GitHub](https://github.com/NousResearch/hermes-agent) and [the AnyRouter plugin](https://github.com/duyet/hermes-provider-anyrouter)
* [Hermes Agent docs](https://hermes-agent.nousresearch.com/docs) — [plugin catalog page for AnyRouter](https://hermes-agent.nousresearch.com/docs/plugins/anyrouter), [plugin system](https://hermes-agent.nousresearch.com/docs/user-guide/features/plugins), [CLI reference](https://hermes-agent.nousresearch.com/docs/reference/cli-commands), [MCP config reference](https://hermes-agent.nousresearch.com/docs/reference/mcp-config-reference)
* [AnyRouter MCP with other clients](/mcp/other-clients)
* [Claude Code](/guides/claude-code)
* [OpenCode](/guides/opencode)
* [Provider Routing](/guides/provider-routing)
* [Chat Completions API](/api-reference/chat-completions)
* [Smart Routing](/features/routing)
---
# Kilo Code with AnyRouter — free models, one API key (/guides/kilo-code)
[Kilo Code](https://kilo.ai) supports custom OpenAI-compatible providers, so it can use AnyRouter directly. You get free models through `anyrouter/free`, every catalog model behind one `sk-ar-` key, and a log row for each request.
## Set it up [#set-it-up]
::::steps
:::step{title="Create an AnyRouter key"}
Open [Dashboard → Keys](https://anyrouter.dev/dashboard/keys) and create a key. Every AnyRouter inference key starts with `sk-ar-`.
:::
:::step{title="Add AnyRouter as a custom provider"}
In Kilo Code, open **Settings** (gear icon) → **Providers**, scroll to the bottom, and click **Custom provider**. Fill in:
| Field | Value |
| ---------------- | ------------------------------------------------------------- |
| **Provider ID** | `anyrouter` |
| **Display name** | `AnyRouter` |
| **Provider API** | `OpenAI Compatible` |
| **Base URL** | `https://anyrouter.dev/api/v1` |
| **API key** | your `sk-ar-` key |
| **Models** | `anyrouter/free` (add `anyrouter/auto` or any catalog id too) |
Kilo Code can read the model list from AnyRouter's `/models` endpoint. If a model is missing, type its id in **Models** by hand.
:::
:::step{title="Pick the model and check the logs"}
Select an AnyRouter model in Kilo Code, run a task, then open your [request logs](https://anyrouter.dev/dashboard/logs). The request appears with the model that answered, tokens, and cost.
:::
::::
Kilo Code is an agent that calls tools. Pick a model that supports tool calling — browse [Chat Completions models](https://anyrouter.dev/models?endpoint=chat-completions).
## Which model id to use [#which-model-id-to-use]
| Model id | What it does |
| ---------------- | -------------------------------------------------------------------------------------------------------- |
| `anyrouter/free` | Routes to the best free model available right now, with automatic fallback. No credits used. |
| `anyrouter/auto` | Picks from top models (free and paid) with health-aware failover. Paid members use credits. |
| `owner/model` | A specific model, for example `z-ai/glm-5.2`. Copy ids from the [catalog](https://anyrouter.dev/models). |
## Free models [#free-models]
`anyrouter/free` costs nothing and needs no provider keys. It has no daily cap on any plan. Details: [Free Tier](/features/free-tier).
## Bring your own keys [#bring-your-own-keys]
Already pay for a provider? Add its key in AnyRouter and Kilo Code uses it through the same base URL. BYOK requests are billed by your provider. See [Use your own provider keys](/guides/byok-provider-keys) and [BYOK](/features/byok).
## Troubleshooting [#troubleshooting]
**401** — the API key field does not hold an active `sk-ar-` key. Check it in [Dashboard → Keys](https://anyrouter.dev/dashboard/keys).
**404 or model not found** — the model id must match an AnyRouter id exactly, including the `owner/` prefix.
**400 `no_eligible_models` on `anyrouter/auto` or `anyrouter/free`** — your privacy or routing settings (for example Zero Data Retention or blocked providers) excluded every candidate. Relax them in the dashboard, or pick a specific model.
## Related [#related]
* [AnyRouter CLI](/cli)
* [Cline](/guides/cline) · [Continue](/guides/continue) · [OpenCode](/guides/opencode) · [Claude Code](/guides/claude-code) · [Codex](/guides/codex)
* [Free Tier](/features/free-tier)
---
# LangChain (/guides/langchain)
Run your LangChain chains and agents against any model in AnyRouter's catalog. Because AnyRouter's endpoint is OpenAI-compatible, you keep LangChain's standard OpenAI integration and change only the base URL, key, and model id.
## Before you start [#before-you-start]
* A LangChain project in Python or TypeScript.
* An AnyRouter API key (prefix `sk-ar-`). Create one in the [dashboard](https://anyrouter.dev/dashboard/keys).
## Set it up [#set-it-up]
Pick your language, install the OpenAI integration, then point the client at `https://anyrouter.dev/api/v1` with your key and a model from the catalog.
```bash
pip install langchain-openai
```
```python
import os
from langchain_openai import ChatOpenAI
chat = ChatOpenAI(
base_url="https://anyrouter.dev/api/v1",
api_key=os.environ["ANYROUTER_API_KEY"], # sk-ar-...
model="anyrouter/free",
)
response = chat.invoke("What is a fallback route? Answer in one sentence.")
print(response.response_metadata.get("model_name")) # the model that served it
print(response.content)
```
```bash
npm install @langchain/openai @langchain/core
```
```typescript
import { ChatOpenAI } from "@langchain/openai"
const chat = new ChatOpenAI({
model: "anyrouter/free",
apiKey: process.env.ANYROUTER_API_KEY, // sk-ar-...
configuration: {
baseURL: "https://anyrouter.dev/api/v1",
},
})
const response = await chat.invoke("Explain DNS load balancing in one sentence.")
console.log(response.response_metadata.model_name) // the model that served it
console.log(response.content)
```
Swap the `model` value to route the same chain to any model in the [catalog](https://anyrouter.dev/models) — no other code changes.
## Verify [#verify]
Export your key, then run the snippet. It prints the model that served the request, then the reply.
```bash
export ANYROUTER_API_KEY="sk-ar-your-key-here"
```
Every request also shows up in [Logs](https://anyrouter.dev/dashboard/logs) with the model, provider, tokens, and latency.
## Free models [#free-models]
Set the model to `anyrouter/free` and AnyRouter routes the request to a free model that is healthy right now — no credits used. Free models have no daily cap on any plan. Details on [Free Tier](/features/free-tier).
Use `anyrouter/auto` to let AnyRouter pick from the full catalog, or any `provider/model` id from the [catalog](https://anyrouter.dev/models) to pin one model.
## Bring your own keys [#bring-your-own-keys]
Already pay for OpenAI, Anthropic, Google, or another provider? Add those keys to AnyRouter and your requests use them, with no credits deducted. See [Get a provider API key](/guides/byok-provider-keys) and [BYOK](/features/byok).
## Troubleshooting [#troubleshooting]
:::details{summary="401 Unauthorized"}
Check that the key starts with `sk-ar-` and is passed as `api_key` (Python) or `apiKey` (TypeScript). Keys are created in the [dashboard](https://anyrouter.dev/dashboard/keys).
:::
:::details{summary="404 model not found"}
Use a full catalog id — `anyrouter/free`, `anyrouter/auto`, or a `provider/model` id such as `z-ai/glm-4.7-flash` — not a bare model name. Browse valid ids in the [catalog](https://anyrouter.dev/models).
:::
## Related [#related]
* [OpenAI SDK](/guides/openai-sdk) — the official client, Python and TypeScript
* [Vercel AI SDK](/guides/vercel-ai-sdk) — the TypeScript-native alternative
* [LlamaIndex](/guides/llamaindex) — RAG and agent framework
* [Chat Completions API](/api-reference/chat-completions) — the underlying endpoint
* [Models catalog](https://anyrouter.dev/models) — every model id you can pass
---
# Latency Overhead (/guides/latency-overhead)
Understand exactly what AnyRouter adds to a request, and measure it yourself. Calling an upstream API through AnyRouter adds a small, predictable amount of latency — and for some users, especially those far from the upstream's origin, AnyRouter is actually **faster** than calling the provider directly.
## Request flow [#request-flow]
## What AnyRouter adds [#what-anyrouter-adds]
| Stage | Typical wall-clock | Notes |
| ----------------------- | ------------------------------ | ------------------------------------------------------------------------ |
| CF Worker dispatch | \~0 ms (warm) / 5–20 ms (cold) | Subsequent requests on the same isolate add nothing |
| Auth + workspace lookup | 1–10 ms | D1 read with KV cache; KV hit is sub-millisecond |
| Routing + cooldown | 1–5 ms | In-memory route table; KV cooldown check |
| Body build / translate | \<1 ms | Pure JS, no I/O |
| AI Gateway hop | 10–30 ms | CF-owned; co-located with the Worker |
| Streaming pipe-through | 0 ms / byte | SSE chunks forward immediately; only the final billing frame is appended |
| Billing + audit writes | 0 ms on the critical path | All run via `waitUntil` after the response is flushed |
**Net steady-state add: \~15–50 ms TTFB, \~10–35 ms TTFT.**
## When AnyRouter is faster than calling the provider directly [#when-anyrouter-is-faster-than-calling-the-provider-directly]
* **You're far from the upstream origin.** AnyRouter terminates TLS at the CF edge nearest you. The Worker reuses a warm connection pool to the upstream, so your packet round-trip is short and the long-haul leg is amortized across all AnyRouter traffic.
* **Your own backend cold-starts.** A serverless app talking to OpenAI pays its own cold-start every time it scales out. AnyRouter's Worker is almost always already warm.
* **Provider rate-limits or has a transient outage.** With `body.models`, AnyRouter walks to the next candidate faster than your app can implement a retry loop. See [Model Fallbacks](/guides/model-fallbacks).
* **Multi-provider redundancy.** A direct integration with one provider can't fail over without code changes. AnyRouter's [provider routing](/guides/provider-routing) does it on every request.
## When AnyRouter is slower [#when-anyrouter-is-slower]
* **You're already in the same region as the provider.** A US-East serverless function hitting OpenAI direct will be faster than going through CF (one fewer hop).
* **First request to a cold Worker isolate.** Rare; subsequent requests on that isolate add nothing.
* **Strict TTFB budgets on small non-streaming responses** where \~20 ms matters more than reliability.
## Measure it yourself [#measure-it-yourself]
Time-to-first-byte tells you the real overhead for your location. Run the same prompt against AnyRouter and the provider directly, then compare.
```bash
# Time-to-first-byte against AnyRouter
curl -w 'ttfb=%{time_starttransfer}s total=%{time_total}s\n' \
-o /dev/null -s \
-X POST https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{"model":"openai/gpt-5.4-mini","messages":[{"role":"user","content":"hi"}],"max_completion_tokens":10}'
```
```bash
# Same prompt against the provider directly (use your own key)
curl -w 'ttfb=%{time_starttransfer}s total=%{time_total}s\n' \
-o /dev/null -s \
-X POST https://api.openai.com/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}],"max_tokens":10}'
```
Run each at least a few times to warm caches before comparing — the first request to a cold Worker isolate or a cold connection pool is not representative.
---
# LiteLLM (/guides/litellm)
[LiteLLM](https://docs.litellm.ai/) calls 100+ LLMs in the OpenAI format. Point it at AnyRouter as a custom OpenAI-compatible provider to reach the whole catalog from either the Python client or the LiteLLM Proxy.
## Before you start [#before-you-start]
* LiteLLM installed (`pip install litellm`), or a running LiteLLM Proxy.
* An AnyRouter API key (prefix `sk-ar-`). Create one in the [dashboard](https://anyrouter.dev/dashboard/keys).
## Set it up [#set-it-up]
Prefix the model with `openai/` so LiteLLM uses its OpenAI-compatible path, set `api_base` to `https://anyrouter.dev/api/v1`, and pass your key. Choose the client or the proxy below.
```bash
pip install litellm
```
```python
import litellm
response = litellm.completion(
model="openai/google/gemini-3.5-flash", # prefix with openai/
api_base="https://anyrouter.dev/api/v1",
api_key="your-anyrouter-api-key",
messages=[{"role": "user", "content": "Explain load balancing."}],
)
print(response.choices[0].message.content)
```
```yaml
model_list:
- model_name: anyrouter-gpt-4o
litellm_params:
model: openai/openai/gpt-4o
api_base: https://anyrouter.dev/api/v1
api_key: os.environ/ANYROUTER_API_KEY
- model_name: anyrouter-gemini
litellm_params:
model: openai/google/gemini-3.5-flash
api_base: https://anyrouter.dev/api/v1
api_key: os.environ/ANYROUTER_API_KEY
```
The `openai/` prefix selects LiteLLM's OpenAI-compatible transport; the second segment (for example `google/gemini-3.5-flash`) is the AnyRouter model id. That is why proxy entries read `openai/openai/gpt-4o`.
## Verify [#verify]
Run the Python snippet, or start your proxy and call one of the `model_name` entries. A model reply confirms LiteLLM is routing through AnyRouter. Export your key first if you reference it from the environment:
```bash
export ANYROUTER_API_KEY="sk-ar-your-key-here"
```
## Troubleshooting [#troubleshooting]
:::details{summary="401 Unauthorized"}
Confirm `api_key` (or `ANYROUTER_API_KEY`) holds a key that starts with `sk-ar-`. Manage keys in the [dashboard](https://anyrouter.dev/dashboard/keys).
:::
:::details{summary="404 model not found"}
The segment after the `openai/` prefix must be a full AnyRouter `provider/model` id. Browse valid ids in the [catalog](https://anyrouter.dev/models).
:::
## Related [#related]
* [LangChain](/guides/langchain) — chains and agents
* [curl](/guides/curl) — call the same endpoint from the terminal
* [Chat Completions API](/api-reference/chat-completions) — the underlying endpoint
* [Models catalog](https://anyrouter.dev/models) — every model id you can pass
---
# LlamaIndex (/guides/llamaindex)
Drive your LlamaIndex indexes and agents with any model in AnyRouter's catalog. In Python, use LlamaIndex's `OpenAILike` class — its integration for OpenAI-compatible servers — because the plain `OpenAI` class only accepts OpenAI's own model names. In TypeScript, the `@llamaindex/openai` LLM takes a custom `baseURL`.
## Before you start [#before-you-start]
* A LlamaIndex project in Python or TypeScript.
* An AnyRouter API key (prefix `sk-ar-`). Create one in the [dashboard](https://anyrouter.dev/dashboard/keys).
## Set it up [#set-it-up]
Pick your language, install the integration, then configure the base URL, your key, and a model from the catalog. Use the LLM in your index querying or agent loops.
```bash
pip install llama-index-llms-openai-like
```
```python
import os
from llama_index.llms.openai_like import OpenAILike
llm = OpenAILike(
model="anyrouter/free",
api_base="https://anyrouter.dev/api/v1",
api_key=os.environ["ANYROUTER_API_KEY"], # sk-ar-...
is_chat_model=True,
context_window=128000,
)
response = llm.complete("Explain semantic search in one sentence.")
print(response.raw.model) # the model that served the request
print(response)
```
`is_chat_model=True` sends requests to the Chat Completions endpoint. Set `context_window` to the context length of the model you use (listed on its catalog page) so LlamaIndex sizes prompts correctly.
```bash
npm install llamaindex @llamaindex/openai
```
```typescript
import { OpenAI } from "@llamaindex/openai"
const llm = new OpenAI({
model: "anyrouter/free",
apiKey: process.env.ANYROUTER_API_KEY, // sk-ar-...
baseURL: "https://anyrouter.dev/api/v1",
})
const response = await llm.complete({
prompt: "Explain vectors in machine learning in one sentence.",
})
console.log(response.text)
```
## Verify [#verify]
Export your key, then run the snippet. A printed completion confirms LlamaIndex is routing through AnyRouter.
```bash
export ANYROUTER_API_KEY="sk-ar-your-key-here"
```
Every request also shows up in [Logs](https://anyrouter.dev/dashboard/logs) with the model, provider, tokens, and latency.
## Free models [#free-models]
Set the model to `anyrouter/free` and AnyRouter routes the request to a free model that is healthy right now — no credits used. Free models have no daily cap on any plan. Details on [Free Tier](/features/free-tier).
Use `anyrouter/auto` to let AnyRouter pick from the full catalog, or any `provider/model` id from the [catalog](https://anyrouter.dev/models) to pin one model.
## Bring your own keys [#bring-your-own-keys]
Already pay for OpenAI, Anthropic, Google, or another provider? Add those keys to AnyRouter and your requests use them, with no credits deducted. See [Get a provider API key](/guides/byok-provider-keys) and [BYOK](/features/byok).
## Troubleshooting [#troubleshooting]
:::details{summary="401 Unauthorized"}
Confirm the key starts with `sk-ar-` and is passed as `api_key` (Python) or `apiKey` (TypeScript). Manage keys in the [dashboard](https://anyrouter.dev/dashboard/keys).
:::
:::details{summary="ValueError: Unknown model"}
The Python `OpenAI` class from `llama_index.llms.openai` rejects model ids that are not OpenAI's own. Use `OpenAILike` from `llama-index-llms-openai-like` as shown above.
:::
:::details{summary="404 model not found"}
Pass a full catalog id — `anyrouter/free`, `anyrouter/auto`, or a `provider/model` id such as `z-ai/glm-4.7-flash`. Valid ids live in the [catalog](https://anyrouter.dev/models).
:::
## Related [#related]
* [OpenAI SDK](/guides/openai-sdk) — the official client, Python and TypeScript
* [LangChain](/guides/langchain) — chains and agents
* [Vercel AI SDK](/guides/vercel-ai-sdk) — TypeScript-native framework
* [Chat Completions API](/api-reference/chat-completions) — the underlying endpoint
* [Models catalog](https://anyrouter.dev/models) — every model id you can pass
---
# Anthropic (/guides/migrate-from-anthropic)
Repoint your Anthropic Messages client at AnyRouter by changing the base URL and API key. Your `messages.create` calls keep the native Anthropic body shape, and the `anthropic-version` header stays required. This guide is for teams already on the Anthropic API who want unified billing and access to every Claude model through one key.
## Before you start [#before-you-start]
* An AnyRouter API key (prefixed `sk-ar-`). Create one in the [dashboard](https://anyrouter.dev/dashboard/keys).
* An existing Anthropic Messages client (`@anthropic-ai/sdk` or raw HTTP).
The SDK appends `/v1/messages` to the base URL, so the AnyRouter base URL must stop at `/api` — not `/api/v1`. Setting it to `/api/v1` produces a double `/v1` and a 404.
## What changes [#what-changes]
| Setting | Before (Anthropic) | After (AnyRouter) |
| -------------- | ------------------------------- | -------------------------------------------------- |
| Base URL | `https://api.anthropic.com` | `https://anyrouter.dev/api` |
| API key | `ANTHROPIC_API_KEY` | Your AnyRouter key, prefixed `sk-ar-` |
| Version header | `anthropic-version: 2023-06-01` | Same. Required. |
| Model | `claude-sonnet-4-5` | `anthropic/claude-sonnet-4-6` or any `anthropic/*` |
## Before and after [#before-and-after]
Only the base URL, key, and model id change — the request body is native Anthropic.
```typescript
import Anthropic from "@anthropic-ai/sdk"
const client = new Anthropic({
baseURL: "https://api.anthropic.com",
apiKey: process.env.ANTHROPIC_API_KEY,
})
const message = await client.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Review this incident summary." }],
})
```
```typescript
import Anthropic from "@anthropic-ai/sdk"
const client = new Anthropic({
baseURL: "https://anyrouter.dev/api",
apiKey: process.env.ANYROUTER_API_KEY,
})
const message = await client.messages.create({
model: "anthropic/claude-sonnet-4-6",
max_tokens: 1024,
messages: [{ role: "user", content: "Review this incident summary." }],
})
```
## Migrate [#migrate]
::::steps
:::step{title="Point the SDK at AnyRouter"}
Set the base URL to `https://anyrouter.dev/api` (stopping at `/api`) and the API key to your AnyRouter key.
:::
:::step{title="Keep the version header"}
The `anthropic-version: 2023-06-01` header is still required. Leave it as-is.
:::
:::step{title="Update model ids to the anthropic/ form"}
Rewrite your model ids to include the owner:
* `claude-sonnet-4-5` → `anthropic/claude-sonnet-4-6`
* `claude-haiku` → `anthropic/claude-haiku-4-5`
:::
::::
## Verify [#verify]
Send one request against the Messages endpoint with your new key:
```bash
curl https://anyrouter.dev/api/v1/messages \
-H "x-api-key: sk-ar-your-key" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-haiku-4-5",
"max_tokens": 512,
"messages": [{"role": "user", "content": "Say hello in one sentence."}]
}'
```
The Messages endpoint accepts native Anthropic request bodies — same shape, same fields. For cross-provider routing instead, use [Chat Completions](/api-reference/chat-completions) or [Responses](/api-reference/responses).
## Migrate with an agent prompt [#migrate-with-an-agent-prompt]
Paste this into a coding agent (Claude Code, Cursor, Codex, …) to repoint the Anthropic client across the project:
```text
Migrate this project from the Anthropic API to AnyRouter.
1. Change the Anthropic SDK base URL to https://anyrouter.dev/api
(the SDK appends /v1/messages — the base URL must stop at /api).
2. Swap ANTHROPIC_API_KEY for ANYROUTER_API_KEY (prefix sk-ar-). Never hardcode it.
3. Keep the `anthropic-version: 2023-06-01` header — it is still required.
4. Rewrite model ids to the anthropic/ form:
claude-sonnet-4-5 → anthropic/claude-sonnet-4-6
claude-haiku → anthropic/claude-haiku-4-5
5. messages.create calls stay unchanged — native Anthropic body shape.
6. Keep streaming and error handling exactly as they are.
7. Add a smoke test that sends one short prompt and prints the reply.
Only swap the provider config — do not change UI or business logic.
```
## Troubleshooting [#troubleshooting]
:::details{summary="I get a 404 on every request"}
The base URL must stop at `/api`, not `/api/v1`. The SDK appends `/v1/messages` itself, so `/api/v1` becomes `/api/v1/v1/messages`. Set `baseURL` to `https://anyrouter.dev/api`.
:::
:::details{summary="I get a 401 Unauthorized"}
Anthropic clients send the key as `x-api-key`. Confirm your AnyRouter key (prefixed `sk-ar-`) is set there, and that the `anthropic-version` header is present.
:::
## Related [#related]
* [Messages API](/api-reference/messages) — native Anthropic endpoint reference.
* [Claude Code](/guides/claude-code) — route the Claude Code CLI through AnyRouter.
* [Models API](/api-reference/models) — list every available model.
---
# OpenAI (/guides/migrate-from-openai)
Change three values — base URL, API key, and model id — and your existing OpenAI SDK works against AnyRouter. Your `chat.completions` and `responses` calls stay exactly the same. This guide is for anyone already calling the OpenAI API who wants one gateway, unified billing, and 180+ models.
## Before you start [#before-you-start]
* An AnyRouter API key (prefixed `sk-ar-`). Create one in the [dashboard](https://anyrouter.dev/dashboard/keys).
* An existing OpenAI client (Python, TypeScript, or raw HTTP) you want to repoint.
Prefer to let a coding agent do the swap? Paste the [agent prompt](#migrate-with-an-agent-prompt) below into Claude Code, Cursor, or Codex and it will make all three changes across your project.
## What changes [#what-changes]
| Setting | Before (OpenAI) | After (AnyRouter) |
| -------- | --------------------------- | ----------------------------------------------------------------------- |
| Base URL | `https://api.openai.com/v1` | `https://anyrouter.dev/api/v1` |
| API key | `OPENAI_API_KEY` | Your AnyRouter key, prefixed `sk-ar-` |
| Model | `gpt-4o-mini` | `openai/gpt-5.4-mini` or any model from [Models](/api-reference/models) |
Everything else — request bodies, streaming, error handling — is unchanged.
## Before and after [#before-and-after]
The only diff is the base URL, the key, and the model id — same request body either way.
```typescript
import OpenAI from "openai"
const client = new OpenAI({
baseURL: "https://api.openai.com/v1",
apiKey: process.env.OPENAI_API_KEY,
})
const completion = await client.chat.completions.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Summarize this pull request." }],
})
```
```typescript
import OpenAI from "openai"
const client = new OpenAI({
baseURL: "https://anyrouter.dev/api/v1",
apiKey: process.env.ANYROUTER_API_KEY,
})
const completion = await client.chat.completions.create({
model: "openai/gpt-5.4-mini",
messages: [{ role: "user", content: "Summarize this pull request." }],
})
```
## Migrate [#migrate]
::::steps
:::step{title="Point the client at AnyRouter"}
Set the base URL to `https://anyrouter.dev/api/v1` and the API key to your AnyRouter key. The request body is identical to what you send OpenAI today.
:::
:::step{title="Update model ids to the provider/model form"}
AnyRouter model ids include the owner. Rewrite your ids:
* `gpt-4o-mini` → `openai/gpt-5.4-mini`
* `gpt-4o` → `openai/gpt-5.4`
Keep any `openai/*` id, or pick a different model from the [catalog](https://anyrouter.dev/models).
:::
:::step{title="Responses works with the same client"}
The Responses API is repointed the same way — no extra setup:
```typescript
const response = await client.responses.create({
model: "openai/gpt-5.4-mini",
input: "Extract the action items from this transcript.",
})
```
:::
::::
## Verify [#verify]
Send one request with your new key and confirm you get a reply:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-mini",
"messages": [{"role": "user", "content": "Write a release note."}]
}'
```
List every model available to your key:
```bash
curl https://anyrouter.dev/api/v1/models -H "Authorization: Bearer sk-ar-your-key"
```
## Routing preferences (optional) [#routing-preferences-optional]
Add a `provider` block to any request to cap price, prefer latency, or pin upstream order:
```json
{
"provider": {
"sort": "latency",
"max_price": { "prompt": "2.00", "completion": "8.00" },
"allow_fallbacks": true
}
}
```
See [Provider Routing](/guides/provider-routing) for every field.
## Migrate with an agent prompt [#migrate-with-an-agent-prompt]
Paste this into a coding agent (Claude Code, Cursor, Codex, …) to make the three changes across the whole project:
```text
Migrate this project from the OpenAI API to AnyRouter.
1. Change the OpenAI client base URL to https://anyrouter.dev/api/v1
2. Swap OPENAI_API_KEY for ANYROUTER_API_KEY (prefix sk-ar-). Never hardcode it.
3. Rewrite model ids to the provider/model form:
gpt-4o-mini → openai/gpt-5.4-mini
gpt-4o → openai/gpt-5.4
Keep openai/* ids, or pick any model from https://anyrouter.dev/models
4. chat.completions and responses calls stay unchanged — same request/response.
5. Keep streaming and error handling exactly as they are.
6. Add a smoke test that sends one short prompt and prints the reply.
Only swap the provider config — do not change UI or business logic.
```
## Troubleshooting [#troubleshooting]
:::details{summary="I get a 401 Unauthorized"}
Check that your key starts with `sk-ar-` and is sent as `Authorization: Bearer sk-ar-your-key`. The old `OPENAI_API_KEY` will not work against AnyRouter.
:::
:::details{summary="I get a 404 for my model"}
The model id must include the owner, e.g. `openai/gpt-5.4-mini`, not `gpt-4o-mini`. Confirm the id exists by listing [Models](/api-reference/models).
:::
## Related [#related]
* [Chat Completions API](/api-reference/chat-completions) — request and response reference.
* [Responses API](/api-reference/responses) — typed output items and tool calls.
* [Smart Routing](/features/routing) — failover and model aliases.
---
# OpenRouter (/guides/migrate-from-openrouter)
Moving from OpenRouter is mostly a base-URL and key change. AnyRouter accepts the same `provider/model` ids and the same request-level `provider` preferences on its OpenAI-compatible endpoints, so your ordering, price caps, and fallback settings carry over unchanged.
## Before you start [#before-you-start]
* An AnyRouter API key (prefixed `sk-ar-`). Create one in the [dashboard](https://anyrouter.dev/dashboard/keys).
* An existing OpenRouter integration built on the OpenAI-compatible client.
Your `HTTP-Referer` and `X-Title` headers can stay — AnyRouter reads both for dashboard attribution, so no header changes are needed.
## What changes [#what-changes]
| Setting | Before (OpenRouter) | After (AnyRouter) |
| -------------------- | ------------------------------ | ------------------------------------------- |
| Base URL | `https://openrouter.ai/api/v1` | `https://anyrouter.dev/api/v1` |
| API key | OpenRouter key | Your AnyRouter key, prefixed `sk-ar-` |
| Model | `provider/model` | `provider/model` from the AnyRouter catalog |
| Provider preferences | `provider` object | `provider` object — same fields |
## Before and after [#before-and-after]
The client swap is a base URL and key change; model ids keep the `provider/model` form.
```typescript
import OpenAI from "openai"
const client = new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: process.env.OPENROUTER_API_KEY,
})
const completion = await client.chat.completions.create({
model: "anthropic/claude-sonnet-4-6",
messages: [{ role: "user", content: "Summarize this support thread." }],
})
```
```typescript
import OpenAI from "openai"
const client = new OpenAI({
baseURL: "https://anyrouter.dev/api/v1",
apiKey: process.env.ANYROUTER_API_KEY,
})
const completion = await client.chat.completions.create({
model: "anthropic/claude-sonnet-4-6",
messages: [{ role: "user", content: "Summarize this support thread." }],
})
```
## Migrate [#migrate]
::::steps
:::step{title="Swap the base URL and key"}
Change the base URL from `https://openrouter.ai/api/v1` to `https://anyrouter.dev/api/v1`, and use your AnyRouter key (prefixed `sk-ar-`).
:::
:::step{title="Verify each model id exists"}
Model ids keep the same `provider/model` form. Confirm each one is in the AnyRouter catalog by listing [Models](/api-reference/models).
:::
:::step{title="Keep your provider preferences"}
Request-level `provider` preferences carry over unchanged: `only`, `ignore`, `order`, `sort`, `max_price`, and `allow_fallbacks`. Leave them as they are.
:::
::::
## OAuth PKCE [#oauth-pkce]
If your app lets users connect their OpenRouter account with OAuth PKCE, the same flow works on AnyRouter. Swap `openrouter.ai` for `anyrouter.dev` in the `/auth` URL and in the `/api/v1/auth/keys` exchange. See [OAuth PKCE](/guides/oauth-pkce).
## Provider preferences [#provider-preferences]
Use `only` to allowlist, `ignore` to exclude, `order` to pin a preference order, and `sort` to choose the strategy:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-mini",
"messages": [{"role": "user", "content": "Draft a changelog entry."}],
"provider": {
"only": ["OpenAI", "Groq"],
"order": ["Groq", "OpenAI"],
"sort": "latency",
"allow_fallbacks": true
}
}'
```
### Price caps [#price-caps]
Filter out upstreams above your budget. Prices are dollars per 1M tokens:
```json
{
"provider": {
"sort": "price",
"max_price": { "prompt": "1.00", "completion": "4.00" },
"allow_fallbacks": true
}
}
```
If every route is filtered out, the request fails — AnyRouter never silently ignores a price ceiling.
### Disable fallbacks [#disable-fallbacks]
Fallbacks are on by default. Turn them off when reproducibility matters more than availability (an eval run against a fixed upstream):
```json
{ "provider": { "allow_fallbacks": false } }
```
## Verify [#verify]
Send one request with your new key and confirm you get a reply:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"messages": [{"role": "user", "content": "Say hello in one sentence."}]
}'
```
## Migrate with an agent prompt [#migrate-with-an-agent-prompt]
Paste this into a coding agent (Claude Code, Cursor, Codex, …) to do the swap:
```text
Migrate this project from OpenRouter to AnyRouter.
1. Change the OpenAI client base URL from https://openrouter.ai/api/v1
to https://anyrouter.dev/api/v1
2. Swap the OpenRouter key for ANYROUTER_API_KEY (prefix sk-ar-). Never hardcode it.
3. Model ids keep the same provider/model form — verify each id exists in the
AnyRouter catalog at https://anyrouter.dev/api/v1/models
4. Request-level `provider` preferences (only / ignore / order / sort /
max_price / allow_fallbacks) are supported — keep them as they are.
5. Leave your HTTP-Referer and X-Title headers as-is — AnyRouter reads both for
dashboard attribution, so no header changes are needed.
6. Keep streaming and error handling unchanged.
7. Add a smoke test that sends one short prompt and prints the reply.
Only swap the provider config — do not change UI or business logic.
```
## Troubleshooting [#troubleshooting]
:::details{summary="A model id returns 404"}
The `provider/model` id must exist in the AnyRouter catalog. Some OpenRouter ids differ — list [Models](/api-reference/models) and pick the matching AnyRouter id.
:::
:::details{summary="My price cap is being ignored"}
It isn't — `max_price` is a hard filter. If no upstream fits your ceiling, the request fails rather than routing to a more expensive provider. Loosen the cap or set `allow_fallbacks: true`.
:::
## Related [#related]
* [Smart Routing](/features/routing) — how AnyRouter picks an upstream.
* [Provider Routing](/guides/provider-routing) — every `provider` field in detail.
* [Models API](/api-reference/models) — list every available model.
* [AnyRouter vs OpenRouter](/guides/anyrouter-vs-openrouter) — honest side-by-side.
* [OpenRouter alternative](https://anyrouter.dev/blog/openrouter-alternative) — checklist of markup, BYOK, billing, and lock-in.
---
# Using Responses API (/guides/migrate-to-responses)
Use the Responses endpoint when your application benefits from a single response envelope with typed output items, tool calls, and optional reasoning metadata. This guide is for agent and tool builders deciding between endpoints, and shows a minimal working request.
## Before you start [#before-you-start]
* An AnyRouter API key (prefixed `sk-ar-`). Create one in the [dashboard](https://anyrouter.dev/dashboard/keys).
* The OpenAI SDK, or any HTTP client — the Responses endpoint is OpenAI-compatible.
## When to use Responses [#when-to-use-responses]
| Use case | Recommended endpoint |
| ------------------------- | --------------------------------------------------- |
| Existing OpenAI chat app | [Chat Completions](/api-reference/chat-completions) |
| Anthropic Messages client | [Messages](/api-reference/messages) |
| Agent or tool workflow | [Responses](/api-reference/responses) |
| Model and price discovery | [Models](/api-reference/models) |
Reach for Responses when you want tool calls and reasoning kept in one typed `output` array — it makes multi-step agent loops easier to track than parsing chat deltas.
## Minimal request [#minimal-request]
The Responses endpoint uses the same base URL and key as Chat Completions:
```typescript
import OpenAI from "openai"
const client = new OpenAI({
baseURL: "https://anyrouter.dev/api/v1",
apiKey: process.env.ANYROUTER_API_KEY,
})
const response = await client.responses.create({
model: "openai/gpt-5.4-mini",
input: "Turn these notes into three action items.",
})
console.log(response.output_text)
```
```bash
curl https://anyrouter.dev/api/v1/responses \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-mini",
"input": "Turn these notes into three action items."
}'
```
## Add tools for agent loops [#add-tools-for-agent-loops]
Responses keeps tool calls in the `output` array, which makes multi-step agent loops easier to track:
```json
{
"model": "openai/gpt-5.4-mini",
"input": "Find the status of order 1234.",
"tools": [
{
"type": "function",
"name": "lookup_order",
"description": "Look up an order by id",
"parameters": {
"type": "object",
"properties": {
"order_id": { "type": "string" }
},
"required": ["order_id"]
}
}
],
"tool_choice": "auto"
}
```
## Routing preferences [#routing-preferences]
Responses accepts the same AnyRouter `provider` object as Chat Completions:
```json
{
"provider": {
"sort": "latency",
"allow_fallbacks": true,
"max_price": {
"prompt": "2.00",
"completion": "8.00"
}
}
}
```
See [Provider Routing](/guides/provider-routing) for every field.
## Verify [#verify]
Send the minimal request above and confirm `output_text` (TypeScript) or a populated `output` array (curl) comes back.
## Troubleshooting [#troubleshooting]
:::details{summary="output_text is empty but there was a tool call"}
When the model calls a tool, the answer lives in the `output` array as a tool-call item, not in `output_text`. Run the tool, append the result, and call again to get the final text.
:::
## Related [#related]
* [Responses API](/api-reference/responses) — full request and response reference.
* [Smart Routing](/features/routing) — how AnyRouter picks an upstream.
* [API Overview](/api-reference/overview) — all endpoints at a glance.
---
# Model Fallbacks (/guides/model-fallbacks)
Keep a request alive across a full provider outage by listing backup models. The `models` parameter automatically tries other models if the primary model's providers are down, rate-limited, or otherwise return a retryable error.
This complements [provider-level routing](/guides/provider-routing): provider routing fails over **between upstreams for the same model**, model fallbacks fail over **between different models**.
## Before you start [#before-you-start]
* An AnyRouter API key (prefixed `sk-ar-`). Create one in the [dashboard](https://anyrouter.dev/dashboard/keys).
* Two or more model ids from the [catalog](/api-reference/models) to chain.
Model fallbacks are currently supported on `/api/v1/chat/completions`. The Responses and Messages endpoints don't read the `models` field yet — use `/chat/completions` for cross-model failover.
## How it works [#how-it-works]
Provide an array of model IDs in priority order on the request body. AnyRouter:
1. Resolves upstream candidates for the primary `model` first.
2. Resolves upstream candidates for each entry in `models`, in order.
3. Concatenates the candidate lists and walks them with the same per-attempt timeout, retry, and cooldown logic used by single-model routing.
If `model` also appears in `models`, the duplicate is removed.
```typescript
const response = await fetch("https://anyrouter.dev/api/v1/chat/completions", {
method: "POST",
headers: {
Authorization: "Bearer ",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "anthropic/claude-sonnet-4-6",
models: ["openai/gpt-5.4-mini", "google/gemini-3.1-flash"],
messages: [
{ role: "user", content: "What is the meaning of life?" },
],
}),
})
const data = await response.json()
console.log(data.choices[0].message.content)
```
```python
import json
import requests
response = requests.post(
"https://anyrouter.dev/api/v1/chat/completions",
headers={
"Authorization": "Bearer ",
"Content-Type": "application/json",
},
data=json.dumps({
"model": "anthropic/claude-sonnet-4-6",
"models": ["openai/gpt-5.4-mini", "google/gemini-3.1-flash"],
"messages": [
{"role": "user", "content": "What is the meaning of life?"}
],
}),
)
data = response.json()
print(data["choices"][0]["message"]["content"])
```
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"models": ["openai/gpt-5.4-mini", "google/gemini-3.1-flash"],
"messages": [{"role": "user", "content": "What is the meaning of life?"}]
}'
```
The primary `model` is always tried first. Models listed in `models` are tried in array order. Entries in `models` that don't resolve to any configured upstream are skipped silently — you can pass an aspirational list and AnyRouter routes whatever it can.
## Fallback Behavior [#fallback-behavior]
For each candidate, AnyRouter runs the upstream call with a 60-second per-attempt timeout. The next candidate is tried when the previous attempt returns a retryable HTTP status, times out, or aborts. Currently retryable: **402, 403, 404, 408, 425, 429, 500, 502, 503, 504**, and network/abort errors. All other status codes (notably 400-level validation errors such as context-length overflow) surface immediately without falling back.
Within each model in the chain, AnyRouter still applies its normal [provider routing](/guides/provider-routing) — every eligible upstream for that model is attempted before moving on to the next model in `models`. Set `provider.allow_fallbacks: false` (combined with `provider.order`) to constrain the per-model candidate list to a single provider before falling over to the next model.
Fallback only happens on the **initial** response. Once an upstream returns 200 and AnyRouter starts piping SSE bytes back to the client, the stream cannot be rewound — mid-stream failures surface as-is.
## Pricing [#pricing]
Requests are priced using the model that actually served the response. The response body's `model` field is rewritten to that catalog model id so your client can see, and your billing/usage row records, which model in the chain was billed.
```json
{
"id": "chatcmpl-...",
"model": "openai/gpt-5.4-mini",
"choices": [...]
}
```
In this example the primary `anthropic/claude-sonnet-4-6` request failed and AnyRouter billed `openai/gpt-5.4-mini` — the model that actually answered. The same value appears on the `model` field of the metadata frame emitted at the end of a streamed response.
When the upstream provider returns its own cost signal (via a Cloudflare AI Gateway response header or a `usage.cost` field on the response body), AnyRouter captures it and surfaces it alongside our locally-computed price. You'll see:
* `usage.cost` — what AnyRouter charged you for the model that answered (`0` for free models and BYOK).
* `usage.cost_details.upstream_inference_cost` — what the upstream provider reported (when available); omitted when the provider reports none.
* `anyrouter_metadata.cost.upstreamAmount` — the upstream-reported cost on the metadata frame for streamed responses (when available).
## Using With the OpenAI SDK [#using-with-the-openai-sdk]
The OpenAI SDK doesn't expose a top-level `models` field, so pass it via `extra_body` (Python) or as a raw additional property (TypeScript). The example below tries `openai/gpt-5.4` first, then walks the `models` list.
```python
from openai import OpenAI
client = OpenAI(
base_url="https://anyrouter.dev/api/v1",
api_key="",
)
completion = client.chat.completions.create(
model="openai/gpt-5.4",
extra_body={
"models": ["anthropic/claude-sonnet-4-6", "google/gemini-3.1-flash"],
},
messages=[
{"role": "user", "content": "What is the meaning of life?"}
],
)
print(completion.choices[0].message.content)
```
```typescript
import OpenAI from "openai"
const client = new OpenAI({
baseURL: "https://anyrouter.dev/api/v1",
apiKey: "",
})
const completion = await client.chat.completions.create({
model: "openai/gpt-5.4",
// @ts-expect-error - non-standard OpenAI parameter, AnyRouter extension
models: ["anthropic/claude-sonnet-4-6", "google/gemini-3.1-flash"],
messages: [
{ role: "user", content: "What is the meaning of life?" },
],
})
console.log(completion.choices[0].message.content)
```
## Choosing a Fallback Chain [#choosing-a-fallback-chain]
A few patterns we see in production:
* **Same-family redundancy** — pair Claude with another Claude tier (`anthropic/claude-sonnet-4-6` → `anthropic/claude-haiku-4-5`) when behavior parity matters more than provider diversity.
* **Cross-provider redundancy** — pair Claude with GPT and Gemini to survive a full provider outage.
* **Quality → speed degradation** — start with a stronger model and fall back to a faster, cheaper one when load spikes (e.g. `openai/gpt-5.4` → `openai/gpt-5.4-mini`).
* **Open-weight backstop** — fall back to an open-weight model hosted on multiple providers (e.g. `meta-llama/llama-3.3-70b-instruct`) to survive proprietary-provider outages.
## Combining With Provider Sorting [#combining-with-provider-sorting]
When you want AnyRouter to pick the **single best endpoint across all models** (instead of strictly trying the primary first), combine `models` with `provider.sort` and `partition: "none"`. See [Advanced Sorting with Partition](/guides/provider-routing#advanced-sorting-with-partition).
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"models": ["openai/gpt-5.4-mini", "google/gemini-3.1-flash"],
"messages": [{"role": "user", "content": "Hello"}],
"provider": {
"sort": { "by": "throughput", "partition": "none" }
}
}'
```
With `partition: "none"`, AnyRouter sorts every endpoint across all three models by throughput and tries the fastest one first — useful when you have multiple acceptable models and just want whichever performs best right now.
## Verify [#verify]
Send a request whose primary model is unlikely to be your cheapest route and inspect the returned `model` field — it tells you which model in the chain actually answered:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"models": ["openai/gpt-5.4-mini", "google/gemini-3.1-flash"],
"messages": [{"role": "user", "content": "Say hello."}]
}'
```
## Troubleshooting [#troubleshooting]
:::details{summary="My fallback model was never tried"}
Fallback only triggers on retryable statuses — **402, 403, 404, 408, 425, 429, 500, 502, 503, 504**, and network/abort errors. A 400-level validation error (like context-length overflow) surfaces immediately without falling back. Fix the request rather than expecting a fallback.
:::
:::details{summary="One of my models in the list is silently ignored"}
Entries in `models` that don't resolve to any configured upstream are skipped silently — you can pass an aspirational list and AnyRouter routes whatever it can. Confirm each id exists in [Models](/api-reference/models).
:::
:::details{summary="My stream failed halfway and didn't fall back"}
Fallback happens only on the initial response. Once bytes are streaming, the stream can't be rewound. Add client-side retry logic for mid-stream failures.
:::
---
# OAuth PKCE (/guides/oauth-pkce)
Users can connect to AnyRouter in one click using [Proof Key for Code Exchange (PKCE)](https://oauth.net/2/pkce/). Your app sends the user to AnyRouter, the user approves, and your app exchanges a short-lived code for an API key that belongs to that user. Usage on that key is billed to the user's own account.
The flow is drop-in compatible with OpenRouter's OAuth PKCE flow. It needs no client registration and no client secret, so it works from a browser-only app, a CLI, or a local tool.
Looking for a registered app with a "Sign in with AnyRouter" button and revocable tokens? See [Sign in with AnyRouter](/guides/sign-in-with-anyrouter). This guide covers the simpler flow where the user approves once and your app receives a regular API key.
## How it works [#how-it-works]
::::steps
:::step{title="Send your user to AnyRouter"}
Send the user to the `/auth` page with a `callback_url` pointing back to your app:
```txt
# S256 code challenge (recommended)
https://anyrouter.dev/auth?callback_url=&code_challenge=&code_challenge_method=S256
# Plain code challenge
https://anyrouter.dev/auth?callback_url=&code_challenge=&code_challenge_method=plain
# No code challenge
https://anyrouter.dev/auth?callback_url=
```
The user signs in to AnyRouter if needed and sees a consent card with your app's name. When they approve, they are redirected back to your `callback_url` with a `code` query parameter. Any query string already on your `callback_url` is kept.
For S256, set `code_challenge` to the base64url encoding of the SHA-256 hash of your `code_verifier`. The verifier is a random string of 43 to 128 characters (letters, digits, `-`, `.`, `_`, `~`). Keep the verifier in your app; never put it in the URL.
The `code_challenge` is optional when you pass a `callback_url`, but recommended. With a challenge, the code is useless to anyone who does not hold your verifier. Without one, the code is only protected by being delivered to your `callback_url`: anyone who gets hold of it before you exchange it can use it.
If the user denies the request, they are redirected to your `callback_url` with `?error=access_denied`.
:::
:::step{title="Exchange the code for a key"}
Make a `POST` request to `https://anyrouter.dev/api/v1/auth/keys`. No `Authorization` header is needed.
```js
const response = await fetch("https://anyrouter.dev/api/v1/auth/keys", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
code: "",
code_verifier: "", // if code_challenge was used
code_challenge_method: "", // if code_challenge was used
}),
})
const { key, user_id } = await response.json()
```
The response contains the new key and the user's id:
```json
{
"key": "sk-ar-v1-actual-secret-only-shown-once",
"user_id": "user_123"
}
```
If you sent a challenge in step 1, the `code_verifier` is required. `code_challenge_method` must match the method you used in step 1.
:::
:::step{title="Use the key"}
Store the key for that user and send it as a Bearer token:
```js
const completion = await fetch("https://anyrouter.dev/api/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${key}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "anthropic/claude-sonnet-4-6",
messages: [{ role: "user", content: "Hello!" }],
}),
})
```
:::
::::
Authorization codes are single-use and expire after 10 minutes.
## Localhost apps [#localhost-apps]
Localhost callbacks work on **any port**. This suits CLI tools and local-first apps that bind a free port for the callback, for example `http://localhost:51423/callback`. `http://127.0.0.1` and `http://[::1]` work the same way. Every other callback must use `https`.
Apps with a localhost callback are named after their host and port (for example `localhost:3000`). Other apps are named after their host. That name appears on the consent card and on the key in the user's dashboard.
## Headless apps [#headless-apps]
If your app runs where a callback cannot be reached (an SSH session, a remote machine, a container), leave out `callback_url`:
```txt
https://anyrouter.dev/auth?code_challenge=&code_challenge_method=S256&key_label=
```
After the user approves, the page shows the code on screen instead of redirecting. The user copies it and pastes it into your app, and you exchange it in step 2 as usual.
A `code_challenge` is **required** in this mode. The code is displayed on screen, so PKCE is what keeps it useless to anyone without your verifier.
## Optional parameters [#optional-parameters]
Add these to the `/auth` URL. They prefill the consent form, and the user can change them before approving.
| Parameter | Effect |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `callback_url` | Where to send the user after approval. Omit for headless mode. |
| `code_challenge` | PKCE challenge. Required when `callback_url` is omitted. |
| `code_challenge_method` | `S256` or `plain`. Defaults to `plain` when a challenge is sent without a method. |
| `key_label` | Prefills the name of the key that will be created. |
| `workspace_id` | Preselects the workspace for the new key. The user can change it. |
| `required_workspace_id` | Requires the key to be created in this workspace. The choice is locked, and the user cannot approve if they are not a member. Takes precedence over `workspace_id`. |
| `limit` | Prefills a spend limit for the key, in USD. |
| `usage_limit_type` | How often the limit resets: `daily`, `weekly` or `monthly`. |
| `expires_at` | Prefills when the key expires, as an ISO 8601 timestamp in the future. |
Workspaces are always checked against the signed-in user's memberships. A workspace they cannot access is rejected.
## Complete browser example [#complete-browser-example]
A single self-contained page in plain browser JavaScript, with no bundler. Save it as `index.html`, serve it over `https` (or from `localhost`), and replace `CALLBACK_URL` if you serve it somewhere other than the default.
```html
Connect AnyRouter
Connect AnyRouter
```
This example prints the key so you can see it work. In a real app, store the key for that user and never log it or show it to anyone else.
## Errors [#errors]
The exchange returns these statuses:
| Status | Message | Cause |
| ------ | ------------------------------- | ------------------------------------------------------------------------------------------------------- |
| 400 | `Invalid code_challenge_method` | The `code_challenge_method` does not match the one used in step 1. |
| 403 | `Invalid code or code_verifier` | The code is unknown or already used, or the `code_verifier` is missing or does not match the challenge. |
| 403 | `Authorization code expired` | More than 10 minutes passed since the user approved. Send the user through step 1 again. |
| 405 | `Method Not Allowed` | The exchange accepts `POST` only. |
Other malformed requests, such as a missing `code`, return `400`.
## Managing and revoking keys [#managing-and-revoking-keys]
The key appears on the user's [Keys page](https://anyrouter.dev/dashboard/keys) with your app's name, so they can see which app created it. They can revoke it there at any time, and it stops working immediately. If the user set a spend limit or expiry on the consent card, it applies to the key. Your app should handle a `401` by sending the user through the flow again.
## Migrating from OpenRouter [#migrating-from-openrouter]
If you already use OpenRouter's OAuth PKCE flow, swap `openrouter.ai` for `anyrouter.dev`:
| Step | OpenRouter | AnyRouter |
| --------- | ---------------------------------------- | ---------------------------------------- |
| Authorize | `https://openrouter.ai/auth?...` | `https://anyrouter.dev/auth?...` |
| Exchange | `https://openrouter.ai/api/v1/auth/keys` | `https://anyrouter.dev/api/v1/auth/keys` |
| Inference | `https://openrouter.ai/api/v1/...` | `https://anyrouter.dev/api/v1/...` |
Parameters, response shape and error statuses are the same. Two differences: the exchange response also includes `user_id`, and AnyRouter adds the optional `limit`, `usage_limit_type` and `expires_at` parameters.
## Linking users to a key [#linking-users-to-a-key]
OpenRouter's PKCE guide tells your app to deep-link the user to the key it just created, so your connection page lands on that key instead of a bare dashboard. AnyRouter accepts the same two addresses:
| Deep link | Lands on |
| --------------------------------------------------------- | ------------------------------------------------------------ |
| `https://anyrouter.dev/keys/` | That key's page, with its usage, spend, routing and settings |
| `https://anyrouter.dev/logs?api_key_hash=` | Request logs, filtered to that key |
The hash is the lowercase hex SHA-256 of the key string — the same digest AnyRouter stores, so your app can compute it without asking the user for anything:
```js
const hashHex = Array.from(
new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(key))),
)
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("")
location.href = `https://anyrouter.dev/keys/${hashHex}`
```
A deep link only resolves inside the account that owns the key. If the visitor is signed out, or the hash belongs to a different account, the page returns **404** instead of showing an unfiltered key or log view. Add `?org=` to `/logs?api_key_hash=` when you created the key in a specific workspace.
---
# OpenAI SDK (/guides/openai-sdk)
AnyRouter speaks the OpenAI API, so the official [OpenAI SDK](https://github.com/openai/openai-python) works as-is. Point it at AnyRouter, pass your AnyRouter key, and every model in the catalog — including free ones — is one `model` string away.
Moving an existing OpenAI app? See [Migrate from OpenAI](/guides/migrate-from-openai) for a before/after diff. Prefer raw HTTP? See [curl](/guides/curl).
## Before you start [#before-you-start]
* Python 3.10+ or Node.js 22+ (current SDK requirements).
* An AnyRouter API key (prefix `sk-ar-`). Create one in the [dashboard](https://anyrouter.dev/dashboard/keys).
## Set it up [#set-it-up]
::::steps
:::step{title="Install the SDK"}
```bash
pip install openai # Python
npm install openai # TypeScript / JavaScript
```
:::
:::step{title="Point the client at AnyRouter"}
Set the client's base URL to `https://anyrouter.dev/api/v1` and pass your AnyRouter key. Export the key so the examples below can read it:
```bash
export ANYROUTER_API_KEY="sk-ar-your-key-here"
```
:::
:::step{title="Pick a model and verify"}
Run the example below. It prints the model that served the request, then the reply. `anyrouter/free` picks a free model for you; `anyrouter/auto` picks a model automatically from the full catalog; any `provider/model` id from the [catalog](https://anyrouter.dev/models) pins one model.
Every request shows up in [Logs](https://anyrouter.dev/dashboard/logs) with the model, provider, tokens, and latency.
:::
::::
## Example [#example]
```python
import os
from openai import OpenAI
client = OpenAI(
base_url="https://anyrouter.dev/api/v1",
api_key=os.environ["ANYROUTER_API_KEY"], # sk-ar-...
)
completion = client.chat.completions.create(
model="anyrouter/free",
messages=[{"role": "user", "content": "Say hello in five words."}],
)
print(completion.model)
print(completion.choices[0].message.content)
```
```typescript
import OpenAI from "openai"
const client = new OpenAI({
baseURL: "https://anyrouter.dev/api/v1",
apiKey: process.env.ANYROUTER_API_KEY, // sk-ar-...
})
const completion = await client.chat.completions.create({
model: "anyrouter/free",
messages: [{ role: "user", content: "Say hello in five words." }],
})
console.log(completion.model)
console.log(completion.choices[0].message.content)
```
## Free models [#free-models]
Set `model` to `anyrouter/free` and AnyRouter routes the request to a free model that is healthy right now — no credits used. Free models have no daily cap on any plan. Details on [Free Tier](/features/free-tier).
## Bring your own keys [#bring-your-own-keys]
Already pay for OpenAI, Anthropic, Google, or another provider? Add those keys to AnyRouter and your SDK calls use them, with no credits deducted. See [Get a provider API key](/guides/byok-provider-keys) and [BYOK](/features/byok).
## Troubleshooting [#troubleshooting]
:::details{summary="401 Unauthorized"}
Check that the key starts with `sk-ar-` and is the one passed to the client. Manage keys in the [dashboard](https://anyrouter.dev/dashboard/keys).
:::
:::details{summary="404 model not found"}
Use a full catalog id such as `anyrouter/free` or `provider/model`, not a bare model name. Browse ids in the [catalog](https://anyrouter.dev/models).
:::
:::details{summary="429 on anyrouter/free"}
This key went over the free-model limit of 2400 requests per minute. Free models have no daily cap. Wait for the number of seconds in the `Retry-After` header and retry, or spread the traffic across API keys.
:::
## Related [#related]
* [Migrate from OpenAI](/guides/migrate-from-openai) — move an existing app
* [curl](/guides/curl) — the same calls from the terminal
* [Vercel AI SDK](/guides/vercel-ai-sdk) — TypeScript framework
* [LangChain](/guides/langchain) — chains and agents
* [LlamaIndex](/guides/llamaindex) — RAG and agents
* [Chat Completions API](/api-reference/chat-completions) — the underlying endpoint
---
# OpenClaw (/guides/openclaw)
[OpenClaw](https://github.com/openclaw/openclaw) is a personal AI assistant that answers you on the messaging channels you already use (Telegram, Discord, Slack, WhatsApp, Signal, iMessage, Email, …). It runs locally and supports any OpenAI-compatible LLM provider — so pointing it at AnyRouter is a config-only change, and every request then flows through one gateway.
## Why route through AnyRouter [#why-route-through-anyrouter]
* **One key** for every model OpenClaw can use.
* **Audit logs** with cost, latency, and tokens per request.
* **Free model swaps** — change one line in `~/.openclaw/openclaw.json`, no key rotation.
## Set it up [#set-it-up]
Create an AnyRouter API key in your [dashboard](https://anyrouter.dev/dashboard/keys) first — every key starts with `sk-ar-`. Then choose the onboarding wizard or edit the config file directly.
Run the OpenClaw onboarding wizard — it handles gateway, workspace, channels, and provider in one flow:
```bash
openclaw onboard --install-daemon
```
When the wizard asks for an LLM provider, pick **Custom (OpenAI-compatible)** and enter:
* **Base URL** — `https://anyrouter.dev/api/v1`
* **API key** — your `sk-ar-…` key
* **Model** — any AnyRouter model id, e.g. `google/gemini-3.1-flash-lite` (the wizard registers it under the provider, then references it as `anyrouter/google/gemini-3.1-flash-lite`)
Then start the gateway and OpenClaw will route every request through AnyRouter:
```bash
openclaw gateway --port 18789 --verbose
```
OpenClaw stores config in `~/.openclaw/openclaw.json`:
```json
{
"env": {
"ANYROUTER_API_KEY": "sk-ar-your-anyrouter-key"
},
"models": {
"mode": "merge",
"providers": {
"anyrouter": {
"baseUrl": "https://anyrouter.dev/api/v1",
"api": "openai-completions",
"apiKey": "${ANYROUTER_API_KEY}",
"models": [
{
"id": "google/gemini-3.1-flash-lite",
"name": "Gemini 3.1 Flash Lite",
"contextWindow": 1000000
}
]
}
}
},
"agents": {
"defaults": {
"model": {
"primary": "anyrouter/google/gemini-3.1-flash-lite"
}
}
}
}
```
Two things OpenClaw requires for a custom provider:
* **The key is an interpolated env var** — `apiKey: "${ANYROUTER_API_KEY}"`, with the value supplied in the top-level `env` block (or your shell). There is no `apiKeyEnv` field.
* **Every model must be declared** in the provider's `models[]` array (at minimum an `id`). OpenClaw won't route to an undeclared model id. Add one entry per AnyRouter model you intend to use.
Reference a model as `anyrouter/` — the provider name you registered (`anyrouter`) plus the declared model `id` (e.g. `anyrouter/google/gemini-3.1-flash-lite`). `mode: "merge"` keeps OpenClaw's built-in providers alongside yours.
## Verify [#verify]
Message OpenClaw on any connected channel, then open your [request logs](https://anyrouter.dev/dashboard/logs). The call should appear within a second or two with the model name, token counts, and cost — confirmation you're routing through AnyRouter. You can also run `openclaw models status --probe` to live-test provider connectivity.
## Recommended models [#recommended-models]
* `anyrouter/google/gemini-3.1-flash-lite` — default low-latency model for most channels
* `anyrouter/anthropic/claude-sonnet-4-6` — best quality for long threads and tool use
* `anyrouter/openai/gpt-5.4-mini` — strong balance, broad capability coverage
List every available model:
```bash
curl https://anyrouter.dev/api/v1/models -H "Authorization: Bearer sk-ar-your-key"
```
## Different models per use case [#different-models-per-use-case]
OpenClaw selects models per **agent**, not per channel directly. Define multiple agents in `agents.list[]` — each with its own `model.primary` — then bind channels to those agents in OpenClaw's `bindings` config:
```json
{
"agents": {
"defaults": {
"model": { "primary": "anyrouter/google/gemini-3.1-flash-lite" }
},
"list": [
{
"id": "fast",
"model": { "primary": "anyrouter/google/gemini-3.1-flash-lite" }
},
{
"id": "deep",
"model": { "primary": "anyrouter/anthropic/claude-sonnet-4-6" }
}
]
}
}
```
Every model referenced here must also be declared in the `anyrouter` provider's `models[]` array (see above). Channel-to-agent routing (e.g. send Telegram to `fast`, Discord to `deep`) is configured in OpenClaw's `bindings` section — see the [OpenClaw configuration reference](https://docs.openclaw.ai/gateway/configuration).
## Troubleshooting [#troubleshooting]
**401 or 403** — verify `echo $ANYROUTER_API_KEY` returns your key and that the dashboard shows it active with credit remaining.
**Unknown model** — make sure the id is prefixed `anyrouter/`, that it's declared in the provider's `models[]` array, and that the rest matches an entry from `GET /api/v1/models`. Run `openclaw models status --probe` to live-test provider connectivity and surface routing issues.
**429** — OpenClaw heartbeats and scheduled tasks can burst. Back off and retry — the same key works again next window. See [Rate Limits](/features/rate-limits).
## Related [#related]
* [OpenClaw on GitHub](https://github.com/openclaw/openclaw)
* [Claude Code](/guides/claude-code)
* [Hermes Agent](/guides/hermes-agent)
* [Smart Routing](/features/routing)
---
# OpenCode with AnyRouter — free models, one API key (/guides/opencode)
Point [OpenCode](https://opencode.ai) at AnyRouter and every session runs through one gateway: unified billing, request audit logs, and a single `sk-ar-` key that reaches every model AnyRouter serves. OpenCode speaks the OpenAI-compatible wire protocol, so this is a config-only change — no OpenCode fork, no rebuild.
## Why route through AnyRouter [#why-route-through-anyrouter]
* **Drop-in.** Zero code changes. The fastest path is one command; the manual path is one provider block in `opencode.json`.
* **One key.** A single AnyRouter API key unlocks the catalog — swap Claude, GLM, Gemini, or GPT without rotating credentials.
* **Audit logs.** Every request lands in your dashboard with cost, latency, and token usage.
* **Correct context windows.** The CLI writes OpenCode's `limit.context` and `limit.output` from the catalog so the TUI sizes conversations correctly.
This is the narrative "use OpenCode with AnyRouter" guide. For the full command reference — flags, profiles, `--dry-run`, and the login flow — see the canonical **[OpenCode CLI setup](/cli/opencode)**. To add AnyRouter as an MCP server (catalog, credits, keys inside the chat), see **[AnyRouter MCP with OpenCode](/mcp/opencode)**.
## Before you start [#before-you-start]
* [OpenCode installed](https://opencode.ai/docs) (`opencode` on your `$PATH`).
* An AnyRouter account. Create a key in the [dashboard](https://anyrouter.dev/dashboard/keys) if you will configure OpenCode by hand — every inference key starts with `sk-ar-`.
## Set it up [#set-it-up]
One command handles login, the key, and a session-only OpenCode provider:
```bash
curl -fsSL https://anyrouter.dev/setup.sh | bash
anyr opencode
```

The first run opens anyrouter.dev (or a device code on SSH). After that, the same stored profile launches OpenCode with AnyRouter already selected.
Pin a model or forward flags:
```bash
anyr opencode --model "z-ai/glm-5.2"
anyr opencode --model auto -- --help
```
Full options are in the **[CLI reference](/cli/opencode)**.
Use this when you want `opencode` (not the AnyRouter CLI) to talk to AnyRouter every time.
### Step 1 — Export your AnyRouter key [#step-1--export-your-anyrouter-key]
```bash
export ANYROUTER_API_KEY="sk-ar-your-anyrouter-key"
```
### Step 2 — Add the AnyRouter provider [#step-2--add-the-anyrouter-provider]
Create `~/.config/opencode/opencode.json` (every project) or `./opencode.json` (this repo):
```json
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"anyrouter": {
"npm": "@ai-sdk/openai-compatible",
"name": "AnyRouter",
"options": {
"baseURL": "https://anyrouter.dev/api/v1",
"apiKey": "{env:ANYROUTER_API_KEY}"
},
"models": {
"anthropic/claude-sonnet-4-6": {
"name": "Claude Sonnet 4.6",
"limit": { "context": 200000, "output": 65536 }
},
"z-ai/glm-5.2": {
"name": "GLM-5.2",
"limit": { "context": 1000000, "output": 65536 }
}
}
}
},
"model": "anyrouter/anthropic/claude-sonnet-4-6"
}
```

`apiKey` is the **environment variable name** in `{env:…}` form — never the secret itself.
### Step 3 — Launch OpenCode and pick the model [#step-3--launch-opencode-and-pick-the-model]
```bash
opencode
```
Open `/models` and choose an `anyrouter/…` id. The TUI labels the provider **AnyRouter**.

## What a session looks like [#what-a-session-looks-like]
OpenCode sends `/v1/chat/completions` to `https://anyrouter.dev/api/v1`. AnyRouter authenticates the `sk-ar-` key, picks an upstream (including your BYOK keys), and records the call.
## Free models and auto routing [#free-models-and-auto-routing]
Set the model to an AnyRouter router id instead of a fixed model:
| Model id | What it does |
| ---------------- | -------------------------------------------------------------------------------------------- |
| `anyrouter/free` | Routes to the best free model available right now, with automatic fallback. No credits used. |
| `anyrouter/auto` | Picks from top models (free and paid) with health-aware failover. Paid members use credits. |
```bash
anyr opencode --model anyrouter/free
```
With a persistent `opencode.json`, add `"anyrouter/free": { "name": "AnyRouter Free" }` to the `models` map and select `anyrouter/anyrouter/free` in `/models`.
`anyrouter/free` needs no provider keys. It has no daily cap on any plan. Details: [Free Tier](/features/free-tier).
If `anyrouter/auto` or `anyrouter/free` returns `400 no_eligible_models`, your privacy or routing settings (for example Zero Data Retention or blocked providers) excluded every candidate — relax them in the dashboard or pick a specific model.
## Bring your own keys [#bring-your-own-keys]
Already pay for a provider? Add its key in AnyRouter and OpenCode uses it through the same base URL. BYOK requests are billed by your provider. See [Use your own provider keys](/guides/byok-provider-keys) and [BYOK](/features/byok).
## Auto-connect with the anyr CLI [#auto-connect-with-the-anyr-cli]
`anyr opencode` connects the AnyRouter MCP gateway for you when **Auto-connect in coding agents** is on in the [dashboard](https://anyrouter.dev/dashboard/mcp). It uses the same key the session launches with, so there is nothing to paste. To turn it off, pass `--no-mcp` on a launch, run `anyr mcp auto off`, or switch it off in the dashboard.
## Verify [#verify]
Run any prompt, then confirm it routed through AnyRouter:
```bash
anyr opencode
```
Open your [request logs](https://anyrouter.dev/dashboard/logs) — the call should appear within a second or two, with the model name, token counts, and cost.

If the row is there, OpenCode is routing through AnyRouter.
## MCP tools in the same session [#mcp-tools-in-the-same-session]
The provider block above is for **inference**. To let OpenCode call AnyRouter tools (`get_credits`, `list_models`, key management), add the MCP server to the same `opencode.json` — see the [MCP guide](/mcp/opencode).
## Troubleshooting [#troubleshooting]
:::details{summary="401 or 403"}
Confirm the key is active in your [dashboard](https://anyrouter.dev/dashboard/keys) and that the process OpenCode is running in has `ANYROUTER_API_KEY` (persistent config) or a stored CLI profile (`anyr config get`).
:::
:::details{summary="Model not found"}
The models-map key must match an AnyRouter id exactly. Filter the catalog to [Chat Completions](https://anyrouter.dev/models?endpoint=chat-completions). The OpenCode default is `anyrouter/`, not the bare id.
:::
:::details{summary="Config is invalid / missing limit.output"}
OpenCode requires both `limit.context` and `limit.output` if `limit` is set. Add `"output": 65536`, or omit `limit`. The CLI writes both.
:::
:::details{summary="Wrong provider still answering"}
A `/connect` login for OpenCode Zen, or a leftover `openai` provider, can take precedence when you launch `opencode` directly. Use `/models` and pick the `anyrouter/…` namespace, or launch with `anyr opencode` so the session-only provider is injected.
:::
---
# Pi (/guides/pi)
[Pi](https://pi.dev) can use custom OpenAI-compatible providers declared in `~/.pi/agent/models.json`. Register AnyRouter once and you can run Pi against any AnyRouter model id — with one `sk-ar-` key, unified billing, and full audit logs.
## Why route through AnyRouter [#why-route-through-anyrouter]
* **One key** for every model Pi can call.
* **Audit logs** with cost, latency, and tokens per request.
* **Swap models freely** — add ids to `models.json`, no key rotation.
## Fastest path [#fastest-path]
```bash
curl -fsSL https://anyrouter.dev/setup.sh | bash
anyr pi
anyr pi --install --ok
```
The launcher writes a Pi agent dir (`PI_CODING_AGENT_DIR`) with AnyRouter already in `models.json`, sets `ANYROUTER_API_KEY`, and starts `pi --provider anyrouter`. Manual `models.json` below is only if you run `pi` yourself.
## Set it up [#set-it-up]
### Step 1 — Create an AnyRouter API key [#step-1--create-an-anyrouter-api-key]
Create an API key in your [dashboard](https://anyrouter.dev/dashboard/keys). AnyRouter keys start with `sk-ar-`. Add it to your shell:
```bash
export ANYROUTER_API_KEY="sk-ar-your-anyrouter-key"
```
Put the same line in `~/.zshrc` or `~/.bashrc` if you want it to persist.
### Step 2 — Add AnyRouter to Pi [#step-2--add-anyrouter-to-pi]
Create or edit `~/.pi/agent/models.json`:
```json
{
"providers": {
"anyrouter": {
"baseUrl": "https://anyrouter.dev/api/v1",
"api": "openai-completions",
"apiKey": "ANYROUTER_API_KEY",
"models": [
{ "id": "openai/gpt-5.4-mini" },
{ "id": "anthropic/claude-sonnet-4-6" }
]
}
}
}
```
`apiKey` is the **environment variable name**, not the secret value. Do not paste your `sk-ar-` key into this file.
### Step 3 — Run Pi [#step-3--run-pi]
Use the custom provider and pick one of the model ids from `models.json`:
```bash
pi --provider anyrouter --model "openai/gpt-5.4-mini"
```
Add more models from the [model catalog](https://anyrouter.dev/models) by appending entries to the `models` array. Keep the `id` exactly as shown in AnyRouter, such as `google/gemini-3.1-flash-lite` or `z-ai/glm-4.7-flash`.
## MCP tools [#mcp-tools]
Pi has no MCP support, so `anyr pi` connects nothing and the Auto-connect setting does not apply to it.
## Verify [#verify]
Run a prompt through the custom provider, then open your [request logs](https://anyrouter.dev/dashboard/logs). The call should appear within a second or two with the model name, token counts, and cost — confirmation you're routing through AnyRouter.
## Troubleshooting [#troubleshooting]
**401 or 403** — confirm `echo $ANYROUTER_API_KEY` prints your key and that the key is active in the dashboard.
**Model not found** — make sure the model id in Pi exactly matches an AnyRouter model id.
**429** — Pi can send repeated agent calls quickly. Wait for the rate-limit window to reset or use a key with a higher limit.
## Related [#related]
* [Pi custom models](https://pi.dev/docs/latest/models)
* [Chat Completions API](/api-reference/chat-completions)
* [Models API](/api-reference/models)
* [Rate Limits](/features/rate-limits)
---
# Provider Routing (/guides/provider-routing)
Control which upstream provider serves each request — pin an order, cap price, prefer latency or throughput, or restrict to providers that meet your data policy. AnyRouter routes each request to the best available upstream for the model you asked for; the `provider` object lets you steer that per request.
By default, requests are load balanced across the top-priority providers to maximize uptime. You customize routing with the `provider` object in the request body for [Chat Completions](/api-reference/chat-completions); the same object is accepted on Responses and Messages endpoints.
## Before you start [#before-you-start]
* An AnyRouter API key (prefixed `sk-ar-`). Create one in the [dashboard](https://anyrouter.dev/dashboard/keys).
* A model id from the [catalog](/api-reference/models) to route.
## Steer a request [#steer-a-request]
::::steps
:::step{title="Add a provider object to the body"}
Every routing control lives under a top-level `provider` key alongside `model` and `messages`.
:::
:::step{title="Pick a strategy"}
Set `sort` to `"price"`, `"throughput"`, or `"latency"`, or set `order` to pin a provider preference list. Add `max_price` to enforce a budget ceiling.
:::
:::step{title="Decide on fallbacks"}
Leave `allow_fallbacks` at its default `true` for resilience, or set it to `false` when you need a request served only by your chosen provider.
:::
::::
Model-id shortcuts save you a `provider` object: append `:nitro` to sort by throughput, `:floor` to sort by price, `:fast` to sort by latency, or `:exacto` to prefer higher-precision weights (and quality-first members on `anyrouter/auto`). On `anyrouter/auto`, append `[1m]` or `[500k]` to keep only members with at least that context window (`provider.min_context`).
## Field reference [#field-reference]
The `provider` object can contain the following fields:
| Field | Type | Default | Description |
| -------------------------- | ----------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `order` | string\[] | – | List of provider slugs to try in order (e.g. `["anthropic", "openai"]`). [Learn more](#ordering-specific-providers) |
| `allow_fallbacks` | boolean | `true` | Whether to allow backup providers when the primary is unavailable. [Learn more](#disabling-fallbacks) |
| `require_parameters` | boolean | `false` | Only use providers that support all parameters in your request. [Learn more](#requiring-providers-to-support-all-parameters) |
| `require_params` | string\[] | – | Hard parameter requirements (for example `["tools"]`). Distinct from `require_parameters`. [Learn more](#requiring-tool-calling-and-context-size) |
| `min_context` | number | – | Minimum context window in tokens. `anyrouter/auto` keeps only members at or above this floor. [Learn more](#requiring-tool-calling-and-context-size) |
| `data_collection` | "allow" \| "deny" | "allow" | Control whether to use providers that may store data. [Learn more](#requiring-providers-to-comply-with-data-policies) |
| `zdr` | boolean | – | Restrict routing to only Zero Data Retention endpoints. [Learn more](#zero-data-retention-enforcement) |
| `enforce_distillable_text` | boolean | – | Restrict routing to only models that allow text distillation. [Learn more](#distillable-text-enforcement) |
| `only` | string\[] | – | List of provider slugs to allow for this request. [Learn more](#allowing-only-specific-providers) |
| `ignore` | string\[] | – | List of provider slugs to skip for this request. [Learn more](#ignoring-providers) |
| `quantizations` | string\[] | – | List of quantization levels to filter by (e.g. `["int4", "int8"]`). [Learn more](#quantization) |
| `sort` | string \| object | – | Sort providers by price, throughput, latency, or exacto. Can be a string (e.g. `"price"`) or an object with `by` and `partition` fields. [Learn more](#provider-sorting) |
| `preferred_min_throughput` | number \| object | – | Preferred minimum throughput (tokens/sec). Number, or object with percentile cutoffs (p50/p75/p90/p99). [Learn more](#performance-thresholds) |
| `preferred_max_latency` | number \| object | – | Preferred maximum latency (seconds). Number, or object with percentile cutoffs (p50/p75/p90/p99). [Learn more](#performance-thresholds) |
| `max_price` | object | – | The maximum pricing you want to pay for this request. [Learn more](#maximum-price) |
**Support status.** Every field documented on this page is honored end-to-end: `order`, `only`, `ignore`, `allow_fallbacks`, `sort` (string form including `"exacto"`, `partition: "model"`, and `partition: "none"` across `models`), `max_price`, `preferred_max_latency`, `preferred_min_throughput`, `require_parameters`, `require_params`, `min_context`, `data_collection`, `zdr`, `quantizations`, `enforce_distillable_text`, and the `:nitro` / `:floor` / `:fast` / `:exacto` model-id shortcuts. Filters that depend on provider metadata (`zdr`, `quantizations`, `data_collection`, `enforce_distillable_text`) operate on the per-upstream `features` block and the model's `data_policy` declarations in our catalog. As that metadata fills in across more providers, the result set for these filters will grow without any client change.
## Price-Based Load Balancing (Default Strategy) [#price-based-load-balancing-default-strategy]
For each model in your request, AnyRouter's default behavior is to load balance requests across providers, prioritizing price.
If you are more sensitive to throughput than price, use the `sort` field to explicitly prioritize throughput.
> When you send a request with `tools` or `tool_choice`, AnyRouter only routes to providers that support tool use. Similarly, if you set `max_tokens`, AnyRouter only routes to providers that support a response of that length.
The default load balancing strategy:
1. Prioritize providers that have not seen significant outages in the last 30 seconds.
2. For the stable providers, look at the lowest-cost candidates and pick one weighted by the inverse square of the price (example below).
3. Use the remaining providers as fallbacks.
**Example.** If Provider A costs $1/M tokens, Provider B costs $2, and Provider C costs $3, and Provider B recently saw a few outages:
* Your request is 9× more likely to land on Provider A than Provider C, because $(1 / 3^2 = 1/9)$.
* If Provider A fails, Provider C is tried next.
* If Provider C also fails, Provider B is tried last.
If you have `sort` or `order` set, load balancing is disabled.
## Provider Sorting [#provider-sorting]
To explicitly prioritize a particular provider attribute, include `sort` in the `provider` preferences. Load balancing is disabled, and the router tries providers in order.
The sort options are:
* `"price"`: prioritize lowest price
* `"throughput"`: prioritize highest throughput
* `"latency"`: prioritize lowest latency
* `"exacto"`: prefer higher-precision weights on a single model, and quality-first members on `anyrouter/auto`
```typescript
fetch("https://anyrouter.dev/api/v1/chat/completions", {
method: "POST",
headers: {
Authorization: "Bearer ",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "meta-llama/llama-3.3-70b-instruct",
messages: [{ role: "user", content: "Hello" }],
provider: { sort: "throughput" },
}),
})
```
```python
import requests
requests.post(
"https://anyrouter.dev/api/v1/chat/completions",
headers={
"Authorization": "Bearer ",
"Content-Type": "application/json",
},
json={
"model": "meta-llama/llama-3.3-70b-instruct",
"messages": [{"role": "user", "content": "Hello"}],
"provider": {"sort": "throughput"},
},
)
```
To always prioritize low prices, set `sort` to `"price"`. To always prioritize low latency, set `sort` to `"latency"`.
## Nitro Shortcut [#nitro-shortcut]
Append `:nitro` to any model slug as a shortcut to sort by throughput. This is exactly equivalent to setting `provider.sort` to `"throughput"`.
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/llama-3.3-70b-instruct:nitro",
"messages": [{"role": "user", "content": "Hello"}]
}'
```
## Floor Price Shortcut [#floor-price-shortcut]
Append `:floor` to any model slug as a shortcut to sort by price. This is exactly equivalent to setting `provider.sort` to `"price"`.
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/llama-3.3-70b-instruct:floor",
"messages": [{"role": "user", "content": "Hello"}]
}'
```
## Exacto Shortcut [#exacto-shortcut]
Append `:exacto` to any model slug as a shortcut to set `provider.sort` to `"exacto"`. On a concrete model this prefers higher-precision weights. On `anyrouter/auto` it picks members by quality instead of live usage. Exacto is a routing preference, not a new model id.
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "anyrouter/auto:exacto",
"messages": [{"role": "user", "content": "Hello"}]
}'
```
## Context-window shortcut [#context-window-shortcut]
Append `[1m]` or `[500k]` (or any `[k]` / `[m]`) to `anyrouter/auto` to set `provider.min_context`. `[1m]` keeps members with at least 1M tokens; `[500k]` keeps members with at least 500k. Combine with `:exacto` (`anyrouter/auto[1m]:exacto`).
```bash
anyr claude --yolo --model anyrouter/auto[1m]
anyr claude --model anyrouter/auto[500k]
```
## Advanced Sorting with Partition [#advanced-sorting-with-partition]
When using [model fallbacks](/features/routing), `sort` can be specified as an object with additional options to control how endpoints are sorted across multiple models.
| Field | Type | Default | Description |
| ---------------- | ------ | --------- | -------------------------------------------------------------------- |
| `sort.by` | string | – | The sorting strategy: `"price"`, `"throughput"`, or `"latency"`. |
| `sort.partition` | string | `"model"` | How to group endpoints for sorting: `"model"` (default) or `"none"`. |
By default, when you specify multiple models (fallbacks), AnyRouter groups endpoints by model before sorting. This means the primary model's endpoints are always tried first, regardless of their performance characteristics. Setting `partition` to `"none"` removes this grouping, allowing endpoints to be sorted globally across all models.
> `preferred_max_latency` and `preferred_min_throughput` do not guarantee you will get a provider or model with this performance level. Providers that hit your thresholds are preferred; those that don't are deprioritized as fallbacks. This is different from `max_price`, which is a hard filter — a request can fail if no provider meets it.
### Use Case 1: Route to the Highest Throughput or Lowest Latency Model [#use-case-1-route-to-the-highest-throughput-or-lowest-latency-model]
When you have multiple acceptable models and want whichever has the best performance right now, use `partition: "none"` with throughput or latency sorting:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"models": [
"anthropic/claude-sonnet-4-6",
"openai/gpt-5.4-mini",
"google/gemini-3.1-flash"
],
"messages": [{"role": "user", "content": "Hello"}],
"provider": {
"sort": { "by": "throughput", "partition": "none" }
}
}'
```
AnyRouter routes to whichever endpoint across all three models currently has the highest throughput, rather than always trying Claude first.
## Performance Thresholds [#performance-thresholds]
You can set minimum throughput or maximum latency thresholds to filter endpoints. Endpoints that don't meet these thresholds are deprioritized (moved to the end of the list) rather than excluded entirely.
| Field | Type | Default | Description |
| -------------------------- | ---------------- | ------- | ------------------------------------------------------------------------------------------------------ |
| `preferred_min_throughput` | number \| object | – | Preferred minimum throughput in tokens/sec. Number (applies to p50) or object with percentile cutoffs. |
| `preferred_max_latency` | number \| object | – | Preferred maximum latency in seconds. Number (applies to p50) or object with percentile cutoffs. |
### How Percentiles Work [#how-percentiles-work]
AnyRouter tracks latency and throughput for each model and provider using percentile statistics calculated over a rolling 5-minute window:
* **p50** (median): 50% of requests perform better than this value
* **p75**: 75% of requests perform better than this value
* **p90**: 90% of requests perform better than this value
* **p99**: 99% of requests perform better than this value
Higher percentiles (p90, p99) give more confidence about worst-case performance; lower percentiles (p50) reflect typical performance. If a provider has a p90 latency of 2 seconds, 90% of its requests complete in under 2 seconds.
When you specify multiple percentile cutoffs, all of them must be met for a provider to land in the preferred group.
### When to Use Percentile Preferences [#when-to-use-percentile-preferences]
* **Real-time applications**: use p90 or p99 latency thresholds for consistent user-facing response times.
* **Batch processing**: use p50 throughput thresholds when average performance matters more than worst-case.
* **SLA compliance**: use multiple percentile cutoffs to enforce performance tiers.
* **Cost optimization**: combine with `sort: "price"` to get the cheapest provider that still meets your performance bar.
### Use Case 2: Find the Cheapest Model Meeting Performance Requirements [#use-case-2-find-the-cheapest-model-meeting-performance-requirements]
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"models": [
"anthropic/claude-sonnet-4-6",
"openai/gpt-5.4-mini",
"google/gemini-3.1-flash"
],
"messages": [{"role": "user", "content": "Hello"}],
"provider": {
"sort": { "by": "price", "partition": "none" },
"preferred_min_throughput": { "p90": 50 }
}
}'
```
AnyRouter picks the cheapest model+provider across the three options that has at least 50 tokens/second throughput at the p90 level. Endpoints below this threshold are still available as fallbacks if all preferred options fail.
You can also use `preferred_max_latency`:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"models": ["anthropic/claude-sonnet-4-6", "openai/gpt-5.4-mini"],
"messages": [{"role": "user", "content": "Hello"}],
"provider": {
"sort": { "by": "price", "partition": "none" },
"preferred_max_latency": { "p90": 3 }
}
}'
```
### Example: Multiple Percentile Cutoffs [#example-multiple-percentile-cutoffs]
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek/deepseek-v3.2",
"messages": [{"role": "user", "content": "Hello"}],
"provider": {
"preferred_max_latency": { "p50": 1, "p90": 3, "p99": 5 },
"preferred_min_throughput": { "p50": 100, "p90": 50 }
}
}'
```
### Use Case 3: Maximize BYOK Usage Across Models [#use-case-3-maximize-byok-usage-across-models]
If you use [Bring Your Own Key](/features/byok) and want to maximize use of your own provider keys, `partition: "none"` helps. When your primary model has no BYOK provider configured, AnyRouter can route to a fallback model that does.
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"models": [
"anthropic/claude-sonnet-4-6",
"openai/gpt-5.4-mini",
"google/gemini-3.1-flash"
],
"messages": [{"role": "user", "content": "Hello"}],
"provider": { "sort": { "by": "price", "partition": "none" } }
}'
```
If you have a BYOK key configured for OpenAI but not for Anthropic, AnyRouter can route to GPT using your own key even though Claude is listed first. Without `partition: "none"`, the router always tries Claude's endpoints first before falling back.
> BYOK endpoints are automatically prioritized when you have API keys configured for that provider. The `partition: "none"` setting allows this prioritization to work across model boundaries.
## Ordering Specific Providers [#ordering-specific-providers]
Set the providers AnyRouter will prioritize using the `order` field.
| Field | Type | Default | Description |
| ------- | --------- | ------- | ------------------------------------------------------------------------ |
| `order` | string\[] | – | List of provider slugs to try in order (e.g. `["anthropic", "openai"]`). |
If you don't set this field, the router [load balances](#price-based-load-balancing-default-strategy) across the top providers to maximize uptime. With `order` set, AnyRouter tries the listed providers one at a time and falls back to other providers if none are operational. If you don't want any other providers, [disable fallbacks](#disabling-fallbacks).
### Example: Specifying providers with fallbacks [#example-specifying-providers-with-fallbacks]
This example skips OpenAI (which doesn't host Mixtral), tries Together, and then falls back to the normal provider list:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "mistralai/mixtral-8x7b-instruct",
"messages": [{"role": "user", "content": "Hello"}],
"provider": { "order": ["openai", "together"] }
}'
```
### Example: Specifying providers with fallbacks disabled [#example-specifying-providers-with-fallbacks-disabled]
With `allow_fallbacks: false`, the request fails if neither listed provider succeeds:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "mistralai/mixtral-8x7b-instruct",
"messages": [{"role": "user", "content": "Hello"}],
"provider": {
"order": ["openai", "together"],
"allow_fallbacks": false
}
}'
```
## Targeting Specific Provider Endpoints [#targeting-specific-provider-endpoints]
Each provider on AnyRouter may host multiple endpoints for the same model — a default endpoint and a specialized "turbo" endpoint, or region-specific endpoints like `google-vertex/us-east5`. Copy the exact provider slug from the provider list on the model detail page to target a specific endpoint.
### Base Slug Matching [#base-slug-matching]
When you use a base provider slug (e.g. `"google-vertex"`) in any provider routing field (`order`, `only`, or `ignore`), it matches **all** endpoints for that provider, including variants and regions. For example, `"google-vertex"` matches `google-vertex`, `google-vertex/us-east5`, `google-vertex/us-central1`, and so on.
To target a **specific** variant or region, use the full slug including the suffix (e.g. `"google-vertex/us-east5"` or `"deepinfra/turbo"`).
| Slug in request | What it matches |
| -------------------------- | ------------------------------------------ |
| `"google-vertex"` | All Google Vertex endpoints (every region) |
| `"google-vertex/us-east5"` | Only the `us-east5` region endpoint |
| `"deepinfra"` | All DeepInfra endpoints (default + turbo) |
| `"deepinfra/turbo"` | Only the DeepInfra turbo endpoint |
### Example: Targeting a specific endpoint variant [#example-targeting-a-specific-endpoint-variant]
DeepInfra offers DeepSeek R1 through multiple endpoints — a default endpoint (`deepinfra`) and a turbo endpoint (`deepinfra/turbo`). Pin to the turbo endpoint:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek/deepseek-r1",
"messages": [{"role": "user", "content": "Hello"}],
"provider": {
"order": ["deepinfra/turbo"],
"allow_fallbacks": false
}
}'
```
This is especially useful when you want to consistently use a specific variant of a model from a particular provider.
> To route to **all** endpoints of a provider (across all regions and variants), use the base slug without a suffix. For example, `"google-vertex"` routes across all Vertex AI regions.
## Requiring Providers to Support All Parameters [#requiring-providers-to-support-all-parameters]
Restrict requests only to providers that support all parameters in your request using `require_parameters`.
| Field | Type | Default | Description |
| -------------------- | ------- | ------- | --------------------------------------------------------------- |
| `require_parameters` | boolean | `false` | Only use providers that support all parameters in your request. |
With the default routing strategy, providers that don't support every [LLM parameter](/api-reference/chat-completions) in your request can still receive the request but will ignore unknown parameters. With `require_parameters: true`, such providers are skipped entirely.
### Example: Excluding providers that don't support JSON formatting [#example-excluding-providers-that-dont-support-json-formatting]
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-mini",
"messages": [{"role": "user", "content": "Hello"}],
"provider": { "require_parameters": true },
"response_format": { "type": "json_object" }
}'
```
## Requiring Tool Calling and Context Size [#requiring-tool-calling-and-context-size]
Use these fields when `anyrouter/auto` (or another auto-routing model) should only pick members that can call tools or that have a large context window. The same fields persist on [presets](/features/presets).
| Field | Type | Description |
| ---------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `require_params` | string\[] | Hard requirements. `["tools"]` keeps only models that advertise tool calling, `["response_format"]` only models whose upstream advertises JSON mode. Distinct from boolean `require_parameters`. |
| `min_context` | number | Minimum context window in tokens. `1_000_000` keeps only members with at least 1M context. |
A non-empty `tools` array on the request also requires tool-calling members, even if you omit `require_params`.
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "anyrouter/auto",
"messages": [{"role": "user", "content": "Plan the next step"}],
"provider": {
"sort": "exacto",
"min_context": 1000000,
"require_params": ["tools"]
}
}'
```
Save the same object on a preset and call `@presets/` — `anyrouter/auto` honors those floors on every request.
## Requiring Providers to Comply with Data Policies [#requiring-providers-to-comply-with-data-policies]
Restrict requests to providers that comply with your data policies using `data_collection`.
| Field | Type | Default | Description |
| ----------------- | ----------------- | ------- | ----------------------------------------------------- |
| `data_collection` | "allow" \| "deny" | "allow" | Control whether to use providers that may store data. |
* `allow` (default): allow providers which may store user data non-transiently and may train on it.
* `deny`: use only providers which do not collect user data.
Some model providers may log prompts. AnyRouter flags these with a **Data Policy** tag on each model's provider list. This is not a definitive source for third-party data policies — it represents our best knowledge.
### Example: Excluding providers that don't comply with data policies [#example-excluding-providers-that-dont-comply-with-data-policies]
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-mini",
"messages": [{"role": "user", "content": "Hello"}],
"provider": { "data_collection": "deny" }
}'
```
## Zero Data Retention Enforcement [#zero-data-retention-enforcement]
Enforce Zero Data Retention (ZDR) on a per-request basis using `zdr`, ensuring your request routes only to endpoints that do not retain prompts.
| Field | Type | Default | Description |
| ----- | ------- | ------- | --------------------------------------- |
| `zdr` | boolean | – | Restrict routing to only ZDR endpoints. |
When `zdr` is `true`, the request only routes to endpoints with a Zero Data Retention policy. When `false` or unset, it has no effect.
### Example: Enforcing ZDR for a specific request [#example-enforcing-zdr-for-a-specific-request]
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4",
"messages": [{"role": "user", "content": "Hello"}],
"provider": { "zdr": true }
}'
```
This is useful when you don't want to globally enforce ZDR but need to ensure specific requests only route to ZDR endpoints.
## Distillable Text Enforcement [#distillable-text-enforcement]
Restrict requests to models where the author has allowed text distillation, using `enforce_distillable_text`.
| Field | Type | Default | Description |
| -------------------------- | ------- | ------- | ------------------------------------------------------------- |
| `enforce_distillable_text` | boolean | – | Restrict routing to only models that allow text distillation. |
Useful for applications that need to ensure their requests only use models where output can be used for training purposes, such as when building datasets for fine-tuning or distillation workflows.
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/llama-3.3-70b-instruct",
"messages": [{"role": "user", "content": "Hello"}],
"provider": { "enforce_distillable_text": true }
}'
```
## Disabling Fallbacks [#disabling-fallbacks]
To guarantee your request is served only by the top (lowest-cost) provider, disable fallbacks. Often combined with [`order`](#ordering-specific-providers) to restrict the candidate list to your chosen providers.
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-mini",
"messages": [{"role": "user", "content": "Hello"}],
"provider": { "allow_fallbacks": false }
}'
```
## Allowing Only Specific Providers [#allowing-only-specific-providers]
Allow only specific providers for a request by setting `only`.
| Field | Type | Default | Description |
| ------ | --------- | ------- | ------------------------------------------------- |
| `only` | string\[] | – | List of provider slugs to allow for this request. |
> Allowing only some providers significantly reduces fallback options and limits request recovery.
### Example: Allowing Azure for a GPT request [#example-allowing-azure-for-a-gpt-request]
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-mini",
"messages": [{"role": "user", "content": "Hello"}],
"provider": { "only": ["azure"] }
}'
```
## Ignoring Providers [#ignoring-providers]
Skip specific providers for a request by setting `ignore`.
| Field | Type | Default | Description |
| -------- | --------- | ------- | ------------------------------------------------ |
| `ignore` | string\[] | – | List of provider slugs to skip for this request. |
> Ignoring multiple providers significantly reduces fallback options and limits request recovery.
### Example: Ignoring DeepInfra for a Llama request [#example-ignoring-deepinfra-for-a-llama-request]
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/llama-3.3-70b-instruct",
"messages": [{"role": "user", "content": "Hello"}],
"provider": { "ignore": ["deepinfra"] }
}'
```
## Quantization [#quantization]
Quantization reduces model size and computational requirements while aiming to preserve quality. Most LLMs today use FP16 or BF16 for training and inference, cutting memory requirements in half compared to FP32. Some deployments use FP8 or integer quantization to reduce size further (e.g., INT8, INT4).
| Field | Type | Default | Description |
| --------------- | --------- | ------- | ------------------------------------------------------------------- |
| `quantizations` | string\[] | – | List of quantization levels to filter by (e.g. `["int4", "int8"]`). |
> Quantized models may exhibit degraded performance for certain prompts depending on the method used.
### Quantization Levels [#quantization-levels]
By default, requests are load-balanced across all available providers, ordered by price. To filter by quantization level, specify `quantizations` with one or more of:
* `int4`: Integer (4 bit)
* `int8`: Integer (8 bit)
* `fp4`: Floating point (4 bit)
* `fp6`: Floating point (6 bit)
* `fp8`: Floating point (8 bit)
* `fp16`: Floating point (16 bit)
* `bf16`: Brain floating point (16 bit)
* `fp32`: Floating point (32 bit)
* `unknown`: Unknown
### Example: Requesting FP8 quantization [#example-requesting-fp8-quantization]
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "meta/llama-3.1-8b-instruct",
"messages": [{"role": "user", "content": "Hello"}],
"provider": { "quantizations": ["fp8"] }
}'
```
## Maximum Price [#maximum-price]
To filter providers by price, specify `max_price` with the highest provider pricing you will accept.
For example, `{"prompt": 1, "completion": 2}` routes only to providers with prompt token pricing ≤ $1/M and completion token pricing ≤ $2/M.
Some providers support per-request pricing — use the `request` field. `image` is also available for the max price per image input.
Practically, this is often combined with `sort` to express "use the provider with the highest throughput, as long as it doesn't cost more than $X/M tokens":
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/llama-3.3-70b-instruct",
"messages": [{"role": "user", "content": "Hello"}],
"provider": {
"sort": "throughput",
"max_price": { "prompt": 1, "completion": 2 }
}
}'
```
Unlike `preferred_min_throughput` and `preferred_max_latency`, `max_price` is a hard filter — if no provider matches, the request fails with a `404` rather than falling back to a more expensive provider.
## Provider-Specific Headers [#provider-specific-headers]
Some upstreams support beta features that can be enabled through special headers. AnyRouter passes through a curated set of provider beta headers when present.
### Anthropic Beta Features [#anthropic-beta-features]
When routing to Anthropic-backed models (Claude), you can request specific beta features with the `anthropic-beta` header. AnyRouter forwards supported values.
#### Supported Beta Features [#supported-beta-features]
| Feature | Header Value | Description |
| --------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| Fine-Grained Tool Streaming | `fine-grained-tool-streaming-2025-05-14` | More granular streaming events during tool calls, with real-time updates as tool arguments are generated. |
| Interleaved Thinking | `interleaved-thinking-2025-05-14` | Allows Claude's thinking/reasoning to be interleaved with regular output rather than appearing as a single block. |
| Structured Outputs | `structured-outputs-2025-11-13` | Enables strict tool use, validating tool parameters against your schema to ensure correctly-typed arguments. |
> AnyRouter manages some Anthropic beta features automatically:
>
> * **Prompt caching and extended context** are enabled based on model capabilities.
> * **Structured outputs for JSON schema response format** (`response_format.type: "json_schema"`) — the header is applied automatically.
>
> For **strict tool use** (`strict: true` on tools), you must pass `anthropic-beta: structured-outputs-2025-11-13` explicitly. Without it, AnyRouter strips the `strict` field and routes normally.
#### Example: Enabling Fine-Grained Tool Streaming [#example-enabling-fine-grained-tool-streaming]
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-H "anthropic-beta: fine-grained-tool-streaming-2025-05-14" \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"messages": [{"role": "user", "content": "What is the weather in Tokyo?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a location",
"parameters": {
"type": "object",
"properties": { "location": { "type": "string" } },
"required": ["location"]
}
}
}],
"stream": true
}'
```
#### Example: Enabling Interleaved Thinking [#example-enabling-interleaved-thinking]
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer " \
-H "Content-Type: application/json" \
-H "anthropic-beta: interleaved-thinking-2025-05-14" \
-d '{
"model": "anthropic/claude-sonnet-4-6",
"messages": [{"role": "user", "content": "Solve this step by step: What is 15% of 240?"}],
"stream": true
}'
```
#### Combining Multiple Beta Features [#combining-multiple-beta-features]
Separate values with commas:
```text
anthropic-beta: fine-grained-tool-streaming-2025-05-14,interleaved-thinking-2025-05-14
```
> Beta features are experimental and may change or be deprecated by Anthropic. Check Anthropic's documentation for the latest list of available beta features.
## Terms of Service [#terms-of-service]
You can view the terms of service for each upstream provider on the [Providers](/features/models-catalog) page. You may not violate the terms of service or policies of third-party providers that power the models on AnyRouter.
## Verify [#verify]
Send a request with a `provider` block and confirm it is honored — for example, pin a single provider with fallbacks off:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-mini",
"messages": [{"role": "user", "content": "Hello"}],
"provider": { "only": ["openai"], "allow_fallbacks": false }
}'
```
If the pinned provider is unavailable and fallbacks are off, the request fails loudly — that is the expected, non-silent behavior.
## Troubleshooting [#troubleshooting]
:::details{summary="My request fails with a 404 when I set max_price"}
`max_price` is a hard filter. If no upstream is within your ceiling, the request fails with a `404` rather than routing to a more expensive provider. Raise the cap or remove it.
:::
:::details{summary="A performance threshold isn't excluding slow providers"}
`preferred_min_throughput` and `preferred_max_latency` are preferences, not hard filters — providers that miss them are deprioritized to the fallback pool, not excluded. Use `only`/`ignore` to hard-exclude a provider.
:::
:::details{summary="My provider slug matches more endpoints than expected"}
A base slug like `"google-vertex"` matches every region and variant. Use the full slug with its suffix (`"google-vertex/us-east5"`, `"deepinfra/turbo"`) to target one endpoint.
:::
---
# Publish an Explain figure (/guides/publish-explain-figure)
[Explain](https://anyrouter.dev/apps/explain) is a collection of interactive isometric figures, each one a real machine that shows how an idea works. You can make a figure in your own coding agent, check it, and upload it.
## Write the figure [#write-the-figure]
Give your agent the prompt at `https://anyrouter.dev/apps/explain/prompt.md`. It describes the format and includes an example. The figure is one JSON object that matches the JSON Schema at `https://anyrouter.dev/apps/explain/schema.json`.
A figure is refused if it breaks one of these rules:
* Build the real machine from at least 12 parts (a few slabs is unfinished work).
* Use the tone hierarchy: a "lo" base and at least one "hi" part for the reader to watch.
* Walk the machine through 3 to 6 steps.
* Each step lights 1 to 4 parts.
* The readout changes from step to step, like a live instrument.
* Every step has its own title.
* Plain text only: no HTML, entities, markdown fences or script URLs.
* No control, zero-width or bidi characters.
* No URLs or domains in the figure's text.
* No placeholder text (TODO, lorem ipsum, Part 1, ...).
* Short lines fit the figure on a phone: hint \<= 40, readout \<= 32, step title \<= 48, part label \<= 28 characters.
* Every part is big enough to see (at least 10px a side at the figure's size).
* The figure fits its frame, assembled and pulled apart.
## Check and upload with curl [#check-and-upload-with-curl]
You need an AnyRouter API key (`sk-ar-v1-...`) from [your dashboard](https://anyrouter.dev/dashboard/keys). Save the request body as `body.json`. It wraps the figure:
```json
{ "payload": { "title": "..." }, "visibility": "unlisted" }
```
Check without saving:
```sh
curl -sS "https://anyrouter.dev/api/v1/apps/explain/submissions?validate_only=1" \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
--data @body.json
```
A valid figure returns `{"valid": true}`. Then upload it:
```sh
curl -sS "https://anyrouter.dev/api/v1/apps/explain/submissions" \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
--data @body.json
```
The reply includes the figure's `url`.
## When a figure is refused [#when-a-figure-is-refused]
A refused figure returns HTTP 422. `error.metadata.issues` lists each problem with where it is, what is wrong and how to fix it:
```json
{
"error": {
"metadata": {
"issues": [
{
"path": "steps",
"code": "step_count",
"message": "2 steps; use 3 to 6",
"fix": "Merge or split steps so there are 3 to 6."
}
]
}
}
}
```
Fix each issue and check again.
## Use the anyr CLI [#use-the-anyr-cli]
The file holds the figure itself, not the wrapper.
```sh
anyr publish explain figure.json --dry-run
anyr publish explain figure.json
```
## Upload a figure built with the anatomy skill [#upload-a-figure-built-with-the-anatomy-skill]
If you built a figure with the `/anatomy` skill, upload the page it wrote instead of a JSON figure. That is the standalone HTML page from `node build.mjs`, plus its `--light` build if you have one. You can also upload a single SVG written with `figureSvg({ standalone: "dark" })`. Each file may be up to 2 MB. On the Explain page, open **Make one locally**, then **Upload an /anatomy figure**. Pick the file, check the preview, and upload it. Or send it through the same endpoint:
```sh
jq -n --rawfile c figure.html --rawfile l figure-light.html \
'{payload: {format: "anatomy-html", content: $c, content_light: $l}, visibility: "unlisted"}' > body.json
curl -sS "https://anyrouter.dev/api/v1/apps/explain/submissions?validate_only=1" \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
--data @body.json
```
Use `"format": "anatomy-svg"` for an SVG. Drop `?validate_only=1` to upload.
The server checks that the file is a figure from the anatomy kit. A page needs its figure card, plate, drawing (`` with a `viewBox`), title and hint. An SVG needs the `iso-svg iso` classes, a `viewBox` and an `aria-label` title. Everything must be inside the file. A figure with a `
```
The user lands on the AnyRouter consent screen, sees your app's name and the exact permissions requested, and approves.
:::
:::step{title="Exchange the code on your callback page"}
After approval, the user returns to your `redirect_uri` with `?code=…&state=…`. Verify the state, then exchange the code:
```html
```
:::
:::step{title="Run AI requests as the user"}
Use the token like any AnyRouter API key. The request is billed to the signed-in user's account.
```js
const res = await fetch("https://anyrouter.dev/api/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "google/gemini-3.5-flash",
messages: [{ role: "user", content: "Hello from my app!" }],
}),
})
```
:::
:::step{title="Show who's signed in"}
Fetch the user's basic profile to render a "Signed in as …" badge:
```js
const me = await fetch("https://anyrouter.dev/api/v1/me", {
headers: { Authorization: `Bearer ${token}` },
}).then((r) => r.json())
// display name, avatar, plan
```
:::
::::
## Permissions [#permissions]
A sign-in token carries exactly two scopes — nothing more:
| Scope | What it allows |
| -------------- | -------------------------------------------------------------------------------------------------------------- |
| `inference` | `/api/v1/chat/completions`, `/api/v1/messages`, `/api/v1/responses`, `/api/v1/embeddings` — billed to the user |
| `read:profile` | `GET /api/v1/me` — display name, avatar, plan |
What it can **not** do: create or revoke API keys, read usage or billing data, change account settings, or touch anything else in the user's account. There is no management access of any kind, and requesting extra scopes has no effect — the token is always limited to the two above.
## Token lifetime and renewal [#token-lifetime-and-renewal]
Sign-in tokens are temporary — 30 days by default. When a token expires, API calls return `401`. There are no refresh tokens: just re-run the sign-in flow, and the user (if still signed in to AnyRouter) is bounced through consent and back with a fresh token in seconds.
Treat every `401` as "token expired, re-authenticate" — restart the authorize flow rather than showing an error.
## Built-in MCP tools [#built-in-mcp-tools]
The same token also works against AnyRouter's MCP server at `https://mcp.anyrouter.dev/api/v1/mcp`. Your app gets:
* **Core tools** — model catalog, credits, and other read-only helpers.
* **Always-ready web tools** — currently the Firecrawl tools (search, scrape, crawl), available when the user has Firecrawl connected in their AnyRouter dashboard.
Sign-in tokens are deliberately limited: management tools (key administration, admin operations) and the user's other MCP connections are not exposed to your app.
## Revocation [#revocation]
Users stay in control: they can revoke your app's access at any time from **Dashboard → Connected apps** in their AnyRouter account. Revocation invalidates every token issued to your app for that user — subsequent requests return `401`, and your app can simply offer sign-in again.
## Best practices [#best-practices]
* **Store one token per user.** Each token bills its owner's account. Never share a token across users or fall back to a shared key.
* **Handle `401` by re-authenticating.** Expiry and revocation both surface as `401` — restart the sign-in flow instead of retrying.
* **Keep the `state` check.** It protects your users from login CSRF; the snippet above already does it.
* **Use exact redirect URIs.** The URI in the authorize request and token exchange must match a registered one exactly, including path and scheme.
* **Don't log tokens.** Treat `sk-ar-v1-…` values like passwords in your logs and error reporting.
## Related [#related]
* [Chat Completions API](/api-reference/chat-completions) — the full request schema
* [Errors](/guides/errors) — status codes and fixes
* [MCP server](/mcp) — what the MCP endpoint offers
* [App attribution](/features/app-attribution) — show up in app rankings
---
# Streaming (/guides/streaming)
Set `stream: true` and AnyRouter returns a stream of [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events) (SSE) instead of one buffered reply. Every Chat Completions and Messages request supports it. This is the fastest way to make long generations feel responsive.
## Why stream? [#why-stream]
* **Lower perceived latency** — render the first token the moment the model emits it, instead of waiting for the full response.
* **Long outputs feel responsive** — users see progress on multi-second generations.
* **Early cancellation** — abort mid-flight if the user changes their mind, and pay only for tokens already emitted.
## Before you start [#before-you-start]
* An AnyRouter API key (prefix `sk-ar-`). Create one in the [dashboard](https://anyrouter.dev/dashboard/keys).
## Make a streaming request [#make-a-streaming-request]
Set `"stream": true` in the request body. Pick your client below.
```bash
curl -N https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4-mini",
"stream": true,
"messages": [{"role": "user", "content": "Count to 10"}]
}'
```
The `-N` flag disables curl's output buffering so chunks appear as they arrive.
```typescript
const stream = await client.chat.completions.create({
model: "openai/gpt-5.4-mini",
messages: [{ role: "user", content: "Count to 10" }],
stream: true,
})
for await (const chunk of stream) {
const token = chunk.choices[0]?.delta?.content ?? ""
process.stdout.write(token)
}
```
The official OpenAI SDK parses the SSE stream for you.
```python
stream = client.chat.completions.create(
model="openai/gpt-5.4-mini",
messages=[{"role": "user", "content": "Count to 10"}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="", flush=True)
```
## Read the event stream [#read-the-event-stream]
Each line is an SSE `data:` event carrying a JSON payload:
```text
data: {"id":"chatcmpl-abc","choices":[{"delta":{"content":"Hel"}}]}
data: {"id":"chatcmpl-abc","choices":[{"delta":{"content":"lo"}}]}
data: {"id":"chatcmpl-abc","choices":[{"delta":{"content":"!"},"finish_reason":"stop"}]}
data: [DONE]
```
* `delta.content` — the token(s) added in this event.
* `delta.role` — set only on the first event.
* `finish_reason` — set on the final event.
* `[DONE]` — sentinel. No more events follow.
## Cancellation [#cancellation]
Close the HTTP connection to cancel a stream. AnyRouter propagates the abort to the upstream provider, so you only pay for tokens emitted up to the cancel point.
Wire cancellation into your UI: when the user navigates away or hits a stop button, call `controller.abort()` on your `AbortController`. AnyRouter bills only for tokens streamed before the abort.
## Verify [#verify]
You are streaming correctly when tokens print incrementally rather than appearing all at once, and the final event carries `finish_reason` followed by `data: [DONE]`.
---
# Unblock free-model rate limits with your own OpenRouter key (/guides/unblock-free-model-rate-limits-openrouter-key)
Free models are popular, so they run out. If `anyrouter/free` or a `:free` model keeps returning `429`, the fastest fix is to add your own OpenRouter key. Your free-model requests then run on your own OpenRouter account first,.
## Why free models return 429 [#why-free-models-return-429]
Free models can return `429` for two reasons, and they need different fixes:
| What you see | Cause | Fix |
| -------------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------- |
| `429` `rate_limit_exceeded` | Your key sent more than 2400 requests in a minute. | Wait `Retry-After` seconds and slow the loop. |
| `429` from the upstream provider | The provider's free capacity is busy. Free capacity is shared by many callers, so it runs out at peak times. | Add your own key so the request runs on your own provider quota. |
Free models have no daily cap on AnyRouter. Requests served through your own BYOK key run on your own provider quota, even for `:free` models. See [Free Tier](/features/free-tier) and [Rate Limits](/features/rate-limits) for the full details.
## Add your OpenRouter key [#add-your-openrouter-key]
::::steps
:::step{title="Create an OpenRouter key"}
Sign in at [openrouter.ai](https://openrouter.ai), open **[openrouter.ai/keys](https://openrouter.ai/keys)**, and click **Create Key**. Copy it right away. OpenRouter keys start with `sk-or-`.
:::
:::step{title="Add it to AnyRouter"}
Open **[Dashboard → BYOK](https://anyrouter.dev/dashboard/byok)**. Find **OpenRouter** under **Available**, click **Add key**, paste the key into the **API key** field, and click **Save**.
:::
:::step{title="Wait for the live check"}
AnyRouter calls OpenRouter once before it stores the key. If the key is wrong, the dialog shows OpenRouter's error and nothing is saved. Once saved, the key is encrypted at rest and never shown again.
:::
::::
You don't need to set a base URL. The default (`https://openrouter.ai/api/v1`) is filled in for you.
## How routing uses your key [#how-routing-uses-your-key]
When a request needs a model your OpenRouter key can serve, AnyRouter tries **your key first**, before the shared pool and before your AnyRouter credits. If your key is rate limited or fails, AnyRouter sets it aside for a short time and moves on to the next option.
Two settings change this:
* **Always use.** Turn this on for the key and AnyRouter uses only your key for matching models. It never falls back to the shared pool or to credits. If your key fails, the request fails.
* **More than one key.** Add several OpenRouter keys and pick a strategy (Fallback, Round Robin, Weighted, or Random) on the OpenRouter card.
The full order is on [How AnyRouter Chooses a Credential](/features/credential-selection).
## What it costs [#what-it-costs]
* **AnyRouter charges nothing** for requests served by your key. No markup, no per-request fee, and no credits are deducted.
* **`:free` models stay free.** They run under OpenRouter's own limits for free models on your account.
* **Paid models bill your OpenRouter account** at OpenRouter's price. If you don't want that, call only `:free` models or set a spending limit on the key in OpenRouter.
## Check that it works [#check-that-it-works]
Call a `:free` model with your AnyRouter key:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer $ANYROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "nvidia/nemotron-nano-9b-v2:free",
"messages": [{ "role": "user", "content": "Say hello in one sentence." }]
}'
```
Then open **[Dashboard → Logs](https://anyrouter.dev/dashboard/logs)**. The newest row's **Provider** column shows which upstream served the request, and **Cost** shows $0. The set of free models changes over time, so pick any current `:free` model from the [model catalog](https://anyrouter.dev/models) if this one isn't listed.
## Donate your key to the shared pool [#donate-your-key-to-the-shared-pool]
If your OpenRouter key has spare capacity, you can share it. On the key in **Dashboard → BYOK**, turn on **Donate to pool**. Other users' requests can then run on your key, and you earn credits for each request it serves. Your own requests always come first, and you can turn it off at any time.
On the Free plan, donating an eligible key also unlocks the shared pool's free models for you, the same as subscribing to Go. Check that OpenRouter's terms allow sharing your key before you donate. See [Share a Key to the Shared Pool](/features/shared-pool) for rewards and limits.
## Troubleshooting [#troubleshooting]
:::details{summary="The key won't save"}
The live check failed. Check that you copied the whole key, including the `sk-or-` prefix, and that the key isn't disabled in OpenRouter. The dialog shows OpenRouter's exact error.
:::
:::details{summary="I get a 429 from OpenRouter"}
Your own OpenRouter free-model limit is used up. Wait for OpenRouter's limit to reset, add a second key, or add a key from another provider with a free tier. See [Free capacity with your own keys](/guides/free-capacity-with-your-own-keys).
:::
:::details{summary="401 upstream_401"}
OpenRouter rejected the key. It may be revoked or deleted. Create a new key in OpenRouter, add it in **Dashboard → BYOK**, and delete the old one.
:::
:::details{summary="I was charged on OpenRouter"}
You called a paid model, which bills your OpenRouter account. Use `:free` model ids only, or set a credit limit on the key in OpenRouter.
:::
---
# Vercel AI SDK (/guides/vercel-ai-sdk)
Build AI features in JavaScript and TypeScript with the [Vercel AI SDK](https://sdk.vercel.ai/docs), routed through AnyRouter. Because AnyRouter is OpenAI-compatible, you configure the `@ai-sdk/openai` provider with a new base URL and key — everything else stays the same.
## Before you start [#before-you-start]
* A JavaScript or TypeScript project.
* An AnyRouter API key (prefix `sk-ar-`). Create one in the [dashboard](https://anyrouter.dev/dashboard/keys).
## Set it up [#set-it-up]
::::steps
:::step{title="Install the SDK and OpenAI provider"}
```bash
npm install ai @ai-sdk/openai
```
Prefer `pnpm add ai @ai-sdk/openai` or `yarn add ai @ai-sdk/openai` if that matches your project.
:::
:::step{title="Point the provider at AnyRouter"}
Create the provider with `createOpenAI`, set `baseURL` to `https://anyrouter.dev/api/v1`, and pass your key. Select models with `anyrouter(...)`.
```typescript
import { createOpenAI } from "@ai-sdk/openai"
import { generateText } from "ai"
const anyrouter = createOpenAI({
baseURL: "https://anyrouter.dev/api/v1",
apiKey: process.env.ANYROUTER_API_KEY, // prefixed sk-ar-
})
const { text, response } = await generateText({
model: anyrouter("anyrouter/free"),
prompt: "Write a two-line poem about routers.",
})
console.log(response.modelId) // the model that served the request
console.log(text)
```
:::
:::step{title="Stream responses (optional)"}
For real-time chat, swap `generateText` for `streamText` and iterate the token stream.
```typescript
import { createOpenAI } from "@ai-sdk/openai"
import { streamText } from "ai"
const anyrouter = createOpenAI({
baseURL: "https://anyrouter.dev/api/v1",
apiKey: process.env.ANYROUTER_API_KEY,
})
const result = streamText({
model: anyrouter("anyrouter/free"),
prompt: "Explain routing tables in two sentences.",
})
for await (const textPart of result.textStream) {
process.stdout.write(textPart)
}
```
:::
::::
## Verify [#verify]
Set your key, then run the first snippet as an ES module (for example `npx tsx index.ts`). It prints the model that served the request, then the completion.
```bash
export ANYROUTER_API_KEY="sk-ar-your-key-here"
```
Every request also shows up in [Logs](https://anyrouter.dev/dashboard/logs) with the model, provider, tokens, and latency.
`anyrouter("model-id")` uses the Responses API (`/api/v1/responses`), with streaming and non-streaming. `anyrouter.chat("model-id")` uses Chat Completions and works too.
## Free models [#free-models]
Set the model to `anyrouter/free` and AnyRouter routes the request to a free model that is healthy right now — no credits used. Free models have no daily cap on any plan. Details on [Free Tier](/features/free-tier).
Use `anyrouter/auto` to let AnyRouter pick from the full catalog, or any `provider/model` id from the [catalog](https://anyrouter.dev/models) to pin one model.
## Bring your own keys [#bring-your-own-keys]
Already pay for OpenAI, Anthropic, Google, or another provider? Add those keys to AnyRouter and your requests use them, with no credits deducted. See [Get a provider API key](/guides/byok-provider-keys) and [BYOK](/features/byok).
## Troubleshooting [#troubleshooting]
:::details{summary="401 Unauthorized"}
Ensure `apiKey` holds a key that starts with `sk-ar-`. Create and manage keys in the [dashboard](https://anyrouter.dev/dashboard/keys).
:::
:::details{summary="AI_APICallError: Invalid JSON response"}
Update to a current `ai` and `@ai-sdk/openai`. If it persists, switch to `anyrouter.chat("model-id")` and include the request id from the error when you contact support.
:::
:::details{summary="404 model not found"}
Pass a full catalog id to `anyrouter(...)` — `anyrouter/free`, `anyrouter/auto`, or a `provider/model` id such as `z-ai/glm-4.7-flash`. Valid ids live in the [catalog](https://anyrouter.dev/models).
:::
## Related [#related]
* [OpenAI SDK](/guides/openai-sdk) — the official client, Python and TypeScript
* [Streaming](/guides/streaming) — how server-sent events work
* [LangChain](/guides/langchain) — chains and agents
* [Chat Completions API](/api-reference/chat-completions) — the underlying endpoint
* [Models catalog](https://anyrouter.dev/models) — every model id you can pass
---
# Skills & Knowledge Hub (/hub)
Your coding agent is only as good as the context it carries. The Hub keeps that context — **skills, prompts, knowledge-base docs, and plugins** — in one place, shared across your machines, your team, and all your agents, and kept up to date automatically.
A **hub** is a named collection. Create one or many: a personal hub for your own setup, a workspace hub your whole team shares, or a public hub anyone can subscribe to.
## What a hub holds [#what-a-hub-holds]
| Kind | What it is |
| ------------- | ---------------------------------------------------------------------- |
| **Skill** | A `SKILL.md` (with optional bundled files) your agent loads on demand. |
| **Prompt** | A reusable prompt or system message. |
| **Knowledge** | A markdown doc your agent can read for project/domain context. |
| **Plugin** | A Claude Code plugin bundle. |
## Visibility levels [#visibility-levels]
| Visibility | Who can see it |
| ------------- | ----------------------------------------- |
| **Private** | Only you. |
| **Workspace** | Everyone in your account or organization. |
| **Public** | Anyone — discoverable and subscribable. |
## Create and fill a hub [#create-and-fill-a-hub]
In the [dashboard](https://anyrouter.dev/dashboard/hubs):
::::steps
:::step{title="Create a hub"}
**New hub** → name it and choose a visibility.
:::
:::step{title="Add items"}
Three ways to add:
* **Direct** — paste a skill, prompt, knowledge doc, or plugin.
* **From skills.sh** — import any public GitHub repo that has a `SKILL.md` at its root (paste `owner/repo` or a raw `SKILL.md` URL).
* **From your coding agent** — upload context you just produced with the MCP tool `hub_add_item`.
:::
::::
Every item is **scanned for prompt injection** before it can sync. Items that fail the scan are flagged and never written to a machine — you always see each item's verdict.
## Sync to your machine [#sync-to-your-machine]
The [`anyr` CLI](/cli) pulls your hubs into a project's `.claude/` tree and keeps them current — re-syncing only rewrites what changed and prunes what was removed.
```bash
# Pull every hub you can see (own + subscribed)
anyr skills sync
# Pull a single hub
anyr skills pull my-hub
# See what's available / what's installed
anyr skills list
anyr skills status
```
Each hub is downloaded to `~/.anyrouter/hubs//` as a self-contained Claude Code plugin — `skills/` for skills (and knowledge/prompts, wrapped so the agent can load them) and `plugins/` for plugin items. Your own `~/.claude/skills` is never touched.
### Load a hub straight into a session [#load-a-hub-straight-into-a-session]
```bash
anyr claude --hub my-hub
```
Run `anyr claude` on its own and it lists your hubs so you can pick one. Either way the CLI syncs the hub, prints exactly what landed (with each item's scan verdict), then launches Claude Code with `--plugin-dir` pointed at the hub — so its skills load (namespaced `anyrouter-my-hub:`) and your agent starts with that context already in place.
## Push from the command line [#push-from-the-command-line]
```bash
# Upload a local skill folder (needs a management key)
anyr skills push ./my-skill --hub my-hub
# Import from skills.sh / GitHub into a hub
anyr skills add owner/repo --hub my-hub
```
## Manage from inside your coding agent (MCP) [#manage-from-inside-your-coding-agent-mcp]
Connect the [AnyRouter MCP server](/mcp) and your agent gets hub tools:
* `list_hubs`, `get_hub`, `search_public_hubs` — browse.
* `create_hub` — start a new collection.
* `hub_add_item` — persist a knowledge doc or skill you just wrote, so your whole team and every agent can sync it.
* `hub_import_skill` — import a skill from skills.sh / GitHub.
## Security [#security]
Skills, prompts, and knowledge become instructions in your agent's context, and plugins can run. The Hub scans every item for prompt-injection and unsafe patterns (instruction overrides, secret exfiltration, destructive commands, and more) on upload and on every edit. High-risk items are **blocked from syncing**; lower-risk findings are surfaced so you can decide. Nothing is loaded silently — the CLI prints exactly what it wrote, from which hub, with each scan verdict.
## Related [#related]
* [CLI](/cli) — `anyr skills` syncs and loads hubs
* [MCP Server](/mcp) — the hub tools available inside your agent
* [Dashboard → Hubs](https://anyrouter.dev/dashboard/hubs) — create and manage your hubs
---
# AnyRouter MCP with Claude Code (/mcp/claude-code)
Claude Code (the CLI) registers MCP servers with `claude mcp add` — a single command — or by editing an `.mcp.json` config file directly.
1. Add the server with one command:
```bash
claude mcp add --transport http anyrouter https://anyrouter.dev/api/v1/mcp
```
What the arguments mean:
* `--transport http` — AnyRouter is a remote HTTP MCP server (not stdio).
* `anyrouter` — the local name; pick anything memorable.
* `https://anyrouter.dev/api/v1/mcp` — the endpoint URL.
To scope it to a single project, add `--scope project` (writes `.mcp.json` in the project instead of the user-level config).
2. Approve the OAuth consent. The next tool invocation opens your browser at `anyrouter.dev/oauth/consent`. Pick a permission level (Read-only is the default and safest) and click **Allow**. Claude Code receives the authorization code through its local callback and stores the token.
1. Open (or create) `.mcp.json` in your project root (or the user-level config for a global entry).
2. Add the AnyRouter server:
```json
{
"mcpServers": {
"anyrouter": {
"type": "http",
"url": "https://anyrouter.dev/api/v1/mcp"
}
}
}
```
3. Restart Claude Code. The next tool call opens `anyrouter.dev/oauth/consent` — pick a permission level and click **Allow**.
Permission level reference: [MCP API reference → Scopes and consent bundles](/api-reference/mcp#scopes-and-consent-bundles).
## Auto-connect with the anyr CLI [#auto-connect-with-the-anyr-cli]
`anyr claude` connects the AnyRouter MCP gateway for you when **Auto-connect in coding agents** is on in the [dashboard](https://anyrouter.dev/dashboard/mcp). It uses the same key the session launches with, so there is nothing to paste. To turn it off, pass `--no-mcp` on a launch, run `anyr mcp auto off`, or switch it off in the dashboard.
## Verify [#verify]
```bash
claude mcp list
```
You should see `anyrouter — Connected` (or similar). Trigger the connection inline:
```bash
claude "list my AnyRouter models that support vision"
```
## Revoke [#revoke]
```bash
claude mcp remove anyrouter
```
This deletes only the local registration. To revoke the OAuth token server-side, use the [MCP dashboard](https://anyrouter.dev/dashboard/mcp) — after that, any later tool call returns `401 Unauthorized` until you reconnect.
## Troubleshooting [#troubleshooting]
:::details{summary="localhost:NNNNN refused to connect after consent"}
The local OAuth callback listener was no longer running when the browser redirected back. Most often the `mcp-remote` (or Claude Code) child process exited between opening the consent page and clicking **Allow**. Retry in one continuous flow — open the prompt and complete consent quickly. If that doesn't help, clear stale auth state:
```bash
rm -rf ~/.mcp-auth
```
then re-run `claude mcp add` and trigger the next tool call.
:::
:::details{summary="anyrouter not appearing in claude mcp list"}
You probably added it with `--scope project` in a different directory. Check `cat .mcp.json` in the project and the user-level config.
:::
---
# AnyRouter MCP with Claude Desktop (/mcp/claude-desktop)
Claude Desktop ships a Connectors UI for adding remote MCP servers, so the whole flow happens in-app. If your build predates the Connectors UI, a manual `mcp-remote` bridge in `claude_desktop_config.json` gets you there too.
1. Open **Settings → Connectors → Add custom connector**.

Fill in the details:
* **Name**: `AnyRouter`
* **Remote MCP server URL**: `https://anyrouter.dev/api/v1/mcp`
Leave the OAuth Client ID and Secret empty — AnyRouter handles registration automatically (Dynamic Client Registration).
2. Approve the OAuth consent. After clicking **Add**, Claude Desktop opens a browser tab at `anyrouter.dev/oauth/consent`.

Pick a permission level — **Read-only** is the default and safest (it can view credits, recent usage, and provider-key status). **Standard** also lets the client mint and rotate API keys. Click **Allow**.
3. When the consent page closes, Claude Desktop reports the connector is connected.

If your build has no Connectors UI, bridge the remote server through `mcp-remote` in `claude_desktop_config.json` (Settings → Developer → Edit Config):
```json
{
"mcpServers": {
"anyrouter": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://anyrouter.dev/api/v1/mcp"]
}
}
}
```
Restart Claude Desktop. The first tool call opens `anyrouter.dev/oauth/consent` — pick a permission level (Read-only is the default and safest) and click **Allow**.
Read more about permission levels in the [MCP API reference](/api-reference/mcp#scopes-and-consent-bundles).
## Verify [#verify]
Ask Claude anything that needs your AnyRouter workspace. The first call triggers a tool invocation; later calls reuse the same authorized session.
**"What AnyRouter models are available?"** — Claude calls `list_models` and surfaces the catalog inline, handy for picking a model id without leaving the chat.

**"How many AnyRouter credits do I have left?"** — Claude calls `get_credits` and reports your workspace balance, lifetime usage, and free-tier grants.

Available tools depend on the permission level you granted at consent. See the [MCP overview](/mcp#what-can-the-client-do) for the full list.
## Revoke [#revoke]
Revoke any time from [your MCP dashboard](https://anyrouter.dev/dashboard/mcp). Re-adding the connector starts a fresh OAuth flow.
---
# AnyRouter MCP with Cline (/mcp/cline)
Cline is a VS Code agent extension with first-class MCP support — set it up from the panel UI or by editing `cline_mcp_settings.json`.
1. Open the **Cline** panel in VS Code.
2. Click the **MCP Servers** icon (the stacked-cube icon at the top of the panel).
3. Switch to the **Remote Servers** tab → **Add Remote Server**.
4. Fill in the details:
* **Server Name**: `anyrouter`
* **Server URL**: `https://anyrouter.dev/api/v1/mcp`
5. Click **Add Server**. The next tool call opens `anyrouter.dev/oauth/consent` — pick a permission level (Read-only is the default and safest) and click **Allow**.
1. In the same MCP Servers panel, click **Configure MCP Servers** to open `cline_mcp_settings.json`.
2. Add the AnyRouter server:
```json
{
"mcpServers": {
"anyrouter": {
"url": "https://anyrouter.dev/api/v1/mcp",
"type": "streamableHttp",
"disabled": false
}
}
}
```
3. Save. Cline picks up changes without a reload; the next tool call starts the OAuth flow.
## Verify [#verify]
In any Cline chat, ask **"list AnyRouter models that support vision"**. The agent calls `list_models` and returns the matching rows.
## Revoke [#revoke]
* **Cline side** — remove the entry from the MCP Servers panel, or set `"disabled": true` in `cline_mcp_settings.json`.
* **AnyRouter side** — revoke the OAuth token from the [MCP dashboard](https://anyrouter.dev/dashboard/mcp). Subsequent tool calls return `401 Unauthorized` until you reconnect.
## Related [#related]
* [MCP overview](/mcp) — what the server does and which tools are exposed
* [MCP API reference](/api-reference/mcp) — full tool schemas, scope tables, manual auth
---
# AnyRouter MCP with Cursor (/mcp/cursor)
Cursor exposes MCP through a Settings UI, and you can reach the same result by editing `~/.cursor/mcp.json` directly — pick whichever you prefer below.
1. Open **Cursor Settings** → **MCP & Integrations** → **Add Custom MCP**.
2. Fill in the details:
* **Name**: `anyrouter`
* **URL**: `https://anyrouter.dev/api/v1/mcp`
3. Click **Save**. Cursor opens your browser at `anyrouter.dev/oauth/consent` — pick a permission level (Read-only is the default and safest) and click **Allow**.
1. Open (or create) `~/.cursor/mcp.json`.
2. Add the AnyRouter server:
```json
{
"mcpServers": {
"anyrouter": {
"url": "https://anyrouter.dev/api/v1/mcp"
}
}
}
```
3. Reload Cursor. The next tool call triggers the OAuth flow at `anyrouter.dev/oauth/consent` — pick a permission level and click **Allow**.
## Verify [#verify]
In any Cursor chat, ask **"list my AnyRouter API keys"**. The agent calls `list_keys` and surfaces the result inline. The tools you can reach depend on the permission level you granted at consent.
## Revoke [#revoke]
Disconnecting has two layers:
* **Cursor side** — remove the entry from Settings → MCP & Integrations, or delete it from `~/.cursor/mcp.json`.
* **AnyRouter side** — revoke the OAuth token from the [MCP dashboard](https://anyrouter.dev/dashboard/mcp). This invalidates the connection immediately; any later tool call from Cursor returns `401 Unauthorized` until you reconnect.
---
# MCP Gateway — connect your tools once (/mcp/gateway)
Point any MCP client at one URL, get AnyRouter's native tools **plus** every connected third-party MCP server — with auth managed for you.
Every coding agent normally has to be wired up to each MCP server separately, and each server has its own login. **MCP Gateway flips that around:** connect a server **once** in the AnyRouter dashboard, and its tools show up at the AnyRouter MCP endpoint you already use — authenticated with the **same AnyRouter API key** you use for models.
One endpoint. One key. Every tool.
```
https://anyrouter.dev/api/v1/mcp
```
## How it works [#how-it-works]
1. **Connect a server.** In the dashboard, open **MCP Gateway**, click **Add server**, pick a server or paste its URL, and connect the way that server needs: sign in, paste an API key, or nothing at all.
2. **AnyRouter discovers its tools** and stores your credential **encrypted** — it never leaves AnyRouter and never reaches your MCP client.
3. **Use it anywhere.** Point any MCP client at the AnyRouter endpoint with your key. The connected server's tools appear alongside AnyRouter's own, **namespaced** so names never collide:
```
linear__create_issue
github__open_pr
```
4. **Call a tool** and AnyRouter proxies it to the right upstream server using the credential it stored for you, then streams the result back.
## Connect a server [#connect-a-server]
1. Go to **Dashboard → MCP Gateway**.
2. Click **Add server**.
3. Paste the **server URL** and click **Detect**. AnyRouter checks what the server needs and suggests a **name** (used as the namespace prefix, e.g. `linear`).
4. Connect the way the server asks. You can change the detected choice under **Advanced**:
* **None** — for servers that don't require a credential.
* **API key / token** — pasted once, stored encrypted, sent to the upstream as an `Authorization: Bearer …` header. **Test key** checks it before you connect.
* **OAuth** — click **Authorize**, approve on the server's own sign-in page, and you return to the dashboard.
5. AnyRouter discovers the server's tools. Pick which ones to expose.
The tools are available at your unified endpoint immediately — no per-client reconfiguration.
## Add from the catalog [#add-from-the-catalog]
You don't have to know a server's URL by heart. Open **Add server** and pick from the **catalog** of popular MCP servers — each one is pre-filled with the right URL and the right way to sign in:
* **Cloudflare** — manage Workers, KV, R2, D1, and observability.
* **Firecrawl** — crawl and extract content from any website.
* **GitHub** — open pull requests, search code, manage issues.
* **Linear** — create and update issues, search your workspace.
* **Notion** — read and write pages and databases.
Select a server and authenticate the way it expects:
* **OAuth** — click **Authorize**, approve on the provider's sign-in page, and you're done. No keys to copy. AnyRouter stores the tokens encrypted and refreshes them for you. A few providers do not offer this sign-in to third-party gateways yet; for those the dialog asks for a personal access token instead.
* **API key** — paste the provider's key once; it's stored encrypted and never reaches your client.
* **Nothing** — some servers are open and need no credential at all.
Once connected, the server's tools appear at your unified endpoint, namespaced by connection — exactly like a server you added by URL.
## Managed Access (teams) [#managed-access-teams]
For teams, connecting servers one person at a time doesn't scale. **Managed Access** lets an organization admin connect the company identity provider **once**, and every member gets the MCP Gateway automatically when they sign in with SSO — no manual keys, no per-person setup.
* **Admin connects the IdP once.** Point Managed Access at your organization's identity provider and approve it a single time.
* **Members just sign in.** When a member signs in through SSO, the gateway and its tools are ready for them immediately — there's nothing to copy or configure.
* **Access follows your directory.** When someone joins or leaves, their access to the gateway follows their SSO account.
> Managed Access is powered by the MCP Enterprise-Managed Authorization extension.
## Access log [#access-log]
Every tool call through the gateway is recorded in your dashboard, so you always know what ran and when. Each entry shows the connection, the tool, who called it, and the result — useful for debugging an agent's behavior and for keeping an audit trail of what your tools did on your behalf.
## Choose which tools to expose [#choose-which-tools-to-expose]
Open a connection to see its discovered tools. Toggle any tool off to hide it from the unified endpoint; toggle it back on at any time. Use **Test a tool** to invoke one directly from the dashboard and confirm the connection works.
## Manage connections [#manage-connections]
Each connection card shows its status:
* **Connected** — tools are live at the endpoint.
* **Needs re-auth** — the stored credential was rejected; use **Re-authorize** to update it.
* **Error** — the server couldn't be reached. AnyRouter keeps serving the last known tool list and retries; a repeatedly failing server is skipped for a short while so it can't slow down the rest.
From a connection's details you can **Re-sync** its tools, **Re-authorize**, or **Disconnect** it.
Use the switch next to a connected server to turn it off without disconnecting. While a server is off, its tools are not listed at your gateway endpoint and calls to them are refused, but its saved API key or sign-in and your tool selection are kept. Turn it back on at any time. **Disconnect** removes the server and its saved credential; you will have to enter the key or sign in again to reconnect.
## Security [#security]
* Upstream credentials are **encrypted at rest** and scoped to your workspace.
* Your MCP client only ever sees your **AnyRouter key** — never the upstream credential.
* Removing a connection deletes its stored credential.
## Use it [#use-it]
Point any MCP client at the endpoint exactly as in the [MCP overview](/mcp) — nothing changes in the client. Once a server is connected, its tools simply appear:
```bash
claude mcp add --transport http anyrouter https://anyrouter.dev/api/v1/mcp
```
Ask your agent to list its tools and you'll see both AnyRouter's native tools and your connected servers', namespaced by connection.
## Related [#related]
Set up the gateway in your client — the endpoint is the same for all of them:
* [Claude Code](/mcp/claude-code) — CLI, one `claude mcp add` command
* [Claude Desktop](/mcp/claude-desktop) — built-in Connectors UI
* [Cursor](/mcp/cursor) — Settings UI or `~/.cursor/mcp.json`
* [Windsurf](/mcp/windsurf) — Cascade panel or `mcp_config.json`
* [Cline](/mcp/cline) — VS Code MCP Servers panel
* [GitHub Copilot](/mcp/github-copilot) — Agent mode in VS Code
* [OpenCode](/mcp/opencode) — `opencode.json`
* [Other clients](/mcp/other-clients) — any MCP-aware client over remote HTTP
* [MCP API reference](/api-reference/mcp) — tool schemas, scopes, manual auth
---
# AnyRouter MCP with GitHub Copilot (/mcp/github-copilot)
GitHub Copilot Chat in VS Code supports remote MCP servers in **Agent mode**. Configure it in a workspace file or in user settings, and the AnyRouter tools appear alongside Copilot's built-in tools.
1. Create `.vscode/mcp.json` at the repo root:
```json
{
"servers": {
"anyrouter": {
"type": "http",
"url": "https://anyrouter.dev/api/v1/mcp"
}
}
}
```
2. Commit this file to share the config with your team — no secrets live in it (OAuth handles auth).
1. Open VS Code settings (`Cmd+,` / `Ctrl+,`) → search for **MCP** → **Edit in settings.json**.
2. Add the AnyRouter server:
```json
{
"github.copilot.chat.mcp.servers": {
"anyrouter": {
"type": "http",
"url": "https://anyrouter.dev/api/v1/mcp"
}
}
}
```
## Activate [#activate]
1. Open Copilot Chat and switch the mode dropdown to **Agent**.
2. Click the **Tools** icon and confirm `anyrouter` shows in the list.
3. On the first invocation, VS Code opens `anyrouter.dev/oauth/consent`. Pick a permission level (Read-only is the default and safest) and click **Allow**.
## Verify [#verify]
In Copilot Chat (Agent mode), ask **"how many AnyRouter credits do I have left?"**. Copilot picks the `get_credits` tool and returns the balance.
## Revoke [#revoke]
* **Copilot side** — remove the entry from `mcp.json` or `settings.json`.
* **AnyRouter side** — revoke the OAuth token from the [MCP dashboard](https://anyrouter.dev/dashboard/mcp). Subsequent tool calls return `401 Unauthorized` until you reconnect.
## Related [#related]
* [MCP overview](/mcp) — what the server does and which tools are exposed
* [MCP API reference](/api-reference/mcp) — full tool schemas, scope tables, manual auth
---
# AnyRouter MCP with OpenCode (/mcp/opencode)
OpenCode registers MCP servers from `opencode.json` — scope it to one project, or make AnyRouter available globally. This is the **tools** connection (catalog, credits, keys). To route OpenCode's **inference** through AnyRouter, use the [OpenCode guide](/guides/opencode) instead.
1. Add the AnyRouter server to `./opencode.json` at the repo root:
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"anyrouter": {
"type": "remote",
"url": "https://anyrouter.dev/api/v1/mcp",
"enabled": true
}
}
}
```

Key fields:
* `type: "remote"` — AnyRouter is a remote HTTP MCP server, not a stdio child.
* `url` — the endpoint, identical for every client.
* `enabled: true` — flip to `false` to keep the entry but disable it.
2. Restart OpenCode. The next tool call opens `anyrouter.dev/oauth/consent` — pick a permission level (Read-only is the default and safest) and click **Allow**.
1. Add the same server block to `~/.config/opencode/opencode.json` so AnyRouter is available across every project:
```json
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"anyrouter": {
"type": "remote",
"url": "https://anyrouter.dev/api/v1/mcp",
"enabled": true
}
}
}
```
2. Restart OpenCode. The next tool call opens `anyrouter.dev/oauth/consent` — pick a permission level and click **Allow**.
You can keep the MCP block next to an AnyRouter **provider** block in the same file — inference and tools are independent. See the [OpenCode guide](/guides/opencode) for the provider.
## Auto-connect with the anyr CLI [#auto-connect-with-the-anyr-cli]
`anyr opencode` connects the AnyRouter MCP gateway for you when **Auto-connect in coding agents** is on in the [dashboard](https://anyrouter.dev/dashboard/mcp). It uses the same key the session launches with, so there is nothing to paste. To turn it off, pass `--no-mcp` on a launch, run `anyr mcp auto off`, or switch it off in the dashboard.
## Verify [#verify]
In any OpenCode chat, ask **"how many AnyRouter credits do I have left?"**. The agent calls `get_credits` and reports the balance. **"What AnyRouter models support tools?"** calls `list_models`.
## Revoke [#revoke]
* **OpenCode side** — set `"enabled": false` in `opencode.json`, or delete the entry entirely.
* **AnyRouter side** — revoke the OAuth token from the [MCP dashboard](https://anyrouter.dev/dashboard/mcp). Subsequent tool calls return `401 Unauthorized` until you reconnect.
---
# AnyRouter MCP with other clients (/mcp/other-clients)
Anything that speaks the [Model Context Protocol](https://modelcontextprotocol.io) over remote HTTP connects to AnyRouter the same way — one URL, `https://anyrouter.dev/api/v1/mcp`, and either OAuth or a static bearer key.
1. **Add a remote MCP server** in your client's config, pointing at:
```
https://anyrouter.dev/api/v1/mcp
```
2. On the first tool invocation, the client opens a browser at `anyrouter.dev/oauth/consent`.
3. Pick a permission level (Read-only is the default and safest) and click **Allow**.
4. The client receives the authorization code through its registered callback and stores the token.
Later tool calls reuse the stored token until you revoke it from the [MCP dashboard](https://anyrouter.dev/dashboard/mcp).
If your client can't run the OAuth callback dance — headless agents, CI jobs, embedded automations — authenticate with a bearer token instead. Two key types work:
* **LLM API key** (`sk-ar-v1-*`) from [/dashboard/keys](https://anyrouter.dev/dashboard/keys) — grants access to `list_models` only.
* **Management API key** (`ak_*`) from [/dashboard/settings#keys](https://anyrouter.dev/dashboard/settings#keys) — grants the scopes you assign.
Smoke test:
```bash
curl https://anyrouter.dev/api/v1/mcp \
-H "Authorization: Bearer ak_your_management_key" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"tools/list","params":{},"id":1}'
```
Full reference: [MCP API → Manual authentication](/api-reference/mcp#manual-authentication).
## Verify [#verify]
Once configured, ask your client to run any AnyRouter tool — for example, **"list AnyRouter models"** calls `list_models` and returns the catalog. Over the static-key path, the same `curl tools/list` above should return a JSON list of tools rather than a `401`.
## Revoke [#revoke]
Revoke an OAuth token any time from the [MCP dashboard](https://anyrouter.dev/dashboard/mcp); the connection stops working immediately. To disable static-key access, revoke or delete the key from [/dashboard/keys](https://anyrouter.dev/dashboard/keys) or [/dashboard/settings#keys](https://anyrouter.dev/dashboard/settings#keys).
## Troubleshooting [#troubleshooting]
:::details{summary="What must my client support?"}
| Capability | Notes |
| -------------------------------------- | ---------------------------------------------------- |
| Remote MCP transport (HTTP) | Required. AnyRouter does not expose stdio. |
| Dynamic Client Registration (RFC 7591) | Required for OAuth. Most modern MCP clients have it. |
| Authorization Code + PKCE (S256) | Required for OAuth. |
| Bearer-token auth | Required for the static-key path. |
If your client supports all four, it works without any AnyRouter-specific configuration.
:::
---
# AnyRouter MCP with Windsurf (/mcp/windsurf)
Windsurf (Codeium) exposes MCP through the Cascade panel, and you can reach the same result by editing `~/.codeium/windsurf/mcp_config.json` directly.
1. Open Windsurf and click the **Cascade** icon in the sidebar.
2. Open the **MCP servers** panel and click **Add custom server**.
3. Fill in the details:
* **Name**: `anyrouter`
* **Server URL**: `https://anyrouter.dev/api/v1/mcp`
4. Save. On the next tool invocation, Windsurf opens `anyrouter.dev/oauth/consent` — pick a permission level (Read-only is the default and safest) and click **Allow**.
1. Open (or create) `~/.codeium/windsurf/mcp_config.json`.
2. Add the AnyRouter server:
```json
{
"mcpServers": {
"anyrouter": {
"serverUrl": "https://anyrouter.dev/api/v1/mcp"
}
}
}
```
3. Reload Windsurf. The next tool call triggers the OAuth flow at `anyrouter.dev/oauth/consent`.
## Verify [#verify]
In any Cascade chat, ask **"check the AnyRouter system status"**. The agent calls `get_system_status` and surfaces the health snapshot inline.
## Revoke [#revoke]
* **Windsurf side** — remove the entry from the Cascade MCP panel, or delete it from `mcp_config.json`.
* **AnyRouter side** — revoke the OAuth token from the [MCP dashboard](https://anyrouter.dev/dashboard/mcp). Subsequent tool calls return `401 Unauthorized` until you reconnect.
## Related [#related]
* [MCP overview](/mcp) — what the server does and which tools are exposed
* [MCP API reference](/api-reference/mcp) — full tool schemas, scope tables, manual auth
---
# AnyRouter MCP Overview (/mcp)
AnyRouter MCP is a [Model Context Protocol](https://modelcontextprotocol.io) server. MCP-aware clients connect with one URL and get tools for browsing the model catalog, managing your API keys, and checking credits — without leaving the client.
The endpoint is:
```
https://anyrouter.dev/api/v1/mcp
```
If you only need to call models, you do **not** need MCP — point your client at the standard [Chat Completions](/api-reference/chat-completions), [Messages](/api-reference/messages), or [Responses](/api-reference/responses) endpoint with an API key. MCP is for the surrounding workflow (browsing, key hygiene, presets) that the chat API can't do.
## Add AnyRouter MCP to your client [#add-anyrouter-mcp-to-your-client]
Every client uses the same endpoint and the same OAuth consent flow. The first tool call redirects you to AnyRouter, where you pick a permission level (Read-only is the default and safest) and approve. From then on the tools just work.
## Who is this for [#who-is-this-for]
Anyone using an MCP-aware tool day-to-day:
* **Claude Desktop** users who want to ask Claude "what AnyRouter models support vision?" or "create me a new API key called staging" without opening a browser.
* **Cursor**, **Claude Code**, and **OpenCode** users who want AnyRouter tools inline alongside their other MCP integrations.
* **Builders of MCP-aware agents** who want a hosted, OAuth-secured catalog and key-management surface for their users.
## What the client can do [#what-the-client-can-do]
These tools show up in your MCP-aware client as soon as the connection is authorized. What appears depends on the permission level you granted on the consent screen — `list_models`, `get_system_status`, and the knowledge-base tools are always available; the rest are gated by scope.

| Tool | What it does |
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `list_models` | Browse models your key can call. Filter by owner, a capability tag, or a job (translation, coding, reasoning, vision, cheap), and sort by price or context |
| `recommend_model` | Pick a ranked model id for one job (translation, coding, reasoning, vision, or the lowest price) |
| `get_usage` | Recent workspace requests, tokens, and spend |
| `list_byok_keys` | See which provider keys are enabled or failing, without the secret |
| `list_okf_concepts` / `get_okf_concept` | Browse and read AnyRouter's knowledge base |
| `get_credits` | Check workspace balance and lifetime usage |
| `list_keys` | List your AnyRouter API keys |
| `create_key` | Mint a new API key |
| `revoke_key` | Disable a key by id |
| `list_presets` | List saved request presets |
| `list_conversations` | Search workspace conversations |
| `publish_prompt` / `star_prompt` / `fork_prompt` | Share, save, and fork community prompts |
| `publish_skill` | Sync a `SKILL.md` to a hub so your machines and agents can install it |
| `get_system_status` | Check AnyRouter's current health: API error rate, provider status, smoke test results |
Connection tools manage the upstream MCP servers your workspace proxies — `list_connections`, `get_connection`, `create_connection`, `update_connection`, `delete_connection`, `reauth_connection`, `discover_connection`, and `test_connection_tool`. They need the connections permission level on the consent screen.
Plus the **Skills & Knowledge Hub** tools — `list_hubs`, `get_hub`, `search_public_hubs`, `create_hub`, `hub_add_item`, and `hub_import_skill` — for managing hubs from inside your agent. See [Skills & Knowledge Hub](/hub).
And the **Artifacts** tools: `publish_artifact`, `list_artifacts`, `get_artifact`, `delete_artifact`, and `set_artifact_visibility`, for publishing pages, diagrams, SVG images and source files and sharing them by URL. See the [Artifacts API](/api-reference/artifacts).
There is no chat tool — MCP clients already call models directly. Administrators see additional `admin_*` tools; see the [MCP API reference](/api-reference/mcp#admin-tools).
### Examples [#examples]
**Browse the catalog with `list_models`.** Ask the client to filter by provider, a capability tag, or a job, and to sort by price or context. By default it returns models your key can actually call — pass `include_unavailable` if you need BYOK-only ids too.
**Pick one id with `recommend_model`.** Ask for the best fit for translation, coding, reasoning, an image, or the lowest price. The answer is a short ranked list: id, why, price, context, and capabilities.

**Check your balance with `get_credits`.** Useful before a long agent run — your workspace balance, lifetime usage, and free-tier grants in one call.

See the [MCP API reference](/api-reference/mcp) for full argument schemas, scope requirements, JSON-RPC error shapes, and the manual-authentication path for clients that can't run OAuth.
## Auto-connect from the anyr CLI [#auto-connect-from-the-anyr-cli]
`anyr claude` connects the AnyRouter MCP gateway for you when **Auto-connect in coding agents** is on in the [dashboard](https://anyrouter.dev/dashboard/mcp). It uses the same key the session launches with, so there is nothing to paste. To turn it off, pass `--no-mcp` on a launch, run `anyr mcp auto off`, or switch it off in the dashboard. The same applies to `anyr codex`, `anyr opencode`, and `anyr grok`. Update the CLI to the latest release to get it.
## Managing connected clients [#managing-connected-clients]
The [MCP dashboard](https://anyrouter.dev/dashboard/mcp) shows every client that has authorized your workspace and lets you revoke any of them with one click. Revoking takes effect immediately — the next tool call from that client returns `401 Unauthorized`.
## Related [#related]
* [MCP API reference](/api-reference/mcp) — full tool catalog, scope rules, error shapes, manual authentication
* [Skills & Knowledge Hub](/hub) — the MCP hub tools for skills, prompts, and knowledge
* [MCP dashboard](https://anyrouter.dev/dashboard/mcp) — your connected MCP clients and revoke controls
---
# A/B Compare Models in the Playground (/scenario-examples/ab-compare-models-playground)
Before you wire a model into production, see how a few candidates answer the *same* prompt — and what each one costs and how fast it responds. With AnyRouter, one key reaches every model, so comparing them is a matter of swapping the `model` field.
## The idea [#the-idea]
Because every model lives behind one key and one base URL, a comparison is just the same request sent N times with a different `model`. Each response reports its own latency and token usage, so you can weigh quality against cost in one pass.
## How it works [#how-it-works]
Every run — from the Playground or from code — goes through your AnyRouter account, so the cost of each comparison shows up in your [usage explorer](/features/usage-explorer) alongside everything else. The `usage` block on each response reports prompt, completion, and total tokens.
## Implementation [#implementation]
The [Playground](https://anyrouter.dev/playground) lets you chat against any catalog model with your account key already attached:
1. Open the [Playground](https://anyrouter.dev/playground) — your account key is attached automatically.
2. Pick a model and send your prompt.
3. Change the model and send the same prompt again.
4. Compare the answers, plus the per-response latency and token usage shown in the metrics footer.
Send the identical messages array to each candidate and collect the results. One key, one base URL:
```ts
import { AnyRouter } from "@anyr/sdk"
const client = new AnyRouter() // reads ANYROUTER_API_KEY
const prompt = "Rewrite this commit message to be clear and concise: 'fix stuff'"
const candidates = [
"openai/gpt-5.4-mini",
"anthropic/claude-haiku-4-5",
"z-ai/glm-4.7-flash",
]
for (const model of candidates) {
const started = Date.now()
const res = await client.chat.completions.create({
model,
messages: [{ role: "user", content: prompt }],
})
console.log(`\n=== ${model} (${Date.now() - started}ms) ===`)
console.log(res.choices[0].message.content)
console.log("tokens:", res.usage?.total_tokens)
}
```
If you run comparisons often, pass a `session_id` so the requests group together in your logs and are easy to find later.
```json
{
"model": "z-ai/glm-4.7-flash",
"messages": [{ "role": "user", "content": "..." }],
"session_id": "model-bakeoff-2025-01"
}
```
## Related [#related]
* [Models Catalog](/features/models-catalog) — browse every available model
* [Usage Explorer](/features/usage-explorer) — see cost and tokens per run
* [Chat Completions API](/api-reference/chat-completions) — full request and response shape
* [AnyRouter SDK](/sdk) — the typed client used above
---
# Add BYOK to an Existing App (/scenario-examples/add-byok-to-existing-app)
You already have an app talking to OpenAI, Anthropic, or another provider, and you pay that provider directly. Bring Your Own Key (BYOK) keeps that billing relationship while moving your traffic through AnyRouter — so you get unified logs, analytics, routing, and presets without paying a markup on tokens.
## The idea [#the-idea]
The only code change is the base URL and the key. Your provider keys stay in AnyRouter as encrypted BYOK credentials; your app never sends them again. When a request matches a model served by a provider you've configured, AnyRouter forwards the call with your key and charges you nothing extra per token.
## How it works [#how-it-works]
| Path | Who you pay | AnyRouter fee |
| ---------------------------------------------- | ------------------------------- | ------------------ |
| BYOK (your provider key matches the model) | The upstream provider, directly | None per token |
| AnyRouter credits (no BYOK key for that model) | AnyRouter credits | Listed model price |
BYOK requests never draw down AnyRouter credits. If you want a safety net, keep credits funded so requests still succeed when a BYOK provider is out of quota — see the fallback step below.
## Implementation [#implementation]
::::steps
:::step{title="Store your provider key"}
Open [Settings → BYOK](https://anyrouter.dev/dashboard/byok) and add the provider key you already use — for example your OpenAI or Anthropic key. AnyRouter encrypts it at rest and only uses it to sign your own requests. You can add more than one provider.
:::
:::step{title="Create an AnyRouter key"}
Create an `sk-ar-` key in [Dashboard → Keys](https://anyrouter.dev/dashboard/keys). This is the only key your app sends from now on.
:::
:::step{title="Point your app at AnyRouter"}
Swap the base URL and key. The request body is unchanged — AnyRouter speaks the same OpenAI-compatible protocol.
```ts
import OpenAI from "openai"
const client = new OpenAI({
baseURL: "https://anyrouter.dev/api/v1",
apiKey: process.env.ANYROUTER_API_KEY, // sk-ar-v1-...
})
const completion = await client.chat.completions.create({
model: "openai/gpt-5.4-mini",
messages: [{ role: "user", content: "Hello from my app" }],
})
```
Because you stored an OpenAI BYOK key in the first step, this request is served with **your** OpenAI account — same bill from OpenAI, plus AnyRouter's logs and analytics on top.
:::
:::step{title="Fall back to credits when BYOK is exhausted (optional)"}
Pair BYOK with [Smart Routing](/features/routing) so that if your provider account hits a rate limit or runs out of quota, AnyRouter can fall back to a credit-billed upstream and keep your app running:
```json
{
"model": "openai/gpt-5.4-mini",
"messages": [{ "role": "user", "content": "Hello" }],
"provider": { "allow_fallbacks": true }
}
```
:::
::::
## Related [#related]
* [Bring Your Own Key (BYOK)](/features/byok) — how BYOK billing and storage work
* [BYOK Providers](/features/byok-providers) — which providers you can attach
* [Migrate from OpenAI](/guides/migrate-from-openai) — swap base URL, key, and model id
* [Migrate from Anthropic](/guides/migrate-from-anthropic) — use Messages with Anthropic clients
---
# Build an Agentic Coding Loop with GLM (/scenario-examples/agentic-coding-loop-with-glm)
A coding agent is a model that can call tools — read a file, run a test, search a codebase — and keep going until the task is done. AnyRouter gives you one provider for the model, and the [Vercel AI SDK](https://ai-sdk.dev) handles the tool-calling loop. GLM models are a strong, low-cost fit: long context and controllable reasoning.
## The idea [#the-idea]
The AI SDK runs the loop for you. When the model emits a tool call, the SDK executes the matching `tool`, feeds the result back, and asks the model what to do next — repeating until the task is done or a step budget is hit. AnyRouter is just the model provider, so the same loop works with any tool-capable model by changing the model id. This example uses `z-ai/glm-4.7-flash`.
## How it works [#how-it-works]
`stopWhen: stepCountIs(n)` bounds how many steps the loop may take. Each entry in the returned `steps` array records the model's tool calls and their results, so you can log or inspect exactly what the agent did.
## Implementation [#implementation]
::::steps
:::step{title="Install the packages"}
```bash
npm install @anyr/ai-sdk-provider ai zod
```
:::
:::step{title="Define tools and run the loop"}
```ts
import { createAnyRouter } from "@anyr/ai-sdk-provider"
import { generateText, tool, stepCountIs } from "ai"
import { z } from "zod"
import { readFile } from "node:fs/promises"
const anyrouter = createAnyRouter() // reads ANYROUTER_API_KEY
const { text, steps } = await generateText({
model: anyrouter("z-ai/glm-4.7-flash"),
system: "You are a coding agent. Use the tools to inspect the repo, then answer.",
prompt: "What does src/index.ts export, and is it covered by a test?",
stopWhen: stepCountIs(8),
tools: {
read_file: tool({
description: "Read a UTF-8 text file from the repo.",
parameters: z.object({ path: z.string() }),
execute: async ({ path }) => ({ contents: await readFile(path, "utf8") }),
}),
list_files: tool({
description: "List files matching a glob.",
parameters: z.object({ glob: z.string() }),
// findFiles is your own glob helper (e.g. fast-glob's `glob(pattern)`).
execute: async ({ glob }) => ({ files: await findFiles(glob) }),
}),
},
})
console.log(text)
console.log(`completed in ${steps.length} steps`)
```
:::
:::step{title="Stream the agent's progress (optional)"}
For a responsive UI or CLI, swap `generateText` for `streamText` and consume the full stream — text deltas, tool calls, and tool results all arrive as they happen:
```ts
import { streamText, stepCountIs } from "ai"
const result = streamText({
model: anyrouter("z-ai/glm-4.7-flash"),
prompt: "Refactor the failing test and explain the change.",
stopWhen: stepCountIs(10),
tools: { /* read_file, list_files, run_test, ... */ },
})
for await (const part of result.fullStream) {
if (part.type === "text-delta") process.stdout.write(part.text)
if (part.type === "tool-call") console.log("\n→ calling", part.toolName)
}
```
:::
:::step{title="Tune reasoning with effort (optional)"}
GLM supports controllable thinking. Pass `reasoning_effort` through the provider's `extraBody`, which is merged into the request body sent to AnyRouter:
```ts
const { text } = await generateText({
model: anyrouter("z-ai/glm-4.7-flash"),
prompt: "Plan a migration from REST to tRPC across this repo.",
providerOptions: { anyrouter: { extraBody: { reasoning_effort: "high" } } },
stopWhen: stepCountIs(12),
tools: { /* ... */ },
})
```
:::
::::
**Bring your own GLM key.** If you have a Z-AI account or Coding Plan, add the key in [Settings → BYOK](https://anyrouter.dev/dashboard/byok). AnyRouter then serves `z-ai/*` requests with your key — same agent code, billed to your Z-AI account at list price.
## Related [#related]
* [AnyRouter SDK](/sdk) — the typed client for direct API calls
* [Vercel AI SDK guide](/guides/vercel-ai-sdk) — wire AnyRouter into the AI SDK
* [Tool Use](/features/tool-use) — how tool calling works through AnyRouter
* [AI Agent Integration](/guides/ai-agent-integration) — patterns for agent frameworks
---
# Cut LLM Costs with Automatic Provider Fallback (/scenario-examples/cut-costs-provider-fallback)
Many models are served by more than one provider, often at different prices and with different rate limits. Let AnyRouter pick the best upstream for each request and fall back automatically when one is unavailable — with a single, unchanged model id in your code.
## The idea [#the-idea]
For a model like `openai/gpt-oss-120b` that several providers host, AnyRouter keeps a live view of price, latency, and health for each upstream. You express a preference — cheapest first, fastest first, or an explicit order — and AnyRouter honors it while skipping any provider that is currently failing. The routing happens on AnyRouter's side, so your request body barely changes.
## How it works [#how-it-works]
You add a `provider` block to any Chat Completions request. `sort` controls preference, `order` pins an explicit sequence, `max_price` caps what you'll pay, and `allow_fallbacks` decides whether AnyRouter may move on when an upstream errors or is rate-limited. Prices throughout are in US dollars per million tokens.
## Implementation [#implementation]
::::steps
:::step{title="Sort by price"}
`sort: "price"` tries the cheapest healthy upstream first; `allow_fallbacks: true` lets it move on if that upstream errors or is rate-limited.
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-v1-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-oss-120b",
"messages": [{"role": "user", "content": "Summarize this changelog in 3 bullets."}],
"provider": {
"sort": "price",
"allow_fallbacks": true
}
}'
```
:::
:::step{title="Pin an explicit order (optional)"}
To control the order yourself — for example, prefer a provider where you have a volume discount — use `order`:
```json
{
"model": "openai/gpt-oss-120b",
"messages": [{ "role": "user", "content": "Hello" }],
"provider": {
"order": ["deepinfra", "cerebras"],
"allow_fallbacks": true
}
}
```
AnyRouter tries `deepinfra` first, then `cerebras`, then any other healthy upstream (because `allow_fallbacks` is on). Set `allow_fallbacks: false` to fail fast and only use the providers you listed.
:::
:::step{title="Cap the price you'll pay (optional)"}
To guarantee a request never exceeds a per-token budget, set `max_price`. Any upstream above the cap is skipped:
```json
{
"model": "openai/gpt-oss-120b",
"messages": [{ "role": "user", "content": "Hello" }],
"provider": {
"sort": "price",
"max_price": { "prompt": 0.5, "completion": 1.5 },
"allow_fallbacks": true
}
}
```
If no upstream is under the cap, the request returns an error rather than silently picking a pricier provider.
:::
::::
Don't want to send a `provider` block on every call? Save the same preferences once as a [preset](/features/presets) and use the preset slug as the model. Every request through that slug inherits the routing rules.
## Related [#related]
* [Smart Routing](/features/routing) — how AnyRouter chooses an upstream
* [Model Fallbacks](/guides/model-fallbacks) — cascade across multiple models
* [Provider Routing](/guides/provider-routing) — the full `provider` block reference
* [Chat Completions API](/api-reference/chat-completions) — request and response shape
---
# Run Claude Code Against AnyRouter (/scenario-examples/run-claude-code-on-anyrouter)
Point Claude Code at AnyRouter and every session is billed once through your AnyRouter account, recorded in your request logs, and free to call any Messages-compatible model in the catalog — including non-Anthropic ones.
## The idea [#the-idea]
Claude Code speaks the Anthropic Messages protocol, and AnyRouter exposes a fully compatible Messages endpoint. So you can drop AnyRouter in front of Claude Code with no change to how you use the tool: point it at AnyRouter's base URL and key, and AnyRouter translates the Messages protocol to whatever the upstream model expects.
## How it works [#how-it-works]
* **One bill** — every session draws from one AnyRouter balance (or your BYOK provider accounts) instead of separate per-provider invoices.
* **Request logs** — each Claude Code call is recorded in your [request logs](/features/request-logs) for debugging and audit.
* **Any model** — switch the coding model without touching Claude Code's own config.
## Implementation [#implementation]
The AnyRouter CLI launches Claude Code with the right environment already set — no extra Node install, no manual env vars.
```bash
curl -fsSL https://anyrouter.dev/setup.sh | bash
anyr claude
anyr claude --install --ok
```
The first run asks how you want to connect — paste an `sk-ar-` key or log in at anyrouter.dev — and saves your profile to `~/.anyrouter/config.yaml`. Every run after that goes straight into Claude Code.
Pin a model or reasoning depth per session:
```bash
anyr claude --model "anthropic/claude-sonnet-4-6"
anyr claude --model auto --effort high
```
`auto` lets your pinned preset choose the model. Anything after `--` is forwarded to Claude Code itself:
```bash
anyr claude --model auto -- --resume
```
Claude Code reads two environment variables. Point them at AnyRouter's Messages base URL and your key:
```bash
export ANTHROPIC_BASE_URL="https://anyrouter.dev/api"
export ANTHROPIC_AUTH_TOKEN="sk-ar-v1-your-key"
claude
```
Drop those lines into a wrapper script if you want to keep a separate Anthropic key for other work — see the [Claude Code guide](/cli/claude-code) for the wrapper recipe.
Because AnyRouter translates the Messages protocol, you can run Claude Code on non-Anthropic models. Bring your own Z-AI key and run GLM:
```bash
anyr claude --model "z-ai/glm-4.7-flash"
```
Browse everything Claude Code can call by filtering the catalog to Messages-compatible models: [/models?endpoint=messages](https://anyrouter.dev/models?endpoint=messages).
## Related [#related]
* [Claude Code setup](/cli/claude-code) — full CLI setup and wrapper recipe
* [Grok Bot, Herdr, and Anyr CLI](/guides/grok-bot-herdr-anyr) — fleet coding with Herdr spaces
* [Run OpenCode against AnyRouter](/scenario-examples/run-opencode-on-anyrouter)
* [Messages API](/api-reference/messages) — the endpoint Claude Code targets
* [AnyRouter CLI](/cli) — every command and flag
* [Unify AI subscriptions](/scenario-examples/unify-ai-subscriptions) — one preset across all your provider accounts
---
# Run OpenCode Against AnyRouter (/scenario-examples/run-opencode-on-anyrouter)
Point OpenCode at AnyRouter and every session is billed once through your AnyRouter account, recorded in your request logs, and free to call any Chat Completions model in the catalog — including models OpenCode Zen does not list.
## The idea [#the-idea]
OpenCode speaks the OpenAI Chat Completions protocol, and AnyRouter exposes a fully compatible `/api/v1` endpoint. Drop AnyRouter in front of OpenCode with no change to how you use the TUI: register it as a custom `@ai-sdk/openai-compatible` provider (or let the AnyRouter CLI inject that provider for one session), and AnyRouter translates each call to whatever the upstream model expects.
## How it works [#how-it-works]
* **One bill** — every session draws from one AnyRouter balance (or your BYOK provider accounts) instead of separate per-provider invoices.
* **Request logs** — each OpenCode call is recorded in your [request logs](/features/request-logs) for debugging and audit.
* **Any model** — switch the coding model in `/models` without touching OpenCode's own auth for each vendor.
* **Correct context** — catalog context windows flow into OpenCode's `limit.context` and `limit.output`, so the agent does not undersize a 1M-token model.
## Implementation [#implementation]
The AnyRouter CLI launches OpenCode with the right provider already set — no install of extra packages and no manual `opencode.json`.
```bash
anyr opencode
```

The first run asks how you want to connect — paste an `sk-ar-` key or log in at anyrouter.dev — and saves your profile to `~/.anyrouter/config.yaml`. Every run after that goes straight into OpenCode.
Pin a model per session:
```bash
anyr opencode --model "anthropic/claude-sonnet-4-6"
anyr opencode --model "z-ai/glm-5.2"
```
`auto` lets your pinned preset choose the model. Anything after `--` is forwarded to OpenCode itself:
```bash
anyr opencode --model auto -- --help
```
Register AnyRouter once in `~/.config/opencode/opencode.json` so a bare `opencode` command uses it:
```json
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"anyrouter": {
"npm": "@ai-sdk/openai-compatible",
"name": "AnyRouter",
"options": {
"baseURL": "https://anyrouter.dev/api/v1",
"apiKey": "{env:ANYROUTER_API_KEY}"
},
"models": {
"anthropic/claude-sonnet-4-6": {
"name": "Claude Sonnet 4.6",
"limit": { "context": 200000, "output": 65536 }
}
}
}
},
"model": "anyrouter/anthropic/claude-sonnet-4-6"
}
```

Export `ANYROUTER_API_KEY` first (create the key in your [dashboard](https://anyrouter.dev/dashboard/keys)). Then run `opencode` and pick `anyrouter/…` in `/models`.

Because AnyRouter is just the Chat Completions backend, you can run OpenCode on non-OpenAI models. Bring your own Z-AI key and run GLM:
```bash
anyr opencode --model "z-ai/glm-5.2"
```
Browse everything OpenCode can call by filtering the catalog to Chat-Completions-compatible models: [anyrouter.dev/models?endpoint=chat-completions](https://anyrouter.dev/models?endpoint=chat-completions).
After the first prompt, open [request logs](https://anyrouter.dev/dashboard/logs) — the row is the proof the session billed through AnyRouter.

---
# How to Unify AI Subscriptions with AnyRouter (/scenario-examples/unify-ai-subscriptions)
If you already pay for several AI providers, AnyRouter can turn those separate accounts into one routing layer. Create one preset, add each provider account inside it, then call that preset from your app or from Claude Code — one model name for everything you use every day.
## The idea [#the-idea]
You give your tools a single model name (`coding-stack`), and AnyRouter handles provider choice, fallback, logs, rate limits, and cost visibility behind it. This guide builds a coding preset that layers your provider accounts, with AnyRouter credits as a last-resort backup.
## How it works [#how-it-works]
The preset named `coding-stack` layers several sources in priority order:
| Source | Example model | Why add it |
| ---------------------------------- | ----------------------------- | ------------------------------------------------------------ |
| Z-AI subscription or API key | `z-ai/glm-4.7-flash` | Strong multilingual coding and low-cost daily work |
| Moonshot Kimi account | `moonshotai/kimi-k2.5` | Long-context code review and Chinese-English work |
| Anthropic or Claude-compatible key | `anthropic/claude-sonnet-4-6` | High-signal agent and refactor tasks |
| OpenAI account | `openai/gpt-5.4-mini` | Fast fallback and OpenAI-compatible tooling |
| AnyRouter credits | Any supported model | Last-resort fallback when your own accounts are out of quota |
With model selection set to `auto`, AnyRouter chooses the highest-priority healthy model in the preset. If your Z-AI source is healthy, `auto` selects `z-ai/glm-4.7-flash` first; if it's unavailable, it moves to Kimi, Claude, OpenAI, or paid fallback according to the preset order.
## Implementation [#implementation]
::::steps
:::step{title="Create an AnyRouter key"}
Open [Dashboard → Keys](https://anyrouter.dev/dashboard/keys) and create an AnyRouter API key. It starts with `sk-ar-`. This is the only key your local tools need — provider keys stay stored in AnyRouter as BYOK credentials.
:::
:::step{title="Create one preset"}
Open [Dashboard → Presets](https://anyrouter.dev/dashboard/presets) and create a preset:
| Field | Value |
| --------------- | -------------------------------------------------------------------------- |
| Name | `Coding stack` |
| Slug | `coding-stack` |
| Mode | `fallback` for predictable order, or `weighted` for traffic sharing |
| Model selection | `auto` |
| Paid fallback | On, if you want AnyRouter credits to cover outages or exhausted BYOK quota |
Add the models in the order you want AnyRouter to try them:
```json
{
"models": [
"z-ai/glm-4.7-flash",
"moonshotai/kimi-k2.5",
"anthropic/claude-sonnet-4-6",
"openai/gpt-5.4-mini"
],
"temperature": 0.2,
"provider": {
"allow_fallbacks": true
}
}
```
Save the preset. You now have one model name for your tools: `coding-stack`. The explicit form `@preset/coding-stack` also works.
:::
:::step{title="Add provider accounts inside the preset"}
Stay on the preset page. For each model row, choose an existing source or click **Add provider account** inline.
| Source | What to add |
| ----------------- | ---------------------------------------- |
| Z-AI | Your Z-AI key for GLM models |
| Moonshot AI | Your Kimi key |
| Anthropic | Your Claude-compatible key or token |
| OpenAI | Your OpenAI key |
| AnyRouter credits | Enable paid fallback for backup capacity |
When adding a provider account:
1. Pick the provider from the row's **Source** menu.
2. Paste the provider key or token.
3. Add a clear alias, such as `zai-personal`, `kimi-pro`, or `openai-work`.
4. Save and validate the key.
AnyRouter saves the provider account as a BYOK source, attaches it to this preset, and encrypts the key before storing it. Your clients can now use the preset as the model:
```bash
curl https://anyrouter.dev/api/v1/chat/completions \
-H "Authorization: Bearer sk-ar-your-key" \
-H "Content-Type: application/json" \
-d '{
"model": "coding-stack",
"messages": [{"role": "user", "content": "Review this patch"}]
}'
```
The request resolves to the first healthy BYOK-backed model that matches the preset. If that source is rate-limited, unhealthy, or out of quota, AnyRouter tries the next branch.
:::
:::step{title="Launch Claude Code through AnyRouter"}
Install the native CLI, then run:
```bash
curl -fsSL https://anyrouter.dev/setup.sh | bash
anyr claude
```
The first run sets you up once. Choose either option:
* **Paste an API key** — the `sk-ar-` key from the first step.
* **Log in at anyrouter.dev** — opens your browser; after you approve, a key is generated and sent back to your terminal automatically.
Your profile is saved to `~/.anyrouter/config.yaml` and reused on every later run, so you go straight into Claude Code.
:::
:::step{title="Reuse the same key for Codex, OpenCode, and more"}
One stored key drives every tool. Launch Codex or OpenCode with the same config:
```bash
anyr codex
anyr opencode
```
The pinned `@preset/coding-stack` and `auto` model selection apply by default, so you usually don't need `--model`. Override per session when you want to:
```bash
anyr claude --model "anthropic/claude-sonnet-4-6"
anyr claude --model auto --effort high
anyr codex --effort xhigh
```
`auto` means "pick the best valid model from the pinned preset." Concrete model ids bypass auto selection for that one session. `--effort` tunes reasoning depth (`low`–`max`). Anything after `--` is forwarded to the underlying tool. See the [AnyRouter CLI reference](/cli) for every command, flag, and the `config.yaml` schema.
:::
::::
**BYOK or AnyRouter credits?** Use BYOK when you already have provider credits, subscriptions, enterprise contracts, preview access, or special limits. Use AnyRouter credits when you want a simple backup source without managing another provider account. Most teams use both: BYOK first so existing provider spend is used, AnyRouter paid fallback second so tools keep working when a provider account is exhausted, and the usage explorer for the full picture across both paths.
## Related [#related]
* [BYOK](/features/byok) — attach and manage provider credentials
* [Presets](/features/presets) — one model name over many sources
* [Smart Routing](/features/routing) — how AnyRouter chooses a branch
* [Claude Code](/guides/claude-code) — use Claude Code with AnyRouter
* [OpenCode](/guides/opencode) — use OpenCode with AnyRouter
* [Provider Routing](/guides/provider-routing) — the full `provider` block reference
---
# SDK Changelog (/sdk/changelog)
# AnyRouter SDK — Changelog [#anyrouter-sdk--changelog]
Release notes for the AnyRouter TypeScript SDK packages.
## [`@anyr/sdk`](https://www.npmjs.com/package/@anyr/sdk) [#anyrsdk]
Official TypeScript SDK for the AnyRouter API.
### Unreleased [#unreleased]
#### ⚠ BREAKING CHANGES [#-breaking-changes]
* **sdk:** removed the `cacheKey` request option. The API no longer forwards a client `cf-aig-cache-key` (a custom key was shared across all callers of the gateway), so the option did nothing. `cacheTtl` and `skipCache` are unchanged.
#### 🐛 Bug Fixes [#-bug-fixes]
* **docs:** error-handling table now lists every mapped status — added `BadRequestError` (400) and `UnprocessableEntityError` (422), and documents that `AnyRouterError` is the catch-all base class
#### 🏗️ Internal [#️-internal]
* declare `"sideEffects": false` so downstream bundlers can tree-shake unused resources — the SDK has no top-level side effects
### [1.0.3](https://github.com/duyet/anyrouter/compare/sdk-v1.0.2...sdk-v1.0.3) (2026-10-02) [#103-2026-10-02]
#### 🐛 Bug Fixes [#-bug-fixes-1]
* **catalog:** show BYOK-only models as billed by your provider, not $0 ([#4125](https://github.com/duyet/anyrouter/issues/4125)) ([8e8e15d](https://github.com/duyet/anyrouter/commit/8e8e15da2cc704edbbe9a5ba323cb3918f4c9df4))
### [1.0.2](https://github.com/duyet/anyrouter/compare/sdk-v1.0.1...sdk-v1.0.2) (2026-09-28) [#102-2026-09-28]
#### ✨ Features [#-features]
* **models:** expose reasoning and completion metadata ([#3511](https://github.com/duyet/anyrouter/issues/3511)) ([d3bebfe](https://github.com/duyet/anyrouter/commit/d3bebfef515382e4da4b421fb0e0acff6a9c9708))
#### 🐛 Bug Fixes [#-bug-fixes-2]
* **release:** repair version drift from stale release-please branch merges ([#3555](https://github.com/duyet/anyrouter/issues/3555)) ([ff0a89f](https://github.com/duyet/anyrouter/commit/ff0a89f1051e6a22bae1ae76ed4bed85e3330102))
### 1.0.0 (2026-09-24) [#100-2026-09-24]
#### ✨ Features [#-features-1]
* **routing:** honor exacto, tools, and 1M context on auto and presets ([#2979](https://github.com/duyet/anyrouter/issues/2979)) ([1df73ef](https://github.com/duyet/anyrouter/commit/1df73efa3c91f2dea48b7dc821dbbcd9ba033f1c)), closes [#2978](https://github.com/duyet/anyrouter/issues/2978)
#### ⚡ Performance [#-performance]
* **api:** bound and trim the keys-list KV payload ([#2597](https://github.com/duyet/anyrouter/issues/2597)) ([#3081](https://github.com/duyet/anyrouter/issues/3081)) ([8bd0a93](https://github.com/duyet/anyrouter/commit/8bd0a938927743cf0f7adc518deee272ce890aa0))
### [1.0.1](https://github.com/duyet/anyrouter/compare/sdk-v1.0.0...sdk-v1.0.1) (2026-09-24) [#101-2026-09-24]
#### ✨ Features [#-features-2]
* **routing:** honor exacto, tools, and 1M context on auto and presets ([#2979](https://github.com/duyet/anyrouter/issues/2979)) ([1df73ef](https://github.com/duyet/anyrouter/commit/1df73efa3c91f2dea48b7dc821dbbcd9ba033f1c)), closes [#2978](https://github.com/duyet/anyrouter/issues/2978)
#### ⚡ Performance [#-performance-1]
* **api:** bound and trim the keys-list KV payload ([#2597](https://github.com/duyet/anyrouter/issues/2597)) ([#3081](https://github.com/duyet/anyrouter/issues/3081)) ([8bd0a93](https://github.com/duyet/anyrouter/commit/8bd0a938927743cf0f7adc518deee272ce890aa0))
### 1.0.0 (2026-09-23) [#100-2026-09-23]
#### ✨ Features [#-features-3]
* **routing:** honor exacto, tools, and 1M context on auto and presets ([#2979](https://github.com/duyet/anyrouter/issues/2979)) ([1df73ef](https://github.com/duyet/anyrouter/commit/1df73efa3c91f2dea48b7dc821dbbcd9ba033f1c)), closes [#2978](https://github.com/duyet/anyrouter/issues/2978)
#### ⚡ Performance [#-performance-2]
* **api:** bound and trim the keys-list KV payload ([#2597](https://github.com/duyet/anyrouter/issues/2597)) ([#3081](https://github.com/duyet/anyrouter/issues/3081)) ([8bd0a93](https://github.com/duyet/anyrouter/commit/8bd0a938927743cf0f7adc518deee272ce890aa0))
### 1.0.0 (2026-08-28) [#100-2026-08-28]
#### ✨ Features [#-features-4]
* **api:** add display\_name to model objects for Claude Code gateway discovery ([77520dd](https://github.com/duyet/anyrouter/commit/77520ddc9b6081953b5d76ab790ed082cbd19211))
* **errors:** tell plan\_required apart from out-of-credits in clients ([4497f6a](https://github.com/duyet/anyrouter/commit/4497f6a6d2c34e20c9b77b0d5f0a7220f644eca9))
#### 🐛 Bug Fixes [#-bug-fixes-3]
* **catalog:** default /models and GET /api/v1/models to reachable rows ([#2354](https://github.com/duyet/anyrouter/issues/2354)) ([fe92824](https://github.com/duyet/anyrouter/commit/fe928241ce035ef26a3a0f1ea870d56353573502))
### [0.1.5](https://github.com/duyet/anyrouter/compare/sdk-v0.1.4...sdk-v0.1.5) (2026-08-20) [#015-2026-08-20]
#### ✨ Features [#-features-5]
* **errors:** tell plan\_required apart from out-of-credits in clients ([4497f6a](https://github.com/duyet/anyrouter/commit/4497f6a6d2c34e20c9b77b0d5f0a7220f644eca9))
#### 🐛 Bug Fixes [#-bug-fixes-4]
* **catalog:** default /models and GET /api/v1/models to reachable rows ([#2354](https://github.com/duyet/anyrouter/issues/2354)) ([fe92824](https://github.com/duyet/anyrouter/commit/fe928241ce035ef26a3a0f1ea870d56353573502))
* **docs:** title-case sidebar folders and slash model ids ([55100ae](https://github.com/duyet/anyrouter/commit/55100aedf37adf5ef275cc7e6272db6b538acf10))
### [0.1.4](https://github.com/duyet/anyrouter/compare/sdk-v0.1.3...sdk-v0.1.4) (2026-08-01) [#014-2026-08-01]
#### ✨ Features [#-features-6]
* **sdk:** cacheTtl/cacheKey/skipCache request options ([1e2ff95](https://github.com/duyet/anyrouter/commit/1e2ff95c988ee3e846337dcbd23d7e268c216883))
* **telemetry:** email engagement tracking and client-type attribution ([ebbdc9d](https://github.com/duyet/anyrouter/commit/ebbdc9dfceb7e263ec39cdab7bea8e9a036f821d))
### 0.1.3 (2026-06-18) [#013-2026-06-18]
#### 🐛 Bug Fixes [#-bug-fixes-5]
* **transport:** automatically retry transient failures — connection errors, timeouts, and `408`/`409`/`429`/`5xx` responses — with exponential backoff + jitter, honoring `Retry-After` / `Retry-After-Ms` and the `x-should-retry` override header
* **transport:** per-request timeout (default 10 min) via `AbortController`, cleared once response headers arrive so it never aborts an in-progress stream
* per-request options on every resource method — `signal`, `timeout`, `maxRetries`, `headers`
* add `APIUserAbortError`, raised when the caller aborts via an `AbortSignal` (never retried)
### 0.1.2 (2026-06-18) [#012-2026-06-18]
#### ✨ Features [#-features-7]
* initial public release of the official AnyRouter TypeScript SDK
* resources: chat completions, embeddings, images, models, credits, API keys, presets, the OpenAI Responses API, and the Anthropic Messages API
* typed request/response/error envelope, dependency-free SSE streaming, OpenRouter-style provider routing (`provider`, `models`, `route`, `session_id`)
* custom `fetch` implementation and per-client headers
## [`@anyr/ai-sdk-provider`](https://www.npmjs.com/package/@anyr/ai-sdk-provider) [#anyrai-sdk-provider]
The AnyRouter provider for the Vercel AI SDK.
### Unreleased [#unreleased-1]
#### ⚠ BREAKING CHANGES [#-breaking-changes-1]
* removed the `cacheKey` provider setting and `providerOptions.anyrouter.cacheKey`. The API no longer forwards a client `cf-aig-cache-key` (a custom key was shared across all callers of the gateway), so it did nothing. `cacheTtl` and `skipCache` are unchanged.
### [1.0.2](https://github.com/duyet/anyrouter/compare/ai-sdk-provider-v1.0.1...ai-sdk-provider-v1.0.2) (2026-10-02) [#102-2026-10-02]
#### Dependencies [#dependencies]
* The following workspace dependencies were updated
* dependencies
* @anyr/sdk bumped from ^1.0.2 to ^1.0.3
### [1.0.1](https://github.com/duyet/anyrouter/compare/ai-sdk-provider-v1.0.0...ai-sdk-provider-v1.0.1) (2026-09-28) [#101-2026-09-28]
#### 🐛 Bug Fixes [#-bug-fixes-6]
* **release:** repair version drift from stale release-please branch merges ([#3555](https://github.com/duyet/anyrouter/issues/3555)) ([ff0a89f](https://github.com/duyet/anyrouter/commit/ff0a89f1051e6a22bae1ae76ed4bed85e3330102))
#### Dependencies [#dependencies-1]
* The following workspace dependencies were updated
* dependencies
* @anyr/sdk bumped from ^1.0.1 to ^1.0.2
### 1.0.0 (2026-09-24) [#100-2026-09-24-1]
#### 🐛 Bug Fixes [#-bug-fixes-7]
* **sdk:** align ai-sdk-provider range with workspace @anyr/sdk 1.0.0 ([#2846](https://github.com/duyet/anyrouter/issues/2846)) ([7cd866e](https://github.com/duyet/anyrouter/commit/7cd866efc67c14c28e90fc43d1c1a7171f542000))
* **sdk:** restore ai-sdk-provider @anyr/sdk pin to workspace 1.0.0 ([#3041](https://github.com/duyet/anyrouter/issues/3041)) ([1e14da4](https://github.com/duyet/anyrouter/commit/1e14da46cfc9e582ddbd302e9fac70ba1b1ff4ef))
#### Dependencies [#dependencies-2]
* The following workspace dependencies were updated
* dependencies
* @anyr/sdk bumped from ^1.0.1 to ^1.0.0
### [0.1.10](https://github.com/duyet/anyrouter/compare/ai-sdk-provider-v1.0.0...ai-sdk-provider-v0.1.10) (2026-09-24) [#0110-2026-09-24]
#### Dependencies [#dependencies-3]
* The following workspace dependencies were updated
* dependencies
* @anyr/sdk bumped from ^1.0.1 to ^1.0.0
### [0.1.9](https://github.com/duyet/anyrouter/compare/ai-sdk-provider-v0.1.8...ai-sdk-provider-v0.1.9) (2026-09-24) [#019-2026-09-24]
#### 🐛 Bug Fixes [#-bug-fixes-8]
* **sdk:** restore ai-sdk-provider @anyr/sdk pin to workspace 1.0.0 ([#3041](https://github.com/duyet/anyrouter/issues/3041)) ([1e14da4](https://github.com/duyet/anyrouter/commit/1e14da46cfc9e582ddbd302e9fac70ba1b1ff4ef))
#### Dependencies [#dependencies-4]
* The following workspace dependencies were updated
* dependencies
* @anyr/sdk bumped from ^1.0.0 to ^1.0.1
### [0.1.8](https://github.com/duyet/anyrouter/compare/ai-sdk-provider-v0.1.7...ai-sdk-provider-v0.1.8) (2026-09-04) [#018-2026-09-04]
#### 🐛 Bug Fixes [#-bug-fixes-9]
* **sdk:** align ai-sdk-provider range with workspace @anyr/sdk 1.0.0 ([#2846](https://github.com/duyet/anyrouter/issues/2846)) ([7cd866e](https://github.com/duyet/anyrouter/commit/7cd866efc67c14c28e90fc43d1c1a7171f542000))
#### Dependencies [#dependencies-5]
* The following workspace dependencies were updated
* dependencies
* @anyr/sdk bumped from ^1.0.0 to ^0.1.6
### [0.1.7](https://github.com/duyet/anyrouter/compare/ai-sdk-provider-v0.1.5...ai-sdk-provider-v0.1.7) (2026-08-20) [#017-2026-08-20]
#### Dependencies [#dependencies-6]
* The following workspace dependencies were updated
* dependencies
* @anyr/sdk bumped from ^0.1.3 to ^0.1.5
### [0.1.5](https://github.com/duyet/anyrouter/compare/ai-sdk-provider-v0.1.4...ai-sdk-provider-v0.1.5) (2026-07-31) [#015-2026-07-31]
#### ✨ Features [#-features-8]
* **ai-sdk-provider:** cache passthrough options ([4090989](https://github.com/duyet/anyrouter/commit/409098972bf55d5ba3dd6e4eeca4ec77493683b3))
* **telemetry:** email engagement tracking and client-type attribution ([ebbdc9d](https://github.com/duyet/anyrouter/commit/ebbdc9dfceb7e263ec39cdab7bea8e9a036f821d))
#### Dependencies [#dependencies-7]
* The following workspace dependencies were updated
* dependencies
* @anyr/sdk bumped from ^0.1.1 to ^0.1.4
### 0.1.4 (2026-06-18) [#014-2026-06-18]
#### 🐛 Bug Fixes [#-bug-fixes-10]
* surface `reasoning_content` from reasoning models (DeepSeek-R1, GLM thinking, …) as the AI SDK's standard reasoning parts — `reasoning` / `reasoningText` and the full-stream `reasoning-start` / `reasoning-delta` / `reasoning-end` parts now work
### 0.1.3 (2026-06-18) [#013-2026-06-18-1]
#### ✨ Features [#-features-9]
* support the Vercel AI SDK v7 (beta) alongside v6 — widened `ai`, `@ai-sdk/provider`, and `@ai-sdk/provider-utils` peer ranges
### 0.1.2 (2026-06-18) [#012-2026-06-18-1]
#### ✨ Features [#-features-10]
* initial public release of the AnyRouter provider for the Vercel AI SDK
* `createAnyRouter()` / `anyrouter` — chat (`LanguageModelV3`), embeddings, and image models
* tool calling, structured output, streaming, and provider routing / `session_id` / `extraBody` passthrough via provider settings and per-call `providerOptions.anyrouter`
---
# AnyRouter SDK (/sdk)
The official TypeScript SDK for the [AnyRouter](https://anyrouter.dev) API. One key, one base URL, every model — with typed requests, typed responses, a typed error envelope, and automatic retries.
## Packages [#packages]
| Package | Use it for |
| ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| [`@anyr/sdk`](https://www.npmjs.com/package/@anyr/sdk) | The direct API client — chat, embeddings, models, credits, keys, presets, the Responses API, and the Anthropic Messages API |
| [`@anyr/ai-sdk-provider`](https://www.npmjs.com/package/@anyr/ai-sdk-provider) | Using the [Vercel AI SDK](https://ai-sdk.dev)? Call every AnyRouter model through `createAnyRouter` |
## Quickstart [#quickstart]
::::steps
:::step{title="Install the SDK"}
```bash
npm install @anyr/sdk
```
:::
:::step{title="Set your key"}
The client reads `ANYROUTER_API_KEY` from the environment when you omit it. Get a key at [anyrouter.dev/dashboard/keys](https://anyrouter.dev/dashboard/keys).
```bash
export ANYROUTER_API_KEY=sk-ar-v1-...
```
:::
:::step{title="Make a request"}
```ts
import { AnyRouter } from "@anyr/sdk"
// Reads the key from ANYROUTER_API_KEY when omitted.
const client = new AnyRouter()
const completion = await client.chat.completions.create({
model: "z-ai/glm-4.7-flash",
messages: [{ role: "user", content: "Hello!" }],
})
console.log(completion.choices[0].message.content)
```
:::
::::
## Streaming [#streaming]
Pass `stream: true` to receive tokens as they are generated:
```ts
const stream = await client.chat.completions.create({
model: "z-ai/glm-4.7-flash",
messages: [{ role: "user", content: "Write a haiku about TypeScript." }],
stream: true,
})
for await (const chunk of stream) {
process.stdout.write(chunk.choices[0]?.delta?.content ?? "")
}
```
## Reliability [#reliability]
Every request automatically retries transient failures — connection errors, timeouts, and `429` / `5xx` responses — with exponential backoff that honors the server's `Retry-After` headers. A per-request timeout bounds the wait for the first response without aborting a running stream, and you can pass an `AbortSignal`, `timeout`, `maxRetries`, or one-off `headers` per call:
```ts
const controller = new AbortController()
const stream = await client.chat.completions.create(
{ model: "z-ai/glm-4.7-flash", messages, stream: true },
{ signal: controller.signal, timeout: 30_000 },
)
```
## Vercel AI SDK [#vercel-ai-sdk]
Prefer the [Vercel AI SDK](https://ai-sdk.dev)? Install `@anyr/ai-sdk-provider` and point any model id at AnyRouter through `createAnyRouter`:
```ts
import { createAnyRouter } from "@anyr/ai-sdk-provider"
import { generateText } from "ai"
const anyrouter = createAnyRouter() // reads ANYROUTER_API_KEY
const { text } = await generateText({
model: anyrouter("z-ai/glm-4.7-flash"),
prompt: "What is AnyRouter?",
})
```
## Related [#related]
* [Vercel AI SDK guide](/guides/vercel-ai-sdk) — use AnyRouter with the AI SDK end to end
* [SDK changelog](/sdk/changelog) — release history for both packages
* [API reference](/api-reference/overview) — the underlying HTTP endpoints
* [CLI](/cli) — launch coding agents through the gateway