DarkRouter DarkRouter
Developers · API

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.

https://darkrouter.ai/api/v1 Create API key Model catalogue

Quickstart

1 Get a key

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.

2 Top up your balance

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.

3 Send a request

Drop the base_url and your key into any OpenAI-compatible SDK — or start with the curl below.

bash · first request
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.

EndpointFormatWhen 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:

bash
curl -s https://darkrouter.ai/api/v1/models
json · response fragment
{
  "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 pricing is the price of the cheapest available route — exactly the one the request will take right now.
  • providers lists all active routes of the model, sorted from cheapest to most expensive; available: false means 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).

bash
curl -s https://darkrouter.ai/api/v1/balance \
  -H "Authorization: Bearer $DARKROUTER_API_KEY"
json · response fragment
{
  "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.

bash
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": "Привет!"}]
  }'
python · pip install openai
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)
node · npm i openai
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);
python · pip install anthropic
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)
node · npm i @anthropic-ai/sdk
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);
python · pip install langchain-openai
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.

bash
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": "Привет!"}]
  }'
python
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 model a 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:

json · 402
{
  "error": {
    "type": "insufficient_balance",
    "message": "Your balance is depleted. Please top up to continue."
  }
}
HTTPtypeWhat 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.
Requests that ended in an error are not billed. A positive balance is checked before proxying, while the exact cost is known after the response — so a single request may take the balance slightly negative, which is normal.

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.

CursorIDE
  1. Settings → Models → API Keys.
  2. Enable “Override OpenAI Base URL” and set https://darkrouter.ai/api/v1.
  3. Paste the DarkRouter key into “OpenAI API Key” and disable the other providers.
  4. “+ Add model” — add an id from the catalogue, for example anthropic/claude-sonnet-4.6.
Cline / Roo CodeVS Code
  1. In the extension settings: API Provider → “OpenAI Compatible”.
  2. Base URL: https://darkrouter.ai/api/v1.
  3. API Key — the DarkRouter key.
  4. Model ID — an id from the catalogue, for example anthropic/claude-sonnet-4.6.
ZedIDE
jsonc
// 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
          }
        ]
      }
    }
  }
}
Claude CodeCLI
bash
# 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 CLICLI
toml
# ~/.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"
opencodeCLI
jsonc
// 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" }
      }
    }
  }
}
ContinueVS Code · JetBrains
yaml
# ~/.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]
AiderCLI
bash
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
Open WebUIself-hosted
  1. Admin Panel → Settings → Connections.
  2. Add an OpenAI API connection: URL https://darkrouter.ai/api/v1, the DarkRouter key.
  3. The model list is fetched automatically (via GET /models); extra ones can be hidden in settings.
LibreChatself-hosted
yaml
# 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
n8nautomation
  1. The “OpenAI Chat Model” node → Credentials → create a new OpenAI credential.
  2. Base URL: https://darkrouter.ai/api/v1, API Key — the DarkRouter key.
  3. 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).
SillyTavernself-hosted
  1. API Connections → API: «Chat Completion», Source: «Custom (OpenAI-compatible)».
  2. Custom Endpoint: https://darkrouter.ai/api/v1, API Key — the DarkRouter key.
  3. Enter the Model ID manually, for example anthropic/claude-sonnet-4.6.
Chatbox / LobeChat / Jandesktop chats
  1. In settings add a provider of type “OpenAI API Compatible” (in Jan — “Add Provider”, in LobeChat — “Custom Provider”).
  2. API Host / Base URL: https://darkrouter.ai/api/v1, API Key — the DarkRouter key.
  3. 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.

Telegram support@darkrouter.ai Model catalogue