3 · Token-based auth, end to end
Generate a token, wire it into config, and confirm 401 → 200 from curl, the OpenAI SDK, and the dashboard.
What it is
Bearer-token auth guarding /v1/*, /stats, and /metrics. Requests without a valid Authorization: Bearer <token> get 401 once auth.tokens_env is set; /health stays open regardless (so load balancers never need a token).
Why
Any time routsi is reachable beyond localhost — a shared dev box, a team's LAN, a cloud VM — you want a cheap, low-ceremony gate before anyone can spend your upstream API budget or read /metrics. Tokens compare in constant time and are never logged or printed; only the env-var name lives in config.
Configure
routsi token -n 1 # → prints: rtk_9f3a... echo 'ROUTSI_TOKENS=rtk_9f3a...' >> ~/.config/routsi/.env chmod 600 ~/.config/routsi/.env
auth: tokens_env: ROUTSI_TOKENS
curl -i localhost:8080/v1/chat/completions \
-d '{"model":"auto","messages":[{"role":"user","content":"hi"}]}'
# → HTTP/1.1 401 Unauthorizedcurl -i localhost:8080/v1/chat/completions \
-H 'Authorization: Bearer rtk_9f3a...' \
-d '{"model":"auto","messages":[{"role":"user","content":"hi"}]}'
# → HTTP/1.1 200 OKfrom openai import OpenAI
client = OpenAI(base_url="http://localhost:8080/v1", api_key="rtk_9f3a...")
r = client.chat.completions.create(model="auto", messages=[{"role":"user","content":"hi"}])
print(r.choices[0].message.content)
# → 200, works unmodified — the SDK sends the token as a normal api_keyopen "http://localhost:8080/?token=rtk_9f3a..."
How to adapt
Rotate by generating a new token with routsi token -n 1 and appending it to ROUTSI_TOKENS (comma-separated — old and new both work until you remove the old one). To require client certificates instead of or alongside tokens, layer in mTLS under the same models.yaml. Never put a token value in models.yaml itself — only the env-var name (tokens_env) belongs there.