Architecture
What happens to one request inside kRouter: access check, format detection, routing, translation, token savers, the upstream call and the reply.
This page follows one request from the moment your tool sends it to the moment the reply streams back.
The stages
Your tool
│ POST /v1/chat/completions, /v1/messages or /v1/responses
▼
1. Access check local caller or valid API key
▼
2. Format detection OpenAI, Claude, Responses or Gemini shape
▼
3. Model resolution combo, model alias or provider/model
▼
4. Account choice Zenith (default) or another strategy
▼
5. Translation, savers into the provider's format; RTK and friends
▼
6. Upstream call proxy-aware, per-account concurrency limit
▼
7. Reply translated back, recorded, streamed to you1. Access check
Calls to /v1/* from the machine kRouter runs on pass without a key unless REQUIRE_API_KEY=true. Every other caller needs a valid API key or gets a 401. "Local" is decided from the connection's peer address, never from the Host header, and a request carrying X-Forwarded-For or X-Real-IP (as a reverse proxy adds) always counts as remote. Dashboard pages and /api/* need a dashboard login, and /api/* routes are refused unless they are on an allow-list.
2. Format detection
The path decides first: /v1/responses is the Responses API and /v1/messages is Claude Messages. Otherwise the body decides: input without messages means Responses, contents means Gemini, OpenAI-only fields such as response_format or stream_options mean OpenAI, and Claude-style content blocks mean Claude. The detected format picks the translator used in stage 5.
3. Model resolution
The model field is read as one of:
- a combo name: the combo's list becomes the fallback chain (see Combos & fallback)
<alias>/<model-id>: everything before the first/names the provider- a model alias you defined
kRouter also looks at what the request carries. If it contains an image (or a PDF, audio or video) and the chosen model cannot read it, the capacity adapter brings in a model that can. The image pool is on by default; the others are opt-in.
4. Account choice
kRouter keeps every active account in an in-memory cache, re-synced from SQLite every 10 seconds, so the hot path does not query the database. It drops accounts that are cooling down or banned and picks one of the rest. The default strategy, Zenith, scores accounts on recent success rate and response time, scales down accounts under 30% remaining quota, and takes the highest score. Unless the provider uses round-robin, later turns of the same conversation go back to the same account when it is free, to keep the provider's prompt cache warm. See The Zenith Routing Engine.
5. Translation and token savers
The request is converted from your tool's format into the provider's. For example, OpenAI messages become Claude messages and tools, or Gemini contents with a systemInstruction, and Responses input items become chat messages. Some providers need more: Kiro answers in the binary AWS EventStream format, which kRouter turns back into a normal stream.
The token savers then run on the translated body: RTK (on by default), then Caveman or Ponytail, Headroom and PXPIPE when you have turned them on. They are skipped when a client sends X-9Router-Token-Saver: off, and when Claude Code calls the Claude Code provider directly, so that Anthropic's prompt cache keeps working.
6. Upstream call
The request goes out through proxyAwareFetch, which honours HTTP_PROXY, HTTPS_PROXY, ALL_PROXY, NO_PROXY and per-account proxy pools.
Each account of some providers has a limit on requests in flight, so a busy tool cannot flood it: Claude Code 5, Kiro 4, Codex 3, Antigravity, Gemini CLI and Qoder 2 each, OpenCode Free and MiMo Code Free 1. A request that cannot get a slot waits (5 to 20 seconds, depending on the provider) before it tries another account. Other providers have no limit unless you set a per-account maximum.
Expired OAuth tokens are refreshed before use, and concurrent refreshes of the same token are merged into one.
If the provider returns an error, kRouter classifies it (the full table is in Combos & fallback), stores a cooldown on that account in SQLite so it survives a restart, and tries another account, then the next combo entry.
7. Reply
Streamed chunks are translated back into your tool's format as they arrive; non-streaming replies are converted the same way. Token counts go to the Usage page. With observability on (the default), the last 1,000 requests are also kept for the dashboard's request log; see Security for exactly what that stores.
File layout
In the kRouter repository:
/open-sse/handlers/ # request entry, dispatch
/open-sse/services/ # account picker, combos, fallback, quota
/open-sse/executors/ # per-provider HTTP calls
/open-sse/translator/ # format conversion
/open-sse/rtk/ # token savers
/open-sse/config/ # provider models, error rules
/src/sse/services/ # auth, token refresh
/src/mitm/ # MITM intercept layer
/src/app/api/ # API and dashboard routes
/src/app/(dashboard)/ # dashboard UI (Next.js App Router)Where to go next
- Providers: the 95+ providers by connection type
- Security: credential handling and what is stored
- Deployment: production setup
- Core Concepts: what RTK, MITM and combos do