Get started in five minutes
DarkRouter is an OpenAI- and Anthropic-compatible API on top of a pool of unofficial providers. Change the base_url and the key in your SDK — everything else works as before: the same requests, the same streaming, the same response structure.
Quickstart
Accounts are provisioned manually — reach out on Telegram or at support@darkrouter.ai. Then create a key on the “API keys” page: it is shown only once, so copy it right away.
By card or with crypto. One balance for all models; the 10% fee is charged only on top-up, tokens are billed at the provider's price 1:1.
Drop the base_url and your key into any OpenAI-compatible SDK — or start with the curl below.
curl https://darkrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $DARKROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4",
"messages": [{"role": "user", "content": "Привет!"}]
}'
Authentication
Every request is authorized with an API key in the Authorization: Bearer <key> header. The /api/v1/messages endpoint additionally accepts Anthropic's native x-api-key: <key> header, which the Anthropic SDK sends on its own. Nothing needs to change.
- Keys are created and disabled on the “API keys” page of your dashboard.
- The raw key is shown once at creation — store it in a secret manager or an environment variable.
- Each key has its own routing settings (provider filters, price ceiling) — handy for separating production from experiments.
Endpoints
The API mirrors vendor dialects: pick the endpoint matching your SDK's format. The request body is proxied to the provider as is — all parameters of the original API are supported.
| Endpoint | Format | When to use |
|---|---|---|
POST /api/v1/chat/completions |
OpenAI Chat Completions | Universal format — works for most models and tools. |
POST /api/v1/responses |
OpenAI Responses | OpenAI's new format (Responses API), for openai/* models. |
POST /api/v1/messages |
Anthropic Messages | Native Anthropic SDK format, for anthropic/* models. |
GET /api/v1/models |
OpenAI Models + pricing | List of models with per-provider pricing. Public, no authentication. |
GET /api/v1/balance |
JSON | Current account balance in USD. Authentication required; works even when the balance is empty. |
GET /api/v1/models is public (no authentication): tools that fetch the model list automatically work out of the box. The response format and per-provider pricing are in the “Models” section.
Models
The model field takes the identifier of a logical model from the catalogue — for example anthropic/claude-sonnet-4.6 or openai/gpt-5.4. A single identifier maps to several routes to different providers: the router picks the cheapest working one and switches to a backup on failure. You don't need to know which provider served the request.
The current list of models and prices per 1M tokens (input / output / cache) is in the model catalogue; the identifier copies in one click.
Model list and pricing over the API
The same thing programmatically — GET /api/v1/models, a public endpoint without authentication. The envelope is compatible with the OpenAI Models API, so clients fetch the list automatically. For aggregators, a per-provider price breakdown is included:
curl -s https://darkrouter.ai/api/v1/models
{
"object": "list",
"data": [
{
"id": "anthropic/claude-sonnet-4.6",
"object": "model",
"created": 1780545983,
"owned_by": "anthropic",
"name": "Claude Sonnet 4.6",
"context_window": 200000,
"pricing": { "input": "1.5", "output": "7.5", "cache_write": "1.875", "cache_read": "0.15" },
"providers": [
{
"provider": "Apixly",
"group": "claude",
"context_window": 200000,
"pricing": { "input": "1.5", "output": "7.5", "cache_write": "1.875", "cache_read": "0.15" },
"available": true
},
{
"provider": "Wellflow",
"group": "direct",
"pricing": { "input": "1.8", "output": "9" },
"available": false
}
]
}
]
}
-
All prices are USD per 1M tokens, as strings (exact decimal values):
input,output,cache_write,cache_read. -
A model's
pricingis the price of the cheapest available route — exactly the one the request will take right now. -
providerslists all active routes of the model, sorted from cheapest to most expensive;available: falsemeans the route is temporarily unhealthy or on cooldown. - The response is cached on the service side (~30 seconds) — you can poll it regularly to keep pricing in sync.
Balance
GET /api/v1/balance returns the current balance of the key's account, in USD. Unlike the proxy endpoints it is not blocked on an empty balance — a depleted account can still read it to know how much to top up. Authentication is the same as everywhere: Authorization: Bearer <key> (or x-api-key).
curl -s https://darkrouter.ai/api/v1/balance \
-H "Authorization: Bearer $DARKROUTER_API_KEY"
{
"object": "balance",
"balance": "12.3456",
"currency": "USD"
}
balance is a string with the exact decimal value and may be slightly negative (the final request is allowed to push the balance below zero). currency is always USD.
Code examples
No proprietary SDK needed — use the official OpenAI / Anthropic clients or any wrapper over them (LangChain, LlamaIndex, Vercel AI SDK…), pointing them at our base_url.
curl https://darkrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $DARKROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4",
"messages": [{"role": "user", "content": "Привет!"}]
}'
from openai import OpenAI
client = OpenAI(
base_url="https://darkrouter.ai/api/v1",
api_key="sk-...", # ключ DarkRouter
)
resp = client.chat.completions.create(
model="openai/gpt-5.4",
messages=[{"role": "user", "content": "Привет!"}],
)
print(resp.choices[0].message.content)
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://darkrouter.ai/api/v1",
apiKey: process.env.DARKROUTER_API_KEY,
});
const resp = await client.chat.completions.create({
model: "openai/gpt-5.4",
messages: [{ role: "user", content: "Привет!" }],
});
console.log(resp.choices[0].message.content);
from anthropic import Anthropic
# Anthropic SDK сам добавляет /v1/messages — поэтому base_url без /v1
client = Anthropic(
base_url="https://darkrouter.ai/api",
api_key="sk-...", # ключ DarkRouter
)
msg = client.messages.create(
model="anthropic/claude-sonnet-4.6",
max_tokens=1024,
messages=[{"role": "user", "content": "Привет!"}],
)
print(msg.content[0].text)
import Anthropic from "@anthropic-ai/sdk";
// Anthropic SDK сам добавляет /v1/messages — поэтому baseURL без /v1
const client = new Anthropic({
baseURL: "https://darkrouter.ai/api",
apiKey: process.env.DARKROUTER_API_KEY,
});
const msg = await client.messages.create({
model: "anthropic/claude-sonnet-4.6",
max_tokens: 1024,
messages: [{ role: "user", content: "Привет!" }],
});
console.log(msg.content[0].text);
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
base_url="https://darkrouter.ai/api/v1",
api_key="sk-...", # ключ DarkRouter
model="anthropic/claude-sonnet-4.6",
)
print(llm.invoke("Привет!").content)
Streaming
SSE streaming is supported on all endpoints — pass "stream": true. Chunks come in the chosen dialect's format (OpenAI or Anthropic), and usage stats arrive in the final chunk: for OpenAI-compatible requests we enable stream_options.include_usage ourselves to bill the request correctly.
curl -N https://darkrouter.ai/api/v1/chat/completions \
-H "Authorization: Bearer $DARKROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-5.4",
"stream": true,
"messages": [{"role": "user", "content": "Привет!"}]
}'
stream = client.chat.completions.create(
model="openai/gpt-5.4",
messages=[{"role": "user", "content": "Привет!"}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
Routing and fallbacks
What happens to a request inside the router:
- 1From
modela priority chain of routes is built — from the cheapest live provider to the backups. - 2A provider error (5xx, 429, timeout, dropped connection) automatically sends the request to the next one in the chain. The client only sees a successful response.
- 3If the response has already started streaming, fallback is impossible — the bytes have gone to the client. So switching happens only before the first byte of the response.
- 4Only a successful response is billed — at the price of the route that served it. Failed attempts and errors are free.
The chain can be controlled per-key: an API key's settings define routing filters — block providers or groups, require cache support or a full set of parameters, cap the maximum price. If the filters cut off every route, a no_route_matches_filters error is returned.
Prompt caching
Native prompt caching is passed straight through: the Anthropic cache_control markup is forwarded to the provider, and the cache_read / cache_write counters come back in usage and are billed at the cache prices from the catalogue — repeated requests with a shared prefix come out noticeably cheaper.
For aggregators reselling our API there is a cache emulation option (enabled on a specific key in the dashboard): even if the provider has no native caching, cache counters appear in the response — you can bill your own users with them. Emulation does not affect our billing: we always charge by the provider's real usage.
Errors
All errors are returned in a single JSON envelope:
{
"error": {
"type": "insufficient_balance",
"message": "Your balance is depleted. Please top up to continue."
}
}
| HTTP | type | What to do |
|---|---|---|
| 400 | invalid_request |
The request body is not valid JSON. |
| 401 | invalid_api_key |
The key does not exist or is disabled. Check the Authorization header. |
| 402 | insufficient_balance |
Balance depleted. Top it up in your dashboard. |
| 404 | model_not_found |
Unknown model id. Check against the catalogue. |
| 502 | upstream_error |
No provider responded (after all fallbacks). Retry the request. |
| 503 | no_variant_available |
The model has no live route right now. |
| 503 | format_not_supported |
No route of the model accepts this endpoint's format — try a different endpoint. |
| 503 | no_route_matches_filters |
Your key's routing filters cut off every route. Relax them in the key settings. |
| 503 | server_busy |
The service is overloaded. Retry a little later. |
Integrations
Any tool that supports an “OpenAI-compatible” provider will work: set https://darkrouter.ai/api/v1, your key, and a model id from the catalogue. Below are tested recipes for popular tools. Tools can fetch the model list themselves — from GET /api/v1/models.
- Settings → Models → API Keys.
- Enable “Override OpenAI Base URL” and set
https://darkrouter.ai/api/v1. - Paste the DarkRouter key into “OpenAI API Key” and disable the other providers.
- “+ Add model” — add an id from the catalogue, for example
anthropic/claude-sonnet-4.6.
- In the extension settings: API Provider → “OpenAI Compatible”.
- Base URL:
https://darkrouter.ai/api/v1. - API Key — the DarkRouter key.
- Model ID — an id from the catalogue, for example
anthropic/claude-sonnet-4.6.
// settings.json (Zed). Ключ не пишется в настройки: задайте его
// в UI провайдера или переменной DARKROUTER_API_KEY (имя выводится
// из id провайдера: darkrouter -> DARKROUTER_API_KEY).
{
"language_models": {
"openai_compatible": {
"darkrouter": {
"api_url": "https://darkrouter.ai/api/v1",
"available_models": [
{
"name": "anthropic/claude-sonnet-4.6",
"display_name": "Claude Sonnet 4.6",
"max_tokens": 200000,
"max_output_tokens": 64000
}
]
}
}
}
}
# Anthropic SDK добавляет /v1/messages сам — base URL без /v1
export ANTHROPIC_BASE_URL=https://darkrouter.ai/api
export ANTHROPIC_AUTH_TOKEN=sk-...
export ANTHROPIC_MODEL=anthropic/claude-sonnet-4.6
claude
# ~/.codex/config.toml
model = "openai/gpt-5.3-codex"
model_provider = "darkrouter"
[model_providers.darkrouter]
name = "DarkRouter"
base_url = "https://darkrouter.ai/api/v1"
env_key = "DARKROUTER_API_KEY"
wire_api = "chat" # для моделей openai/* можно "responses"
// opencode.json (в корне проекта или ~/.config/opencode/)
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"darkrouter": {
"npm": "@ai-sdk/openai-compatible",
"name": "DarkRouter",
"options": {
"baseURL": "https://darkrouter.ai/api/v1",
"apiKey": "{env:DARKROUTER_API_KEY}"
},
"models": {
"anthropic/claude-sonnet-4.6": { "name": "Claude Sonnet 4.6" },
"openai/gpt-5.4": { "name": "GPT-5.4" }
}
}
}
}
# ~/.continue/config.yaml
models:
- name: DarkRouter Sonnet
provider: openai
model: anthropic/claude-sonnet-4.6
apiBase: https://darkrouter.ai/api/v1
apiKey: sk-...
roles: [chat, edit, apply]
export OPENAI_API_BASE=https://darkrouter.ai/api/v1
export OPENAI_API_KEY=sk-...
# префикс openai/ говорит aider «OpenAI-совместимый endpoint»,
# дальше идёт id модели из каталога
aider --model openai/anthropic/claude-sonnet-4.6
- Admin Panel → Settings → Connections.
- Add an OpenAI API connection: URL
https://darkrouter.ai/api/v1, the DarkRouter key. - The model list is fetched automatically (via
GET /models); extra ones can be hidden in settings.
# librechat.yaml
endpoints:
custom:
- name: DarkRouter
apiKey: "${DARKROUTER_API_KEY}"
baseURL: "https://darkrouter.ai/api/v1"
models:
default:
- openai/gpt-5.4
- anthropic/claude-sonnet-4.6
fetch: true # список моделей подтянется из GET /models
titleConvo: true
- The “OpenAI Chat Model” node → Credentials → create a new OpenAI credential.
- Base URL:
https://darkrouter.ai/api/v1, API Key — the DarkRouter key. - In the Model field enter an id from the catalogue (by default n8n tries to load the list — enter the value manually / via an expression).
- API Connections → API: «Chat Completion», Source: «Custom (OpenAI-compatible)».
- Custom Endpoint:
https://darkrouter.ai/api/v1, API Key — the DarkRouter key. - Enter the Model ID manually, for example
anthropic/claude-sonnet-4.6.
- In settings add a provider of type “OpenAI API Compatible” (in Jan — “Add Provider”, in LobeChat — “Custom Provider”).
- API Host / Base URL:
https://darkrouter.ai/api/v1, API Key — the DarkRouter key. - The model list is fetched automatically; if the client can't — add an id from the catalogue manually.
Support
Didn't find an answer, need an account or special terms for an aggregator — write to us: we'll help bring any tool's integration to a working state.