TinyRouter
A self-hosted, OpenAI-compatible LLM gateway small enough to read before you hand it your API keys. Deterministic fallback, two runtime dependencies, one YAML file.
A gateway sits between your provider credentials and every prompt you send. TinyRouter is roughly
2,200 lines of TypeScript with two runtime dependencies, yaml and zod —
one person can read all of it in an evening. It has no accounts, billing, database, Redis, or
background control plane, and the gateway serves no dashboard.
Install
Standalone executables for Linux and macOS, x64 and arm64. They embed the Bun runtime, so nothing else is needed:
curl -fsSL https://github.com/honzabit/tinyrouter/releases/latest/download/tinyrouter-linux-x64.tar.gz | tar -xz
Or run the container:
docker run --rm -p 8080:8080 --env-file .env \
-v "$PWD/tinyrouter.yaml:/etc/tinyrouter/tinyrouter.yaml:ro" \
ghcr.io/honzabit/tinyrouter:latest
Describe your fallbacks in YAML
Serve your own model first and fail over to a hosted one only when it is down or overloaded. Targets are tried in the order you write them — nothing is inferred:
providers:
local:
type: openai-compatible
base_url: http://localhost:11434/v1
anthropic:
type: anthropic
api_key: ${ANTHROPIC_API_KEY}
routes:
assistant:
- local/qwen3
- anthropic/claude-haiku-4-5
Then call the alias exactly as you would call a model:
curl http://localhost:8080/v1/chat/completions \
-H 'Authorization: Bearer local-secret' \
-d '{"model":"assistant","messages":[{"role":"user","content":"Hello"}]}'
See what it is doing
A single static HTML file ships in ui/ and reads the gateway's own endpoints. It shows
which providers are healthy, which the circuit breaker has demoted, and where your tokens went:
| provider | circuit | models | 2xx | errors | bad bodies |
|---|---|---|---|---|---|
| anthropic | cooling | claude-haiku-4-5 | 0 | 3 | 0 |
| local | healthy | qwen3 | 184 | 0 | 0 |
Bad bodies is the column no status code can give you: a provider that answers
200 and then hangs part-way through the stream. The status line went out long before
anyone knew.
Download it and open it locally rather than using a copy hosted here. It is a page that reads your gateway, and a page served from someone else's domain is a page whose JavaScript you have to trust with it. That is the whole argument for this project, so it would be odd to make an exception.
When to use it
- You want to audit the thing that holds your keys. Two dependencies, no build magic, one file of configuration.
- You refuse to run infrastructure for a proxy. One process. No database, no Redis, no admin UI.
- You run local models with cloud fallback. Serve your own hardware first, fail over only when it cannot answer.
- You need failover you can reason about. The routing contract fits in a paragraph and never switches providers mid-stream.
- Clients you don't control need one endpoint. Anything that accepts an OpenAI base URL gets aliases and failover with no code changes.
Use something else when
| You need | Better fit |
|---|---|
| Budgets, virtual keys, per-user spend tracking, an admin UI | LiteLLM |
| Guardrails, semantic caching, prompt management | Portkey |
| A hosted gateway with nothing to operate | OpenRouter, Cloudflare AI Gateway |
| A hundred providers out of the box | LiteLLM or a hosted gateway |
| Provider switching inside one app whose code you control | An SDK such as the AI SDK — you may not need a proxy at all |
Those are good projects. TinyRouter exists for the case where the feature you want most is being able to understand the whole thing.
Documentation
Reference lives in the repository, versioned alongside the code, so a clone or a source archive describes the commit you actually have rather than whatever is newest:
- Configuration — every key, circuit breaking, request filters, finding model IDs
- Routing contract — what happens per request, and what each adapter translates
- Operations — endpoints, metrics, the status page, shutdown, TLS
- Development — build, test, smoke against live providers, releasing