ZEN·PROXY
a local OpenAI-compatible proxy that unlocks opencode's anonymous free tier — for any agent.
Zen Proxy is a zero-dependency, locally-run OpenAI-compatible proxy that unlocks opencode's Zen free tier for any coding agent — not just opencode.
opencode gives you free models like
mimo-v2.5-free,big-pickle, andnemotron— but only to requests that look like they come from opencode itself: the rightUser-Agentand anx-opencode-sessionheader. Most agents force their own identity and get shut out with400 MissingSessionID("OpenCode's free tier can only be used in OpenCode") or429 FreeUsageLimitError. Zen Proxy quietly speaks for them: it injects the correctUser-Agent, mints stable per-client session IDs, forwards your real IP, and re-exposes everything as a standard/v1/chat/completions+/v1/modelsAPI.The result: whatever tool you love — Cline, Roo Code, Continue, Aider, mimo, or a plain
curl— can now ride opencode's free models with zero accounts, zero API keys, and zero config beyond abaseURL.
What it enables:
- Free OpenAI-compatible models outside opencode — bring your own agent, keep the free tier
- Anonymous access with no accounts or keys (
Bearer public), or bring your own Zen key (BYOK) for your own quota - Smart model fallback — when one free model is saturated, it rolls to the next
- Model aliases — call them
gpt-4oorclaude-3-5, get routed to free models - Self-updating — tracks new opencode releases (auto User-Agent) and a GitHub Action keeps the shipped free-model list current as models come and go
- Per-IP fairness — real client IPs are forwarded (local clients fall back to your machine's real IP, same quota bucket as opencode direct)
- A retro-zine management dashboard — stats, one-click model tests, live config, and logs at
http://127.0.0.1:8787/ - One-file install on Linux, macOS, and Windows with a single
curl
opencode's free -free models (mimo-v2.5-free, big-pickle, nemotron, …) are only served to requests that mimic the real client. Since opencode started requiring an x-opencode-session header, anything without it gets 400 MissingSessionID: "OpenCode's free tier can only be used in OpenCode" — even with a valid API key. Most coding agents — like the mimo CLI fork — force their own identity and get slammed with that error (or 429 FreeUsageLimitError).
zen-proxy injects the correct User-Agent and a stable synthetic x-opencode-session per client, then re-exposes everything as a plain OpenAI API. No accounts, no API keys, no source patches.
works with: mimo cli, Cline, Roo Code, Continue, Aider, opencode forks — anything that lets you set a
baseURL+apiKey.
▸ feature list
- OpenAI-compatible API —
/v1/chat/completions(stream + non-stream),/v1/models,/v1/responses - UA unlock — injects
User-Agent: opencode/1.18.30plus a stable per-clientx-opencode-sessionupstream, the two things that open the free tier - BYOK — ride anonymous
publicor bring your own Zen key (stable + no shared-pool throttling) - Smart fallback — tries models in order on
429/5xx, honorsretry-after - Model aliases — e.g.
gpt-4o → mimo-v2.5-free, replies rewritten back - Per-IP fairness — real client IPs forwarded; local clients fall back to your real IP (same quota bucket as opencode direct)
- Management dashboard — glass… no, sticker-style UI at
/for stats, model tests, config & logs - Zero dependencies — one
zen-proxy.mjs, runs on any Node ≥ 18
The management UI runs on http://localhost:8787 (or http://127.0.0.1:8787) once the proxy is running — live stats, one-click model tests, auto-sync status, config editing, and server logs.
Requires Node.js ≥ 18.
|
Linux / macOS curl -fsSL https://raw.githubusercontent.com/12errh/zen-proxy/main/install.sh | bash |
Windows (PowerShell) irm https://raw.githubusercontent.com/12errh/zen-proxy/main/install.ps1 | iex |
Or just clone and run — it's a single file, no npm install:
git clone https://github.com/12errh/zen-proxy.git && cd zen-proxy
node zen-proxy.mjsnode zen-proxy.mjs # or the installer's `zen-proxy` launcher
# dashboard → http://127.0.0.1:8787/| Thing | Value |
|---|---|
| Dashboard / admin UI | http://127.0.0.1:8787/ |
| OpenAI base URL | http://127.0.0.1:8787/v1 |
| API key | public (or your proxyKey once set) |
| Example model | mimo-v2.5-free (auto default picks a healthy free model) |
| Health check | http://127.0.0.1:8787/health |
Cline / Roo / Continue / Aider / mimo — anywhere you configure an OpenAI-compatible provider:
Quick test:
curl -s http://127.0.0.1:8787/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"mimo-v2.5-free","messages":[{"role":"user","content":"say hi"}]}'- Overview — upstream health, uptime, request counters, live recent-request feed with status + latency
- Models — add/remove allowed models, pick the default, one-click Test per model
- Settings — edit host, port, upstream URL, User-Agent, proxy key, BYOK key, timeout, fallback list, aliases; saved to
zen-proxy.jsonand applied instantly - Logs — live terminal-style log tail
Set proxyKey and the dashboard, /api/*, chat completions, and /v1/models all require it (/health stays open for uptime monitors).
Config lives in zen-proxy.json (auto-created on first run, hot-reloaded when edited — the dashboard writes it too). Env vars can override at startup.
| Key | Default | Description |
|---|---|---|
host |
127.0.0.1 |
Bind address (restart needed) |
port |
8787 |
Listen port (restart needed) |
upstream |
https://opencode.ai/zen/v1 |
Zen API base |
ua |
opencode/1.18.30 |
The User-Agent that unlocks the free tier (auto-updates to new opencode releases) |
autoUA |
true |
Track opencode releases and update ua automatically (opencode/<latest>) |
uaRefreshMs |
21600000 |
How often to check for a new opencode version (ms) |
injectSession |
true |
Mint an x-opencode-session header per client (upstream rejects requests without one) |
gated (sync state) |
— | Models answering 403 FreeTierError: the free tier only accepts genuine agent requests, so our probe can't verify them. Shown as agent-only, kept in the list, never removed |
probeAuth |
auto |
Credentials for auto-sync health probes: auto (your Zen key if set, else anonymous), anonymous (always the public free tier), or key (always your key, so probes reflect your own quota) |
defaultModel |
"" |
Empty = auto: pick the first healthy free model (no more hardcoded/vanished defaults) |
fallbackModels |
["space-bunny-free","mimo-v2.6-flash-free", …] |
Tried in order on 429/5xx and unavailable-model 4xx. Auto-sync only removes a model the upstream says is gone (not supported, 404) — temporary blocks like 403 FreeTierError keep it configured so it recovers on its own |
responsesModels |
["gpt-5*","gpt-6*","grok-*","muse-spark-*"] |
Models served on /v1/responses (patterns may end in *); the proxy translates to/from chat completions for you |
rateLimitMax |
0 (off) |
Max chat requests per client per window — 0 disables the limiter |
rateLimitWindowMs |
60000 |
Rate-limit window |
modelAliases |
{} |
e.g. {"gpt-4o":"mimo-v2.5-free"} — reply model rewritten back |
proxyKey |
"" |
If set, clients must send it as Bearer; locks the dashboard too |
defaultZenKey |
"" |
Your own Zen key (BYOK) instead of anonymous public |
trustForwarded |
false |
Trust x-forwarded-for/x-real-ip from a reverse proxy |
timeoutMs |
120000 |
Upstream timeout (streaming and non-streaming) |
cacheMs |
30000 |
/v1/models cache TTL |
Env vars: HOST, PORT, ZEN_URL, ZEN_UA, INJECT_SESSION (0 to disable), AUTO_UA (0 to disable), UA_REFRESH_MS, PROBE_AUTH, RESPONSES_MODELS (JSON), RATE_LIMIT_MAX, RATE_LIMIT_WINDOW_MS, DEFAULT_MODEL, FALLBACK_MODELS (JSON), MODEL_ALIASES (JSON), PROXY_KEY, ZEN_KEY, TRUST_FORWARDED=1, TIMEOUT_MS, CACHE_MS, AUTO_SYNC (0 to disable), AUTO_SYNC_MS, ZEN_PROXY_CONFIG (custom config path).
Anonymous public access rides opencode's shared free pool (per-IP quota, sometimes saturated). For stable, reliable use set defaultZenKey to your own free Zen key — or send it per request as x-zen-key / a non-public bearer token.
| Method | Path | Description |
|---|---|---|
POST |
/v1/chat/completions |
Chat completions (stream + non-stream) |
POST |
/v1/responses |
Responses passthrough |
GET |
/v1/models |
Allowed models (cached) |
GET |
/health |
Health check |
GET |
/ |
Dashboard |
GET |
/api/status |
Stats + upstream health |
GET/PUT |
/api/config |
Read / update config |
POST |
/api/test |
Test a model ({"model":"…"}) |
GET |
/api/logs |
Log tail |
POST |
/api/reset |
Reset request stats |
GET |
/assets/* |
Static assets (logo, favicon) |
Install as a service via install.sh (it prompts you), or manually:
sudo systemctl enable --now zen-proxyopencode serves its free models only to genuine opencode traffic. Verified by capturing and replaying a real opencode request, the upstream accepts a request only when it carries:
stream: true, and- a realistic set of tool definitions in the request body
A bare curl, a health probe, or any non-agent client gets:
403 FreeTierError: OpenCode's free tier can only be used from within OpenCode
This is not the model being broken. A model in this state is shown as agent-only, is kept in your list, and is never pruned. Because the built-in probe can't verify it, the proxy learns real health from your actual client traffic — a model that serves your agent shows ok (live traffic).
| badge | meaning |
|---|---|
ok (live traffic) |
a real request through the proxy succeeded — the strongest signal |
ok |
the health probe got a clean 200 |
agent-only |
free-tier gated: works for real agents, not verifiable by the probe |
flaky |
temporary trouble (timeout, 5xx, rate-limit) |
dead |
upstream says it's gone (not supported, 404) — the only state that removes it |
If your agent streams and sends tool definitions (Claude Code, Cline, Roo Code, Continue, Aider, mimo, opencode itself — all of them do), it works. If it makes simple one-shot calls without tools, those specific calls get gated and the proxy transparently falls back to another free model.
You don't need to change anything. When a fallback happens the response tells you:
{ "model": "mimo-v2.6-flash-free", "zen_served_by": "space-bunny-free", /* … */ }plus response headers x-zen-served-by and x-zen-fallback: true.
The proxy deliberately does not fake opencode's tool schemas to get past this check. That gate is an access control, and the honest behaviour is to tell you which models are gated and let your real agent traffic use them.
- This rides opencode's anonymous free tier: per-IP request/daily quotas and a shared pool that's sometimes saturated. Don't rotate/abuse IPs or run heavy workloads anonymously.
- When the proxy falls back to a different model, the response includes
zen_served_byplusx-zen-served-by/x-zen-fallbackheaders so you can see which model actually answered. - The free tier only accepts real agent traffic (streaming + genuine tool definitions). A bare
curlor the built-in health probe gets403 FreeTierError— that is not the model being broken. Models in this state are shown as agent-only and the proxy learns their real health from your actual traffic (ok (live traffic)). - opencode keeps tightening the anonymous free tier — most
-freemodels now answer403 FreeTierError: "OpenCode's free tier can only be used from within OpenCode", and that hits the real opencode client too. The proxy detects it and rolls to the next working model, so requests keep succeeding, but the pool is much smaller than it used to be. A Zen API key (BYOK) is the reliable path. - The
-freemodels are "as-is" free tiers — expect rate limits and occasional provider errors. - For anything serious, BYOK.
zen-proxy · anonymous free tier · no accounts · no keys · 100% local
go make some noise.
MIT License


{ "provider": { "zen": { "baseURL": "http://127.0.0.1:8787/v1", "apiKey": "public", "models": { "mimo-v2.5-free": {} } } } }