Self-hosted · OpenAI-compatible · Alpha

TinyRouter

An LLM gateway small enough to read before you hand it your API keys.

One OpenAI-compatible endpoint in front of OpenAI, Anthropic, Gemini, and your own models, with fallbacks you write down in YAML rather than ones a router guesses at. No accounts, no database, no dashboard in the gateway.

curl -fsSL https://tinyrouter.dev/install.sh | sh

Picks the build for your system, checks it against the release's SHA256SUMS, and unpacks it here. Read it first · Quick start

Fallbacks you can read

Give an alias a list of targets. TinyRouter tries them in exactly the order you wrote, and nothing is inferred. Serve your own hardware first and reach for a hosted model only when it cannot answer:

routes:
  assistant:
    - local/qwen3
    - anthropic/claude-sonnet-4-6
Your app"model": "assistant"
TinyRouter
  1. local/qwen3503 · fall back
  2. anthropic/claude-sonnet-4-6200 · served
Retries and fallbacks happen only before a response starts. Once tokens are flowing, that provider is the answer.

What it promises

Fallback in the order you wrote

A target is retried or passed over only for the statuses you list (429 and 5xx by default) or a connection that fails. Never on a heuristic.

No switching mid-stream

Once a provider starts answering, it finishes the answer. Replaying the request elsewhere could duplicate output or tool calls.

No quietly weakened requests

A target that cannot honor part of a request, such as an image format, a strict JSON schema, a reasoning-effort level, or several choices, is skipped before any call. Routing moves on.

Nothing you send is logged

Logs record targets, status codes, and latency. Never prompts, completions, provider keys, or the bearer tokens your clients send.

The whole routing contract fits on one page.

Quick start

  1. Install

    curl -fsSL https://tinyrouter.dev/install.sh | sh

    The script picks the archive for Linux or macOS on x64 or arm64, checks it against that release's SHA256SUMS, and unpacks tinyrouter and tinyrouter.example.yaml into the current directory. It needs no root. Pin a release with | sh -s -- 0.9.0. It is about a hundred lines, so read it before you pipe it into a shell. The executable embeds the Bun runtime, so nothing else needs installing, except apk add libstdc++ on Alpine.

    Or download by hand
    curl -fsSL https://github.com/honzabit/tinyrouter/releases/latest/download/tinyrouter-linux-x64.tar.gz | tar -xz

    Swap linux-x64 for linux-arm64, darwin-x64, or darwin-arm64, and check the download against SHA256SUMS. On macOS, extract with tar rather than Finder, which marks the executable as damaged. On Alpine, use linux-x64-musl or linux-arm64-musl, after apk add libstdc++. Releases up to 0.9.0 have no musl build; use the container below for those.

  2. Configure

    The archive includes an example that matches the executable beside it. Keep the providers you use, add a route like the one above, and export the keys it references:

    cp tinyrouter.example.yaml tinyrouter.yaml
    export TINYROUTER_API_KEY=local-secret
    export ANTHROPIC_API_KEY=sk-ant-...
  3. Run

    ./tinyrouter

    It reads tinyrouter.yaml and listens on 127.0.0.1:8080 until you set server.host. Use --check to validate a configuration without starting.

  4. Call the alias like a model

    curl http://localhost:8080/v1/chat/completions \
      -H 'Authorization: Bearer local-secret' \
      -H 'Content-Type: application/json' \
      -d '{"model":"assistant","messages":[{"role":"user","content":"Hello"}]}'

    Any client that takes an OpenAI base URL works unchanged: point it at http://localhost:8080/v1.

Or run the multi-architecture container:

docker run --rm -p 8080:8080 --env-file .env \
  -v "$PWD/tinyrouter.yaml:/etc/tinyrouter/tinyrouter.yaml:ro" \
  ghcr.io/honzabit/tinyrouter:latest

Providers

Clients always see one shape, OpenAI's /v1/chat/completions and /v1/models, streaming or not. Behind it:

TypeWhat TinyRouter does
openaiPasses Chat Completions through unchanged
openai-compatible The same for any OpenAI-shaped server, such as Ollama or vLLM. No API key required.
anthropic Translates messages, tools, images, and reasoning effort, and normalizes JSON and streams
gemini Translates messages, tools, images, JSON mode, and reasoning effort, and normalizes JSON and streams

Also in the box

Request filters

Block API keys and private keys outright. Redact emails, phone numbers, and card numbers before a request leaves. Scope a filter to cloud providers and your local model still sees everything.

Circuit breaking

Opt in, and a provider that keeps failing is demoted to a last resort for a cooldown instead of costing every request its full timeout.

Stall detection

timeout_ms bounds every gap in a response, not just the wait for headers, so a provider that goes quiet halfway through is cut loose.

Prometheus metrics

Requests, attempts, tokens per model, and how every response body ended. Built in, with no exporter to run.

Named API keys

Give each caller its own key and revoke one by deleting a line. The name appears in the request log, never in metrics.

Closed by default

Listens on loopback until you say otherwise, and can put its observability endpoints behind the same key.

See what it is doing

A single static HTML file in ui/, also attached to every release as tinyrouter-ui.html, 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-sonnet-4-6 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

Use something else when

You needBetter fit
Budgets, virtual keys, per-user spend tracking, an admin UILiteLLM
Guardrails, semantic caching, prompt managementPortkey
A hosted gateway with nothing to operateOpenRouter, Cloudflare AI Gateway
A hundred providers out of the boxLiteLLM or a hosted gateway
Provider switching inside one app whose code you controlAn 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

Changelog

What changed in each release, and which changes broke something

Status page

Serving the page and letting it read your gateway