Self-hosted AI gateway in pure Go for Claude Code and any OpenAI/Anthropic-compatible client. One binary, zero heavy dependencies, multi-provider routing with key rotation, fallbacks, budgets, semantic cache, guardrails, and an MCP control surface.
Inspired by the best: OmniRoute · claude-code-router · LiteLLM · one-api · Bifrost · Portkey — all credited in Credits.
Core
- 6 provider types:
anthropic,anthropic-compat,openai,bedrock(SigV4 + event-stream),vertex(api-key / bearer / service-account),antigravity(free Claude Opus 4.5 via Google OAuth) - Full Anthropic ↔ OpenAI protocol translation, both directions, including streaming SSE
- Dual protocol:
POST /v1/messages(Anthropic) +POST /v1/chat/completions(OpenAI) — works with Claude Code, OpenCode, Cursor, Aider, Continue, any OpenAI SDK - Key rotation: round-robin or fill-first, cooldown with exponential backoff,
Retry-Afterrespect - Fallback chains + circuit breaker (5 consecutive failures → 2 min pause)
- Load balancing:
weighted/least_busy/latency(EMA) strategies
Routing
- Scenario routing (from claude-code-router):
long_context/image/thinking— route to different chains based on request content - Combo models: one alias → chain of different models with fallback
- Session-sticky routing by
metadata.user_id - Prefix-based rules with model mapping and stripping
Client management
- Virtual keys (LiteLLM-style): named clients with USD budgets (daily/weekly/monthly),
allowed_modelsglob patterns, TPM limits - Per-request headers (Cloudflare AI Gateway-style):
x-ccg-skip-cache,x-ccg-cache-ttl,x-ccg-cache-key,x-ccg-collect-log,x-ccg-max-attempts,x-ccg-metadata
Observability
- Web dashboard: charts (24h + 14d), per-provider/model breakdowns, live request feed, YAML config editor with validation + rollback
- Prometheus
/metrics: requests, tokens, cost, latency histogram, per-provider/model - Usage webhooks with HMAC signatures
- TTFT (time-to-first-byte) per streamed request
- CSV export
Security
- Guardrails: block/redact regex patterns on request and response, PII presets (
email,phone,card), prompt-injection detection, denied tools, streaming scan - Response cache: exact + semantic (embeddings-based), per-request TTL override
- Rate limiting: RPM + TPM per client token
- Distributed state: Redis or Postgres for multi-replica deployments
Extensibility
- MCP server (HTTP + stdio): manage the gateway from Claude — stats, logs, models, budgets, reload, cost estimation
- Request transformers:
max_tokens_cap,set:key=value,reasoning_effort,drop_keys,system_prefix - Price auto-sync from OpenRouter API
- Hot config reload (
POST /admin/reload) — no restart needed - YAML config editor with validation + backup + rollback
git clone https://github.com/Mukller/claude-code-gateway.git
cd claude-code-gateway
cp .env.example .env # add your provider keys
docker compose up -d --buildPoint Claude Code at the gateway:
export ANTHROPIC_BASE_URL=http://localhost:8090
export ANTHROPIC_AUTH_TOKEN=ccg-local-dev-token
export CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1
claudeOr use a pre-built binary:
# Download from Releases
chmod +x cc-gateway-linux-amd64
./cc-gateway-linux-amd64 -config config.yamlOr Docker:
docker run -d -p 8090:8090 \
-v ./config.yaml:/app/config.yaml:ro \
-v ./data:/app/data \
-e NINE_ROUTER_KEY=sk-... \
ghcr.io/mukller/claude-code-gateway:latestThe gateway exposes both Anthropic and OpenAI protocols simultaneously:
| Tool | Protocol | Base URL |
|---|---|---|
| Claude Code | Anthropic | http://localhost:8090 (no /v1) |
| OpenCode | OpenAI | http://localhost:8090/v1 |
| Cursor | OpenAI | http://localhost:8090/v1 |
| Aider | OpenAI | http://localhost:8090/v1 |
| Zed | OpenAI | http://localhost:8090/v1 |
| Continue | OpenAI | http://localhost:8090/v1 |
| Windsurf | OpenAI | http://localhost:8090/v1 |
| Any OpenAI SDK | OpenAI | http://localhost:8090/v1 |
OpenCode config (~/.config/opencode/config.json):
{
"provider": {
"gateway": {
"apiKey": "ccg-local-dev-token",
"baseUrl": "http://localhost:8090/v1",
"models": {
"claude-sonnet-4-5": { "name": "Sonnet 4.5 (gateway)" },
"claude-opus-4-5": { "name": "Opus 4.5 (gateway)" }
}
}
},
"model": "gateway/claude-sonnet-4-5"
}Cursor (Settings → Models → OpenAI API Key):
- API Key:
ccg-local-dev-token - Base URL:
http://localhost:8090/v1
Aider:
export OPENAI_API_BASE=http://localhost:8090/v1
export OPENAI_API_KEY=ccg-local-dev-token
aider --model gateway/claude-sonnet-4-5curl:
curl http://localhost:8090/v1/chat/completions \
-H "x-api-key: ccg-local-dev-token" \
-H "Content-Type: application/json" \
-d '{"model":"claude-sonnet-4-5","messages":[{"role":"user","content":"hello"}]}'Anthropic (direct)
providers:
- name: anthropic
type: anthropic
base_url: "https://api.anthropic.com"
keys: ["${ANTHROPIC_API_KEY}"]OpenRouter
providers:
- name: openrouter
type: openai
base_url: "https://openrouter.ai/api/v1"
keys: ["${OPENROUTER_KEY}"]
discover_models: trueDeepSeek
providers:
- name: deepseek
type: openai
base_url: "https://api.deepseek.com/v1"
keys: ["${DEEPSEEK_KEY}"]
models:
- deepseek-chat
- deepseek-reasonerGroq
providers:
- name: groq
type: openai
base_url: "https://api.groq.com/openai/v1"
keys: ["${GROQ_KEY}"]
discover_models: trueMistral
providers:
- name: mistral
type: openai
base_url: "https://api.mistral.ai/v1"
keys: ["${MISTRAL_KEY}"]
discover_models: trueTogether AI
providers:
- name: together
type: openai
base_url: "https://api.together.xyz/v1"
keys: ["${TOGETHER_KEY}"]
discover_models: truexAI (Grok)
providers:
- name: xai
type: openai
base_url: "https://api.x.ai/v1"
keys: ["${XAI_KEY}"]Cerebras
providers:
- name: cerebras
type: openai
base_url: "https://api.cerebras.ai/v1"
keys: ["${CEREBRAS_KEY}"]Fireworks AI
providers:
- name: fireworks
type: openai
base_url: "https://api.fireworks.ai/inference/v1"
keys: ["${FIREWORKS_KEY}"]Ollama (local)
providers:
- name: ollama
type: openai
base_url: "http://localhost:11434/v1"
keys: ["ollama"]
models:
- llama3:70b
- codellama:34bAWS Bedrock
providers:
- name: bedrock
type: bedrock
region: us-east-1
keys: ["${AWS_ACCESS_KEY_ID}:${AWS_SECRET_ACCESS_KEY}"]Google Vertex AI
providers:
- name: vertex
type: vertex
auth_style: api-key
keys: ["${VERTEX_API_KEY}"]Antigravity (free Claude Opus 4.5)
providers:
- name: antigravity
type: antigravity
antigravity_auth_file: "~/.local/share/opencode/auth.json"Requires opencode auth login → Google → Antigravity (one-time).
These 6 providers offer free models (registration + API key required, no payment needed). All expose OpenAI-compatible endpoints so use type: openai in the gateway config.
| Provider | Free models | Notes |
|---|---|---|
| OrcaRouter | orcarouter/free, deepseek/deepseek-v4-flash-free, deepseek/deepseek-v4-pro-free, qwen/qwen3.8-27b-free |
No key, no card |
| TeamoRouter | deepseek-v4-flash-free, deepseek-v4-pro-free |
1M context, $0 in/out |
| AgentRouter | claude-opus-4-8, claude-opus-5, gpt-5.6-sol |
$125 credits on sign-up (GitHub account age matters) |
| Token Harbor | mimo-v2.5:free, deepseek-v4-flash:free |
Email confirmation, 7-day free window |
| NaraRouter | agnes-2.0-flash, agnes-2.5-flash, laguna-s-2.1, mistral-large, mistral-medium-3-5, tencent-hy3-free |
7M tokens/day, 10 req/min — needs Telegram |
| FreeRouter | qwen3.8-max |
No account needed, 100 req/min |
To enforce a strictly free model policy (return 403 on any non-free model request), enable the gateway-level gate:
routing:
free_only: true
free_models:
- "openai/*"
- "deepseek/*"
- "qwen/*"
- "mistral/*"Any request to a model outside this list (e.g. anthropic/claude-opus-4) returns 403 forbidden: model X is not in free_models list. Pattern syntax: only the suffix * is supported (e.g. openai/*, deepseek/*).
providers:
- name: orcarouter
type: openai
base_url: "https://www.orcarouter.ai/api/v1"
keys: ["${ORCAROUTER_KEY}"]
- name: teamorouter
type: openai
base_url: "https://teamorouter.com/api/v1"
keys: ["${TEAMOROUTER_KEY}"]
- name: nara
type: openai
base_url: "https://router.bynara.id/api/v1"
keys: ["${NARA_KEY}"]
routing:
rules:
- prefix: "free/"
strip_prefix: true
chain: [orcarouter, teamorouter, nara]
clients:
- name: dima
token: "${DIMA_TOKEN}"
allowed_models:
- "deepseek/*"
- "qwen/*"
# 0 budget = blocked immediately, can be bumped at runtime via
# curl -X POST -H "x-api-key: $ADMIN" -H "Content-Type: application/json" \
# -d '{"name":"dima","budget_usd":5}' http://localhost:8090/admin/tokens/update
budget_usd: 0
budget_period: monthlyFull config example
server:
listen: ":8090"
keepalive_seconds: 15
shutdown_timeout: 60s
auth:
require_auth: true
tokens:
- "${GATEWAY_TOKEN}"
admin_token: "${GATEWAY_ADMIN_TOKEN}"
clients:
- name: anton
token: "${GATEWAY_TOKEN}"
- name: teammate
token: "${TEAM_TOKEN}"
budget_usd: 50
budget_period: monthly
allowed_models: ["glm*", "deepseek*"]
tpm: 200000
providers:
- name: anthropic
type: anthropic
base_url: "https://api.anthropic.com"
keys: ["${ANTHROPIC_API_KEY}"]
- name: openrouter
type: openai
base_url: "https://openrouter.ai/api/v1"
keys: ["${OPENROUTER_KEY}"]
discover_models: true
weight: 3
routing:
alias_claude_prefix: true
default_chain: [anthropic, openrouter]
rules:
- prefix: "combo/fast"
targets:
- { provider: openrouter, model: "meta-llama/llama-4-scout" }
- { provider: anthropic, model: "claude-haiku-4-5" }
- prefix: "anthropic/"
strip_prefix: true
chain: [anthropic]
scenarios:
long_context:
threshold_tokens: 80000
chain: [anthropic]
image:
chain: [anthropic]
cache:
enabled: true
ttl: 30m
guardrails:
request:
pii_presets: [email]
injection_detection: block
pricing_sync:
enabled: true
interval: 6h
state:
redis_url: "redis://localhost:6379"| Endpoint | Description |
|---|---|
POST /v1/messages |
Anthropic Messages API (streaming + non-streaming) |
POST /v1/messages/count_tokens |
Token estimation |
GET /v1/models |
Model catalog (?format=openai for OpenAI shape) |
GET /healthz |
Health check |
GET /metrics |
Prometheus metrics |
POST /mcp |
MCP server (HTTP transport) |
GET /admin/dashboard |
Web dashboard |
GET /admin/stats |
Usage statistics |
GET /admin/logs?limit=N |
Recent request log |
GET /admin/tokens |
Client budgets and spend |
GET /admin/keys |
Provider key pool status |
GET /admin/config |
Sanitized config snapshot |
GET /admin/config/yaml |
Raw config.yaml |
POST /admin/config/yaml |
Update config (validated + backed up) |
POST /admin/config/rollback |
Rollback to previous config |
POST /admin/reload |
Hot reload providers/routing/pricing |
POST /admin/flush-cache |
Clear response cache |
GET /admin/export.csv |
Export usage log as CSV |
GET /admin/inflight |
Current in-flight request count |
The gateway waits for in-flight requests to complete before exiting. On SIGTERM / SIGINT:
- Stops accepting new connections
- Drains in-flight requests (up to
server.shutdown_timeout, default 30s) - Logs
all in-flight requests completedorgraceful drain deadline reached, N request(s) may have been cut
Configure the drain timeout:
server:
shutdown_timeout: 60s # give long-running streams more timeMonitor the current in-flight count at any time:
curl -H "x-api-key: $ADMIN" http://localhost:8090/admin/inflight
# {"in_flight": 3}The gateway exposes itself as an MCP server — Claude can manage it natively:
# HTTP transport
claude mcp add --transport http ccg http://localhost:8090/mcp --header "x-api-key: ccg-admin-token"
# stdio transport
claude mcp add ccg -- ./bin/gateway -config config.yaml -mcpTools: gateway_stats, gateway_logs, gateway_models, gateway_providers, gateway_tokens, gateway_reload, estimate_cost.
cmd/gateway entry point + claude launcher
internal/core Anthropic/OpenAI types, protocol translation (req/resp/SSE)
internal/provider key pool, request execution, routing registry, SigV4, event-stream
internal/server HTTP handlers, dashboard, MCP, metrics, guardrails
internal/cache LRU+TTL exact cache
internal/logstore JSONL usage log + aggregates
internal/pricing glob pricing table, cost calculation
internal/ratelimit per-client RPM/TPM limiter
internal/state Redis/Postgres/memory state backends
internal/config YAML + env expansion
make test # go test ./...
make vet # go vet ./...
make fmt # gofmt -w .
make run # local start
make docker-up # build + run in dockerIdeas and inspiration from the best in class:
| Project | What we borrowed |
|---|---|
| claude-code-router | Scenario routing, transformers, model aliases |
| LiteLLM | Virtual keys, budgets, allowed_models, TPM |
| OmniRoute | MCP server, fallback chains, model discovery, dashboard |
| one-api / new-api | Channel weights, circuit breaker, CSV export, runtime key management |
| gpt-load | Key pool health probes |
| uni-api | Per-key rate limits in YAML |
| Helicone | TTFT metric |
| Bifrost | Semantic cache via embeddings |
| Portkey Gateway | Guardrails on request and response |
| Cloudflare AI Gateway | Per-request headers, cache control |
| CLIProxyAPI | SSE keep-alive, session affinity, fill-first |
| Habr: 6 free AI routers | list of free-tier providers, promoted to the "Free tier providers" section |
Free tier providers recommended in the article: OrcaRouter, TeamoRouter, AgentRouter ($125 credits), Token Harbor, NaraRouter (7M tokens/day), FreeRouter. All use OpenAI-compatible endpoints and are configured as type: openai providers.
If you find this project useful, consider supporting its development:
- GitHub Sponsors — @Mukller
- Star the repo — helps visibility
- Report issues — bugs, feature requests, docs improvements
- Contribute — PRs welcome (see
CONTRIBUTING.md)
MIT