A tiny local daemon that routes LLM API calls to whichever provider can serve them.
Your apps keep calling api.anthropic.com and friends. router67 quietly takes those hostnames over, reads the model field, and forwards each request to the right upstream — silently falling back through a chain you configure whenever a provider errors or rate-limits.
# build + install: generates a local CA, trusts it, aliases 127.0.67.1,
# and seeds ~/.router67/config.toml
cargo build --release
sudo ./target/release/router67 install
# add your API keys (inline or via env vars) and pick your model aliases
$EDITOR ~/.router67/config.toml
# run (needs root to bind :443)
sudo -E ./target/release/router67 daemonCheck state at any time:
./target/release/router67 statusTear it all down (hosts block, lo0 alias, trusted CA):
sudo ./target/release/router67 uninstallSee config.example.toml. Schema:
intercept_hosts = ["api.anthropic.com", "api.openai.com", "openrouter.ai"]
[daemon]
listen = "127.0.67.1:443" # loopback IP + :443
log_level = "info"
max_connections = 32
[[providers]]
name = "anthropic"
base_url = "https://api.anthropic.com"
auth = { header = "x-api-key", value_env = "ANTHROPIC_API_KEY" }
extra_headers = { "anthropic-version" = "2023-06-01" }
[providers.available_models]
"claude-sonnet-4.6" = "claude-sonnet-4-5-20250929"
[[providers]]
name = "openrouter"
base_url = "https://openrouter.ai/api"
auth = { header = "Authorization", prefix = "Bearer ", value_env = "OPENROUTER_API_KEY" }
[providers.available_models]
"claude-sonnet-4.6" = "anthropic/claude-sonnet-4.6"Each provider's auth must specify exactly one of:
value_env = "OPENAI_API_KEY"— read the secret from that environment variable at request time (recommended)api_key = "sk-..."— hard-coded inline in the config file (convenient for single-user setups; only use if~/.router67/config.tomlis0600-restricted)
Provider order defines the fallback chain. For an incoming request with model = "X", router67 filters the provider list down to those whose available_models contains "X" and tries them in declaration order. available_models["X"] is the name written into the outbound request body.
Failure cascade (next provider is tried) on:
- transport error (DNS fail, TCP reset, TLS handshake fail, …)
- HTTP
429 - HTTP
5xx
Any other status is returned to the client untouched. There are no retries against the same provider — application-layer retries own that.
If every provider in the chain fails, router67 returns 502 with a JSON body listing each attempt.
router67 installgenerates a local root CA under~/.router67/ca/, adds it to the System keychain viasecurity add-trusted-cert, and aliases127.0.67.1ontolo0. All persistent, one-time setup.router67 daemonwrites the/etc/hostsblock mapping eachintercept_hostsentry to the loopback IP, then binds:443on that IP and terminates TLS using on-the-fly leaf certs signed by the CA (resolved from the client's SNI). When the daemon stops (Ctrl-C / SIGTERM), the hosts block is removed — so your network is only rewritten while the daemon is running.- The daemon's own upstream calls use a hand-rolled UDP DNS stub pointed at
1.1.1.1/8.8.8.8, bypassing/etc/hostsso dialingapi.anthropic.comresolves to the real origin instead of looping back.
src/
main.rs CLI entry
cli.rs clap subcommands
config.rs TOML schema + load
providers.rs Provider struct + auth helper
ca.rs CA gen + SNI-keyed leaf cert resolver
hosts.rs /etc/hosts managed-block editor
install.rs install / uninstall (CA trust + hosts)
dns.rs minimal UDP DNS stub (bypasses /etc/hosts)
pool.rs bounded thread pool
http_io.rs sync HTTP/1.1 request parse + response write helpers
daemon.rs TcpListener + rustls accept loop + dispatch
router.rs model extraction + rewrite
proxy.rs upstream attempt via ureq + fallback cascade
MVP. macOS only (hosts file + security CLI integration). Sync (no tokio) with a bounded thread pool, HTTP/1.1 only.
- Linux / Windows support
- TUI config + OAuth login
- OAuth flows
- Tray icon
- Config hot-reload
- launchd / systemd units