Skip to main content
Glama

Urai Router

One keys.txt → every LLM provider, one local endpoint, automatic key rotation.

Urai Router is a small, dependency-free Python gateway for people who keep several LLM API keys. It reads a plain text file, auto-detects each provider, discovers the models each key unlocks (including free OpenCode Zen models), rotates keys on 401, 403, 429 and 5xx, and exposes everything as:

  • an OpenAI-compatible HTTP API at http://127.0.0.1:8787/v1

  • an MCP stdio server for OpenCode, Kiro CLI and any MCP client

  • an Agent Skill (SKILL.md) that teaches an agent how to use it

  • generated OpenCode and Kiro configuration so both tools understand the provider and model list without manual editing

No database. No Node modules. No Docker. Python 3.9+ standard library only.

urai-router init                      # create ~/.config/urai-router/keys.txt
$EDITOR ~/.config/urai-router/keys.txt
urai-router setup all --start         # OpenCode + Kiro + skill, start proxy
urai-router doctor

Why this exists (audit first)

The project started with an audit of existing open-source routers. The summary is in docs/AUDIT.md. In short:

Project

Stars

License

What it is

Why not just use it

BerriAI/litellm

62k

MIT-ish

Full AI gateway / SDK

Heavy, needs Postgres/Redis for teams; not a local MCP+skill

songquanpeng/one-api

37k

MIT

Key redistribution server

Web UI + DB, aimed at resale, not CLI agents

QuantumNous/new-api

51k

AGPL-3.0

Fork of one-api

AGPL, DB, server-first

musistudio/claude-code-router

38k

MIT

Claude Code routing

Node/TS, Claude-shaped, not MCP-native

jasonxu114514/opencode2api

1.4k

GPL-3.0

OpenCode Zen proxy

Go, GPL, Zen-only, no keys.txt/MCP config

routatic/proxy

983

AGPL-3.0

Multi-upstream proxy

AGPL, Go, no MCP/skill surface

None of them combine all four requirements: plain keys.txt, free OpenCode Zen discovery, MCP-native agent tools, and automatic OpenCode/Kiro config. Urai Router is deliberately the small tool that does those four things.


Related MCP server: io.github.0xzr/freellmpool

Install

git clone https://github.com/etside/Urai-router.git
cd Urai-router
python3 -m pip install --user -e .        # or: pipx install .
urai-router --version

For a zero-install run: python3 -m urai_router --help from the repo.


keys.txt

Default search order:

  1. --keys PATH

  2. $URAI_KEYS_FILE

  3. ./keys.txt

  4. ~/.config/urai-router/keys.txt

  5. ~/.config/urai/keys.txt

  6. ~/.urai/keys.txt

  7. ~/urai-keys.txt

  8. ~/keys.txt

# provider:key    (provider prefix is optional when the key is recognizable)
opencode:sk-...
openrouter:sk-or-v1-...
groq:gsk_...
deepseek:sk-...
anthropic:sk-ant-...
google:AIza...
xai:xai-...

# labels and weights control rotation
opencode:sk-...        # label=main weight=3
opencode:sk-...        # label=backup weight=1
  • # starts a comment; label=, weight=, @label, *weight are parsed.

  • ${ENV_VAR} is expanded.

  • Duplicate keys are ignored.

  • An unlabelled key is auto-detected by prefix, then by a 1-token probe.

  • keys.txt is hot-reloaded when its mtime changes.

  • The file is never sent anywhere and never written into logs; keys list shows masked values only.

Keep it private:

chmod 600 ~/.config/urai-router/keys.txt

Providers understood automatically

Registered out of the box:

opencode (Zen), openrouter, openai, deepseek, groq, anthropic, google (Gemini OpenAI endpoint), xai, nvidia, moonshot, mistral, together, fireworks, cerebras, huggingface, zai, minimax, deepinfra, perplexity, github models, nebius, sambanova, cohere, and local ollama / lmstudio / llamacpp.

Add any OpenAI-compatible provider in ~/.config/urai-router/config.json:

{
  "providers": {
    "my-gateway": {
      "name": "My Gateway",
      "base_url": "https://gateway.example.com/v1",
      "api_style": "openai",
      "default_models": ["my-model"]
    }
  }
}

Then use my-gateway:key... in keys.txt.

OpenCode Zen free models

GET https://opencode.ai/zen/v1/models is public and returns pricing metadata. Urai Router reads it, marks cost.text.input.price == 0 as free, and reads the per-model api block so OpenAI-style models use /chat/completions while Anthropic-style models use /messages (translated both ways, including tools and SSE streaming).

You still need one Zen API key to call the models; the catalogue itself is public. Free rows are time-limited by OpenCode, so run urai-router sync periodically.


Routing

Model reference syntax:

Reference

Meaning

auto / best

free models first, then any usable model

free / urai/free

free models only

opencode/deepseek-v4.1-flash-free

explicit provider + model

openrouter/vendor/model:free

OpenRouter model ids keep their slash

deepseek-chat

resolved across the catalogue when unambiguous

alias

any key in config.json → aliases

Failover policy:

Upstream

Action

401 / 403

mark key invalid, rotate immediately

429

honour Retry-After, exponential cooldown, rotate

408 / 425 / 5xx / 529

short exponential cooldown, rotate

400 / 422

request or model problem — do not blame the key

404

try the model's alternative endpoint, then report

Cooldowns and counters persist in ~/.local/state/urai-router/keys-state.json (no key material inside).


Use as an OpenAI-compatible API

urai-router serve              # foreground: 127.0.0.1:8787
urai-router proxy start        # background, writes pid/port + proxy.log
urai-router proxy status
urai-router proxy stop
curl -s http://127.0.0.1:8787/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model":"auto","messages":[{"role":"user","content":"say pong"}]}'
from openai import OpenAI

client = OpenAI(base_url="http://127.0.0.1:8787/v1", api_key="urai-local")
print(client.chat.completions.create(
    model="auto",
    messages=[{"role": "user", "content": "hello"}],
).choices[0].message.content)

Endpoints:

  • GET /health

  • GET /v1/models (?free=1, ?provider=opencode, ?refresh=1)

  • POST /v1/chat/completions (streaming supported)

  • GET /v1/urai/stats · /v1/urai/keys (masked) · /v1/urai/providers

  • GET /v1/urai/explain?model=auto

  • POST /v1/urai/reload · /v1/urai/test

Bind to 127.0.0.1 by default. If you expose the port, set a token:

urai-router serve --token "$(openssl rand -hex 24)"

OpenCode integration

urai-router setup opencode --set-default

This merges the following into ~/.config/opencode/opencode.jsonc (or .json), preserving existing settings and writing a timestamped backup:

  • a custom provider urai-router using @ai-sdk/openai-compatible

  • options.baseURL = http://127.0.0.1:8787/v1

  • a models map generated from every usable/free model (context + output limits, ★ marks free rows)

  • an mcp.urai-router local server entry

Then restart OpenCode. Models appear as urai-router/opencode/deepseek-v4.1-flash-free, urai-router/auto, etc. --set-default points model and small_model at the first free Zen model.

Refresh after adding keys or when Zen changes its free list:

urai-router sync

Project-local config instead of global:

urai-router setup opencode --project

Dry run and diff-friendly export:

urai-router setup opencode --dry-run
urai-router export opencode

Kiro CLI integration

urai-router setup kiro

Writes:

  • ~/.kiro/settings/mcp.json → mcpServers.urai-router (stdio, urai-router mcp)

  • ~/.kiro/agents/urai-router.json → an agent that includes the MCP server and the urai_* tools, with @urai-router in tools

Verify:

kiro-cli mcp list
kiro-cli chat --agent urai-router

Kiro keeps its own model backend; the router is used through the MCP tools (urai_chat, urai_models, urai_health, ...). That is intentional: Kiro does not expose a custom OpenAI provider setting.


MCP server

urai-router mcp speaks JSON-RPC over stdio (newline-delimited, MCP 2025-06-18, also compatible with 2025-03-26 / 2024-11-05).

Tools:

Tool

Purpose

urai_chat

chat completion through the router, key rotation included

urai_models

merged catalogue, free_only supported

urai_keys

masked health / cooldowns

urai_health

keys + catalogue + proxy status

urai_stats

alias for health

urai_explain

show routing for a model

urai_refresh

reload keys and catalogues

urai_ensure_proxy

start the local HTTP proxy and return its URL

urai_check

probe keys

urai_setup

generate OpenCode/Kiro/skill config (dry_run default true)

Resources: urai://config, urai://keys, urai://providers, urai://models, urai://stats.

MCP client snippet (OpenCode style):

{
  "mcp": {
    "urai-router": {
      "type": "local",
      "command": ["urai-router", "mcp"],
      "enabled": true
    }
  }
}

MCP client snippet (Kiro style):

{
  "mcpServers": {
    "urai-router": {
      "command": "urai-router",
      "args": ["mcp"],
      "disabled": false,
      "timeout": 30000
    }
  }
}

Skill

urai-router setup skills copies the Agent Skill to:

  • ~/.config/opencode/skills/urai-router/SKILL.md

  • ~/.agents/skills/urai-router/SKILL.md

  • ~/.claude/skills/urai-router/SKILL.md

Use --project to also write .opencode/, .agents/ and .claude/ variants. The skill teaches an agent when to call urai_*, how to route free models, and how to report failover without leaking keys.


CLI reference

urai-router init [--path PATH] [--force]
urai-router serve [--host H] [--port P] [--token T] [--no-detect] [--no-preload]
urai-router mcp
urai-router keys list|check|detect
urai-router models [--free] [--provider P] [--refresh] [--json]
urai-router setup [opencode|kiro|skills|all] [--project] [--dry-run] [--set-default] [--start]
urai-router sync [--set-default]
urai-router doctor [--json]
urai-router test [--model M] [--prompt TEXT] [--json]
urai-router explain [--model M]
urai-router export [opencode|kiro|env|mcp|providers]
urai-router proxy start|status|stop
urai-router version

Global flags: --keys, --config, --state-dir, -v.


Configuration

~/.config/urai-router/config.json (created on demand):

{
  "host": "127.0.0.1",
  "port": 8787,
  "token": "",
  "max_attempts": 8,
  "default_model": "auto",
  "prefer_free": true,
  "catalog_ttl": 900,
  "zen_ttl": 300,
  "request_timeout": 120,
  "cooldowns": {
    "rate": 60,
    "invalid": 1800,
    "server": 20,
    "network": 15,
    "max": 3600
  },
  "aliases": {
    "fast": "opencode/deepseek-v4.1-flash-free"
  },
  "providers": {},
  "proxy": { "cors": true, "log_requests": true }
}

State and cache:

  • ~/.local/state/urai-router/keys-state.json — health, cooldowns (no secrets)

  • ~/.local/state/urai-router/proxy.json — pid, port, url

  • ~/.local/state/urai-router/proxy.log

  • ~/.cache/urai-router/catalog.json — model metadata cache


Architecture

keys.txt ──▶ KeyStore ──▶ Router ──▶ Provider adapter ──▶ upstream
                │           │                              │
                │           ├─ model resolution            ├─ OpenAI chat/completions
                │           ├─ retry / cooldown            └─ Anthropic messages (+SSE)
                │           └─ free-first auto
                ▼
      proxy (:8787/v1)   MCP stdio   OpenCode config   Kiro config   SKILL.md
  • urai_router/providers.py — registry, catalogue parsing, request building

  • urai_router/keys.py — parsing, rotation, cooldowns, auto-detection, state

  • urai_router/router.py — model resolution, failover, streaming

  • urai_router/proxy.py — HTTP server + background process management

  • urai_router/mcp_server.py — MCP tools/resources/prompts

  • urai_router/config_writer.py — OpenCode/Kiro/skill writers

  • urai_router/adapters.py — OpenAI ↔ Anthropic translation


Security

  • Keys are read from a local file and used only in Authorization / x-api-key headers to the provider you configured.

  • Logs and API responses only contain masked keys or labels.

  • keys-state.json and catalog.json never contain key material.

  • .gitignore excludes keys.txt, .env, and state.

  • The proxy binds to loopback by default; add --token if you change that.

  • doctor warns when the keys file is group/world-readable.

Report vulnerabilities privately; see SECURITY.md.


GitHub Actions

  • CI (.github/workflows/ci.yml): compile, unit tests against a local fake upstream, package build, zipapp smoke test. No network keys required.

  • Release (.github/workflows/release.yml): on a v* tag, builds sdist, wheel and urai-router.pyz, then creates a GitHub release with GH_TOKEN (the token supplied by GitHub Actions). Manual workflow_dispatch supported.

Create the repo and push (what was used here):

gh repo create etside/Urai-router --public --source=. --remote=origin --push

Troubleshooting

Symptom

Fix

no usable route

add keys, then urai-router keys list and doctor

models empty

urai-router models --refresh; check network

OpenCode shows no models

urai-router sync, restart OpenCode

all keys cooling

add keys or wait; check urai-router keys list

401 from every key

urai-router keys check, fix/remove invalid keys

Zen free list changed

urai-router sync

Kiro doesn't see MCP

kiro-cli mcp list; re-run setup kiro

proxy not reachable

urai-router proxy status; see proxy.log


License

MIT. See LICENSE.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    A
    maintenance
    Exposes a unified pool of free LLM chat routes behind one OpenAI-compatible endpoint, letting MCP clients browse available providers and models, check which free tiers are usable right now, and send chat or agent requests with automatic failover. Works without credentials whenever an enabled keyless route is available.
    -
  • F
    license
    Not graded
    quality
    A
    maintenance
    Enables access to 178 pooled free-tier and keyless LLM chat routes through a single OpenAI-compatible endpoint, usable by coding agents, local proxies, and other MCP clients. It automatically selects only accessible enabled routes, fails over on rate limits or outages, and can start without credentials when a keyless provider is available.
    -
  • F
    license
    Not graded
    quality
    A
    maintenance
    Publishes an MCP server that pools many free LLM providers — including keyless routes — behind one local OpenAI-compatible endpoint, letting coding agents and compatible clients discover, route to, and fail over across available chat models. It also exposes tools for status, model inventory, and capacity/health inspection.
    -