Free, keyless coding models for every coding harness, through one local daemon. Works without accounts or API keys.
npm i -g bansos-routerStart the daemon and point your coding harness at it:
bansos start --bg # detached daemon on 127.0.0.1:17070
bansos setup opencode # configure any supported harnessFor pi, install the companion extension (extensions/pi):
pi install npm:pi-bansos-routerSupported harnesses for bansos setup: claude-code, aider, opencode, codex, hermes, goose, openclaw, antigravity, jcode, 9router, continue, cline, roo, and omp. See docs/harnesses.md for harness-specific details.
OMP (Oh My Pi) can connect two ways, pick one:
bansos setup ompwrites abansosprovider into~/.omp/agent/models.yml(start the daemon yourself withbansos start).- The pi extension loads in OMP too (it is a pi fork):
omp plugin install pi-bansos-routerregisters abansosrprovider and auto-manages the daemon.
Using both is harmless but redundant: the IDs differ (bansos vs bansosr), so you get two providers listing the same models. Prefer one.
Open http://127.0.0.1:17070 in your browser for the Web UI dashboard: model catalog with live latency ping, harness setup (preview or apply in one click), relay manager, playground, activity and usage, daemon logs, and settings (security mode, catalog refresh, port and bind, restart, health check).
bansos autostart on # also: off, statusThe daemon then starts whenever you log in, through the system's own service manager: a systemd user service on Linux, a LaunchAgent on macOS, a script in the Startup folder on Windows. On Linux and macOS it is also restarted if it crashes, while bansos stop still stops it for good. A daemon you are already running is left alone and the service takes over at the next login. The same switch is in the dashboard's Settings tab. On Linux, loginctl enable-linger $USER starts it at boot, before you log in.
These hold in every mode (not just strict):
- CORS only for loopback origins: a page served from a non-loopback origin gets no CORS headers, so it can neither read nor write the daemon.
- Host header validated: loopback clients must present a loopback (or own-interface) Host header; anything else gets 403. This blocks DNS rebinding where a hostile page resolves to
127.0.0.1. - Relay probe & mutation are SSRF-guarded: probes may only target the active/saved relay or a public
https://host; literal loopback, private, and link-local IPs (e.g.169.254.xmetadata) are rejected unless explicitly saved. Relay URLs must behttp(s)://without credentials.
Run the daemon in a container instead of installing globally:
docker build -t bansos-router .
docker run -d --name bansos -p 17070:17070 -v bansos-data:/home/node/.bansos bansos-routerThe container runs as the non-root node user (uid 1000); state lives in /home/node/.bansos inside the container, which is what the volume above mounts.
Or with compose:
docker compose up -d- Image is a multi-stage build on node:22-alpine (~58 MB compressed), CLI + daemon + Web UI included, zero runtime dependencies.
- The container runs the daemon in the foreground (Docker owns the lifecycle) bound to
0.0.0.0, with tini for correct signal forwarding and a healthcheck on/healthz. - State persists across restarts in the
bansos-datavolume; point your harnesses athttp://127.0.0.1:17070as usual. docker compose logs -freplacesbansos logsinside the container.
| Command | Purpose |
|---|---|
bansos start [--bg] [--port N] [--bind H] [--unsafe-allow-non-loopback] |
Run the daemon (foreground, or detached with --bg) |
bansos logs [--activity] |
Tail the daemon log in real time (every mode: daemon always logs to ~/.bansos/logs/bansosd.log); --activity prints the structured request feed shown in the web UI "Activity" tab |
bansos stop [--all] |
Stop the daemon recorded in state.json; --all stops every bansos daemon on the machine |
bansos status [--json] |
Daemon status (port, model count, alive models); reports every running daemon on the auto-bump range (17070-17090) |
bansos models [--json] |
List live catalog from /v1/models |
bansos ping [model] [--json] |
Probe live latency and rate-limit status of all models (or a specific model) |
bansos refresh |
Ask the daemon to re-run health checks now |
bansos usage [--json] |
Request counts and token totals per model and upstream (last 60 days) |
bansos setup <harness...> [--model <id> | --models <id,...> --upstream <id,...>] [--dry-run] [--undo] |
Write, update, or undo harness configs |
bansos relay <on|off|status|url|use|list|remove> |
Manage relay egress |
bansos autostart <on|off|status> [--json] |
Start the daemon when you log in (systemd / launchd / Startup folder) |
bansos doctor |
Diagnose daemon reachability and harness config validity |
bansos --version |
Print version |
bansos <command> --help / bansos help <command> |
Per-command usage, defaults, exit codes, examples |
bansosd |
Alias for the daemon (e.g. bansosd --bg) |
Strict mode is opt-in and fail-closed. Add a security block to ~/.bansos/config.json and explicitly list every provider that may receive requests:
{
"port": 17070,
"bind": "127.0.0.1",
"security": {
"mode": "strict",
"dlp": "redact",
"allowedUpstreams": ["zen"],
"allowCrossProviderFailover": false
}
}mode is case-insensitive. Any value other than "strict" or "normal" runs in normal mode, and the daemon log and bansos doctor flag it. The same settings can be changed from the dashboard's Settings tab; a security change applies live.
With mode: "strict":
- Non-loopback binds rejected unless
--unsafe-allow-non-loopbackpassed. - Relay egress, probing, and mutation disabled across CLI, API, Web UI.
- Only exact upstreams in
allowedUpstreamsreceive requests (empty list blocks all). Models from the other upstreams are left out of/v1/models,bansos setupand the smart default; the dashboard still lists them, marked blocked. - Cross-provider failover disabled.
- Sensitive values and raw upstream errors suppressed from logs.
- DLP keeps pasted credentials from leaving: API keys, PATs, private keys, and credential assignments in a request bound for an external upstream (zen/kilo/llm7/ovh) are replaced with
[REDACTED:<type>]before it is sent, so the rest of the message still goes through and the session keeps working. Set"dlp": "block"to reject such requests with HTTP 422 instead (note that a blocked secret stays in the session history, so every later request in that session is blocked too). Loopback (self-hosted) gateways are exempt, since their own keys are legitimately part of the request body.
- One local daemon on
127.0.0.1:17070, started withbansos start. It speaks three wire protocols: OpenAI Chat Completions, Anthropic Messages, and OpenAI Responses (Codex CLI). - Keyless free upstreams only: OpenCode Zen, KiloCode gateway, LLM7, and OVHcloud AI Endpoints (anonymous tier).
- OpenCode Zen note: Zen uses Cloudflare JA4 TLS fingerprinting and session affinity. It requires either Bun runtime or an OpenCode installation (installed via
curl -fsSL https://opencode.ai/v2/install | bash, package managers, ornpm i -g @opencode/cli). When OpenCode is installed, bansos-router automatically launches its background service. If neither is available, requests to Zen models transparently fail over to Kilo/LLM7/OVH free models.
- OpenCode Zen note: Zen uses Cloudflare JA4 TLS fingerprinting and session affinity. It requires either Bun runtime or an OpenCode installation (installed via
bansos setup <harness>writes config for Claude Code, Aider, OpenCode, Codex, Hermes, Goose, OpenClaw, Antigravity, JCode, 9Router, Continue, Cline, Roo Code, and OMP.- Web UI dashboard served at
http://127.0.0.1:17070/. The catalog shows per-model capabilities (Think / Vision badges) and live latency; the activity tab tracks requests, tokens, latency, and error rate; the settings tab edits~/.bansos/config.jsonand restarts the daemon. - The pi extension (
pi-bansos-router) registers thebansosrprovider, so every free model shows up in pi's/modelpicker. There is also a/bansosrcommand for status. If the daemon is not running when pi starts, the extension starts it and stops it again on exit. - The catalog is health-checked: live
:freemodels replace stale seeds on a timer. Relay egress can route around rate limits, and there is a per-IP rate limiter. - If a model is rejected with
401/403/429/5xx, the daemon auto-fails over to the closest equivalent model on a different upstream (same reasoning level, context window, and effort capability), retrying up to two extra candidates before surfacing an error. Request duration (durationMs) is logged on every completion and rejection. - A model that answers
429is parked for a cooldown, so the next request starts on a fallback instead of spending another round trip to learn the same thing.Retry-Aftersets the duration when the upstream sends one, otherwise it is a minute, capped at fifteen. Parked models stay listed in/v1/modelsand are still used when nothing else qualifies. - A
401/403means the upstream will not serve that model from where the request egressed (OpenCode Zen, for instance, returns403 This service is not available in your regionwhen Cloudflare routes through a region its providers block). Retrying cannot help, so the model is parked for 30 minutes and the request fails over instead of returning the error. Sendx-bansos-no-failover: 1when you want the model's own status back. The upstream's own message is logged alongside the status.
By default, bansos setup automatically configures intelligent defaults per harness:
- Claude Code: Maps tiers automatically (
haiku-> fast non-reasoning,sonnet-> daily reasoning,opus-> highest-capacity reasoning). - Multi-model harnesses (
opencode,goose,openclaw,continue): Registers all available models with the smart default (currentlymuse-spark-1.3-contributor-free) as primary. - Discovery harnesses (
9router,omp): Register a provider that lists models from/v1/modelsitself. - Single-model harnesses (
aider,codex,hermes,antigravity,jcode,cline,roo): Same smart default. Pass--model <id>to pin a specific model.
To write only some models, pass --models <id,...> and/or --upstream <id,...> (every model of that upstream), for example bansos setup opencode --upstream zen --models kilo-auto/free. Claude Code then picks its Haiku, Sonnet and Opus tiers from that set. The dashboard's Agent Setup tab has the same picker, with a checkbox per model and per upstream.
The model list changes as upstreams rotate their free tiers, so it lives on its own page: ihsan-ramadhan.github.io/bansos-router. A GitHub Action runs the daemon's own catalog check every six hours and publishes the result there, with reasoning, vision, context and max output for each model, and as models.json. It checks from a GitHub datacenter, so run bansos models or bansos ping to see what your machine can reach.
Muse Spark answers only on the Responses API (/v1/responses) and returns HTTP 500 on chat completions. bansos-router translates the upstream leg both ways, so Muse works from any harness: chat completions, Responses, and Anthropic clients all see their usual wire format, tool calls included.
Notes:
- Kilo's
:freemodels are liveness-gated: the daemon re-checks the kilo catalog on a timer and drops models that are no longer offered free. - Zen seeds are re-checked against
GET /v1/modelson refresh. The listing is not exhaustive (some servable promos are never listed), so an unlisted seed is probe-verified keyless before being kept; a retired promo (e.g.hy3-free, gone and answering 401) disappears automatically. - LLM7 models are filtered dynamically by
usage_based_only: false(tier turbo);defaultandfastare stable selectors.protier is paid-only and excluded. - OVH's anonymous tier allows only 2 requests per minute per IP, so its models mostly serve as failover targets. The
ovh/prefix is local to bansos and stripped before the request leaves; a bare id likegpt-oss-120bresolves too.
For contributors working from source:
git clone https://github.com/ihsan-ramadhan/bansos-router
cd bansos-router
npm install
npm run build # builds Web UI (Vite) + CLI (esbuild), outputs dist/
npm link # make `bansos`/`bansosd` available globally
npm run typecheck # tsc --noEmit
npm test # node:test
npm run dev # run the bansos CLI from source
npm run dev:daemon # run the daemon from source
npm run dev:ui # run Vite dev server for Web UI- Architecture
- Wire protocols & translation
- Harness integration
- Upstreams, catalog & relay
- Contributing
GPL-3.0-only. See LICENSE.