Cloudflare Worker or Python Flask server that lets any harness (Claude Code, Codex CLI, Aider, OpenHands, LangChain, curl, ...) use OpenCode models through one OpenAI/Anthropic-compatible endpoint — no OpenCode account or API key needed for the free models.
| Cloudflare Worker | Python (Flask) | |
|---|---|---|
| Files | worker.js + bridge.js |
server.py + bridge.py |
| Runtime | edge, no server | local machine, VPS, Docker, systemd… |
| Install | npx wrangler deploy |
pip install -r requirements.txt |
| Secrets | dashboard secrets | env vars (ZEN_API_KEY, GATEWAY_KEY, OC_UA_VERSION) |
Both expose identical endpoints and the identical free-tier fingerprint.
The Python bridge is cross-validated against the Worker's bridge.js
(Anthropic/Responses ↔ chat conversion, SSE aggregation and re-emission).
Verified live against the upstream catalog. GET /v1/models returns the
current list with free: true flags (additions/removals upstream are picked
up automatically).
| Model | Status (2026-10-18) |
|---|---|
big-pickle |
✅ verified |
beam-free |
✅ verified |
deepseek-v4.1-flash-free |
✅ verified |
glm-5.3-flash-free |
✅ verified |
longcat-2.5-preview-free |
✅ verified |
mimo-v2.6-flash-free |
✅ verified |
nemotron-3.5-lightning-free |
✅ verified |
nemotron-3.5-super-vl-free |
✅ verified |
space-bunny-free |
✅ verified |
jev-1.13-free |
✅ works — but it is a TypeSafe decision model, not chat; use POST /v1/systemone (below) |
Free-tier catalog is limited-time promos upstream — models can rotate; the
gateway's /v1/models always reflects what is currently available, and any
model ending in -free (plus big-pickle) is routed through the free
fingerprint automatically.
GET /v1/models catalog
POST /v1/chat/completions OpenAI Chat wire (stream + non-stream)
POST /v1/messages Anthropic Messages wire (Claude Code compatible)
POST /v1/responses OpenAI Responses wire
POST /v1/systemone Jev/SystemOne decision wire (free tier)
GET /health
jev-1.13 / jev-1.13-free are not chat models — they are TypeSafe
decision models (classify state into answers with probabilities/confidence).
They speak their own wire format on a dedicated endpoint, which the gateway
passes through:
POST /v1/systemone
{
"model": "jev-1.13-free",
"state": "My Stripe integration keeps failing for 3 days.",
"questions": {
"department": {"type": "choice", "instructions": "Which team should handle this",
"criteria": {"billing": "Payment issues", "technical": "Bugs"}},
"is_urgent": {"type": "noul", "instructions": "The customer explicitly requests a refund"}
}
}
Response: {"answers": {"department": {"choice": "billing", "confidence": 0.44, ...}, ...}}
Question types: choice (≤255 options), score (2–10 ordered levels), noul
(true/false). Free tier verified working — see
the Jev guide and the
API reference for the full schema.
OpenCode's free tier is not account-gated — it is client-shape gated. Both runtimes reproduce the official opencode CLI fingerprint on every free request (reverse-engineered by the opencode2api project, documented in their UPSTREAM-COMPAT.md):
- Upstream path
/inference/openai/v1/chat/completions— all free models accept the OpenAI chat wire; Anthropic/Responses client requests are bridged locally (bridge.js/bridge.py, same architecture as zen-proxy) User-Agent: opencode/latest/<ver>/cli— version pinned (2.0.29); the gate rejects too-old AND too-new versionsx-opencode-session: ses_+ descending 48-bit timestamp hex + 14 base62 chars (exact ID encoding frompackages/opencode/src/id/id.ts)- Four session headers;
x-opencode-requestmust NOT be sent (403) stream: trueforced upstream — non-stream clients get local SSE→JSON aggregationtoolsmust includebash,glob,grep,readwith non-emptyparameters.properties— stubs are injected automatically, andtool_choice: noneis set when the client brought no tools
⚠️ Fragility: this is an adversarial compat layer, not a stable API. OpenCode has tightened the gate five times between 2026-09-17 and 2026-10-15. If free models start returning403 FreeTierError, check the compat doc above and re-pin the version via theOC_UA_VERSIONsecret / env var.
Option A — button above (Deploys to Cloudflare from this repo).
Option B — CLI:
git clone https://github.com/nullstacks/opencode-gateway
cd opencode-gateway
npx wrangler login
npx wrangler deploy| Secret | Purpose |
|---|---|
ZEN_API_KEY |
fallback Zen key — unlocks all 85 paid models for keyless callers |
GATEWAY_KEY |
require callers to present this key (lock public deployments) |
OC_UA_VERSION |
override the pinned opencode CLI version |
npx wrangler secret put ZEN_API_KEYpip install -r requirements.txt # flask + httpx
python3 server.py # serves on 0.0.0.0:8080 (PORT/HOST to override)Production options:
gunicorn -w 4 -b 0.0.0.0:8080 server:app
flask --app server run --host 0.0.0.0 --port 8080A Dockerfile is included: docker build -t opencode-gateway . && docker run -p 8080:8080 opencode-gateway.
| Variable | Purpose |
|---|---|
ZEN_API_KEY |
fallback Zen key — unlocks paid models for keyless callers |
GATEWAY_KEY |
require callers to present this key (lock public deployments) |
OC_UA_VERSION |
override the pinned opencode CLI version |
PORT / HOST |
bind address (default 0.0.0.0:8080) |
# OpenAI client — free model, no key
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"glm-5.3-flash-free","messages":[{"role":"user","content":"hi"}]}'
# Paid model — pass your Zen key from https://opencode.ai/auth
curl http://localhost:8080/v1/chat/completions \
-H "Authorization: Bearer <zen-key>" \
-d '{"model":"gpt-5.5","messages":[{"role":"user","content":"hi"}]}'
# Claude Code
export ANTHROPIC_BASE_URL=http://localhost:8080
export ANTHROPIC_API_KEY=free
export ANTHROPIC_MODEL=glm-5.3-flash-freeKey resolution order: X-Zen-Key header → ?key= → Authorization: Bearer
→ x-api-key → ZEN_API_KEY env. public / empty = free tier.
MIT