On this page
Run the router yourself
The Haarness Router puts one OpenAI-compatible endpoint in front of your own provider accounts, on your own machine or inside your network. It is licensed under MIT (Open source), and the managed router in Haarness Cloud is built on the same code. You do not need a Cloud account.
The router's source is not public yet. Its repository opens soon. Until then, write to [email protected] if you want to run it.
Managed router or your own?
| Managed router (Cloud) | Your own router | |
|---|---|---|
| Provider accounts | None needed, or your own keys per router | Your own |
| Who bills you | Haarness, in credits; or your provider, with your keys | Each provider, directly |
| Usage history, spending caps, per-key model lists | Built in | Not included |
| Runs inside your own network | No | Yes |
| Price | Credits, at published rates | Free software; you pay your providers |
Both speak the same API, so switching later only means changing the base URL and the key.
If you only want to use your own provider keys, you may not need to self-host: a Cloud router can use Your provider keys instead of credits. See Models and routers.
Get a first router running
You need the router's source, a current Rust toolchain (install it with rustup) and a C compiler.
Build it, in the router's source folder:
cargo build --releaseWrite a
config.tomlthat names the providers you have keys for, and the environment variables that hold the keys:[providers.openai] kind = "openai" api_key_env = "OPENAI_API_KEY" [providers.anthropic] kind = "anthropic" api_key_env = "ANTHROPIC_API_KEY"List only providers whose key you have: if a variable named here is not set, the router does not start.
Start it with your keys in the environment and a key of its own for your clients:
export OPENAI_API_KEY=… ANTHROPIC_API_KEY=… export HAARNESS_ROUTER_KEY=$(openssl rand -hex 32) ./target/release/haarness-router --config config.toml --require-keyIn the same shell, send it a request:
curl http://127.0.0.1:8080/v1/chat/completions \ -H "Authorization: Bearer $HAARNESS_ROUTER_KEY" \ -H 'Content-Type: application/json' \ -d '{"model":"openai/gpt-5-nano","messages":[{"role":"user","content":"Hello"}]}'
The router listens on http://127.0.0.1:8080, so only this machine can reach it. Models are named
provider/model, and GET /v1/models lists the ones your keys can use. Point any OpenAI-compatible client at
http://127.0.0.1:8080/v1 with HAARNESS_ROUTER_KEY as the API key. Its dashboard is at
http://127.0.0.1:8080/ui: models, routes, live metrics and a playground.
To use its models in Desktop, the terminal UI or the CLI, connect the router.
Add providers, aliases and fallback
Each [providers.<name>] section takes a kind:
kind |
Notes |
|---|---|
openai, anthropic, gemini |
Native adapters |
groq, mistral, xai, deepseek, together, fireworks |
Presets for OpenAI-compatible providers |
amazon-bedrock |
A Bedrock API key; set base_url for a region other than us-east-1 |
openai-compatible |
Any other OpenAI-compatible server, such as a local model server: set base_url |
[providers.inhouse]
kind = "openai-compatible"
base_url = "https://llm.example.internal/v1"
api_key_env = "INHOUSE_API_KEY"A plain http:// address is refused unless the section also sets allow_insecure_http = true.
An alias gives clients one model name that tries several models in order:
[aliases.fast]
targets = ["openai/gpt-5-nano", "anthropic/claude-haiku-4-5-20251001"]Clients ask for the model fast. The router moves to the next target only when one cannot be reached, or answers
408, 429 or a 5xx before it has sent anything.
config.all-providers.example.toml in the router's source shows every provider type. Its README and docs/
folder cover model capabilities, --set overrides and the dashboard.
Run it on a server
Before other machines can reach the router:
Require a key with
--require-key, as above.Protect the dashboard, or turn it off. By default
/uihas no password, and it can create router keys: anyone who can open it can make a key for themselves, even with--require-key. Give it a username and password from two environment variables:[web_ui] basic_auth_username_env = "HAARNESS_UI_USERNAME" basic_auth_password_env = "HAARNESS_UI_PASSWORD"Or start the router with
--set web_ui.enabled=falseto serve the API only.Put HTTPS in front with a reverse proxy that terminates TLS. Haarness connects to a router on another machine only over HTTPS, and the dashboard's password is only safe over HTTPS.
Listen on another address with
--host 0.0.0.0,--port 9090or--bind 10.0.0.5:8080. Keep the default127.0.0.1when the proxy runs on the same machine.
What it does with your keys
- Provider keys come only from environment variables. The config file and
--setname the variables; there is no setting for a key's value. The router reads them once at startup and never returns or logs them. - Router keys stay with the router. The key a client sends is checked and never forwarded to a provider. Keys created in the dashboard live in memory and are gone after a restart.
- Callers cannot bring their own provider key unless you set
allow_upstream_key_header = trueunder[auth]. Then a request may carry one inX-Upstream-API-Key, and only for an exactprovider/model. - It keeps nothing. No prompts, no usage history, no billing. It logs one line per request, without the prompt, headers or keys.
- The config is strict. An unknown setting, a typo or a missing environment variable stops the router at startup, rather than failing a request later.
Flags
| Flag | Environment variable | Effect |
|---|---|---|
--config FILE |
HAARNESS_ROUTER_CONFIG |
The config file, config.toml by default; - reads it from standard input |
--require-key |
Require the key in HAARNESS_ROUTER_KEY |
|
--no-auth |
Accept requests without a key, whatever the config says | |
--host, --port, --bind |
HAARNESS_ROUTER_HOST, _PORT, _BIND |
Listen address, 127.0.0.1:8080 by default |
--set PATH=VALUE |
Change one config value; repeatable | |
--unset PATH |
Remove one config value or section; repeatable |
| Path | What it serves |
|---|---|
/v1/chat/completions, /v1/models |
The OpenAI-compatible API |
/ui |
The dashboard |
/metrics |
Prometheus metrics; they need a router key when keys are required |
/healthz, /readyz |
Health checks, always open |
Next
Connect it to Haarness: Haarness Router.