Smart Ollama gateway with sequential-primary key rotation, automatic cooldown on 429s, and admin APIs β works with Claude Code, OpenCode, Roo Code, Hermes AI, OpenClaw, CrewAI, and Kilo Code.
git clone https://github.com/manmeet0409/ollama-proxy
cd ollama-proxy
npm install
node ollama-proxy.jsThen point your AI coding tool at http://localhost:11435.
If you're an AI agent helping set up ollama-proxy:
# 1. Install dependencies
npm install
# 2. Start the proxy
node ollama-proxy.jsThe agent will figure out the rest.
| Feature | What it does | |
|---|---|---|
| π | Sequential-primary rotation | 1 request per key; busy keys cause automatic round-robin; waits for busy keys before failing |
| β‘ | Fail-count cooldowns | Key gets 60s cooldown on 429/401; after 3 consecutive failures β 10min long cooldown |
| β³ | Busy-wait polling | If all keys are busy, proxy polls and waits up to 2 minutes before returning 429 |
| π | Dual API support | Handles both /v1/chat/completions (OpenAI-style) and /v1/messages (Anthropic-style) |
| π | Admin dashboard | Live key status, activity logs, and key management at http://localhost:11435/dashboard |
| πΎ | Persistent keys | Keys survive restarts via keys.json |
| π§© | Universal compatibility | Works with every AI coding framework that supports custom OpenAI-compatible endpoints |
| βοΈ | Request coalescing | Simultaneous identical requests share one upstream call β no duplicate fetches |
| π« | Metadata caching | /api/tags, /v1/models, /api/show responses are cached to reduce upstream load |
Works with any AI coding framework that supports custom OpenAI-compatible or Anthropic-compatible API endpoints.
| Framework | Config method | Key setting |
|---|---|---|
| Claude Code | Environment variable | ANTHROPIC_BASE_URL=http://localhost:11435 |
| OpenCode | opencode.json β provider.openai-compatible.options.baseURL |
http://localhost:11435/v1 |
| Roo Code | VS Code settings.json β roo-cline.baseUrl |
http://localhost:11435/v1 |
| Hermes AI | ~/.hermes/config.yaml β model.base_url |
http://localhost:11435/v1 |
| OpenClaw | ~/.openclaw/openclaw.json β models.providers.<id>.baseUrl |
http://localhost:11435/v1 |
| CrewAI | Python LLM(base_url=...) |
http://localhost:11435/v1 |
| Kilo Code v5 | Direct ollama provider UI |
Base URL field β http://localhost:11435 |
| Kilo Code v7+ | kilo.jsonc β openai-compatible provider |
baseURL: "http://localhost:11435" |
Note: ollama-proxy passes
/v1/messages(Anthropic-style) through to Ollama without transformation. Ollama v0.14.0+ natively supports/v1/messages. For older Ollama versions, use/v1/chat/completionsinstead.
git clone https://github.com/manmeet0409/ollama-proxy
cd ollama-proxy
npm installOption A β keys.json (recommended, persists across restarts):
{
"keys": [
{ "key": "sk-your-first-key-here", "name": "Work PC" },
{ "key": "sk-your-second-key-here", "name": "Home Server" }
]
}Option B β Environment variable (for quick testing):
API_KEYS=sk-key-1,sk-key-2Option C β Dashboard UI (no file editing needed):
Visit http://localhost:11435/dashboard after starting the proxy. Use the Add Key form at the bottom of the left panel β keys are saved to keys.json automatically.
node ollama-proxy.jsOutput:
[KEYS] Loaded 2 keys from /path/to/keys.json
Proxy running on http://localhost:11435
Upstream: https://ollama.com
Keys loaded: 2
Set the base URL to http://localhost:11435 in your framework's settings (see Compatibility for per-framework examples).
| Variable | Default | Description |
|---|---|---|
PORT |
11435 |
Proxy listen port |
UPSTREAM_HOST |
ollama.com |
Upstream Ollama host |
API_KEYS |
β | Comma-separated keys (fallback if no keys.json) |
KEYS_FILE |
./keys.json |
Path to keys persistence file |
SOCKET_TIMEOUT_MS |
300000 |
Upstream connect + first-byte timeout |
BODY_RECEIVE_TIMEOUT_MS |
30000 |
Max time to read request body |
MAX_BODY_SIZE |
10485760 |
Max request body size (10MB) |
CACHE_TTL_TAGS |
300000 |
Cache TTL for /api/tags (5min) |
CACHE_TTL_MODELS |
300000 |
Cache TTL for /v1/models (5min) |
CACHE_TTL_SHOW |
600000 |
Cache TTL for /api/show (10min) |
KEY_BUSY_POLL_INTERVAL_MS |
500 |
How often to re-check for a free key (0.5s) |
KEY_BUSY_MAX_WAIT_MS |
120000 |
Max time to wait when all keys are busy (2min) |
{
"keys": [
{ "key": "...", "name": "Work PC", "status": "active" },
{ "key": "...", "name": "Home Server", "status": "active", "cooldownUntil": null, "usageCount": 0, "failCount": 0 }
],
"savedAt": "2026-04-25T12:00:00.000Z"
}Fields:
key(required) β the API keynameβ friendly label shown in dashboardstatusβactiveorcooldowncooldownUntilβ Unix timestamp (ms), set automatically on 429usageCountβ total requests served by this keyfailCountβ consecutive 429/401 failures (resets after long cooldown)concurrencyβ current active requests (capped at 1)
The proxy implements a "Primary-First, Idle-Next" rotation strategy with busy-wait polling:
- Preference: The primary key (index 0 or last successfully advanced index) is checked first. If it is
activeand hasconcurrency: 0, it is used. - Round-Robin: If the primary is busy, the proxy scans the remaining keys in sequence. The first idle, active key found is picked.
- Busy-Wait: If all active keys are busy (none are on cooldown, but all are currently processing a request), the proxy enters a polling loop. It re-checks for a free key every
500msfor up to120s. - Cooldown: If a key receives a
429(Rate Limit) or401(Unauthorized) from upstream:- It enters
cooldownstatus. - The current request immediately retries with the next available key.
- The key remains in cooldown for
60s(short) or10min(after 3 failures).
- It enters
- Exhaustion: If no keys become available after the full 120s wait period, the proxy returns a
429 All API keys are rate-limitedresponse to the client.
| Method | Path | Description |
|---|---|---|
GET |
/health |
Health check β returns key statuses |
* |
/v1/chat/completions |
OpenAI Chat Completions API |
* |
/v1/messages |
Anthropic Messages API (proxied to Ollama's /v1/chat/completions) |
GET |
/api/tags |
Ollama model list (cached) |
GET |
/v1/models |
Model list (cached) |
POST |
/api/show |
Model info (cached) |
GET |
/dashboard |
Admin dashboard UI (localhost only) |
| Method | Path | Description |
|---|---|---|
GET |
/api/keys |
List all keys with status and metrics |
POST |
/api/keys |
Add a new key |
PATCH |
/api/keys/:index |
Update key name |
DELETE |
/api/keys/:index |
Remove a key |
GET |
/api/logs |
Live activity logs |
The admin dashboard is available at http://localhost:11435/dashboard (only accessible from localhost).
Features:
- Key list β name, masked key, status (Ready / Busy / Cooldown), concurrency, usage count
- Live activity β real-time request logs with status code highlighting
- Add/remove keys β manage keys without editing
keys.json
http://localhost:11435/dashboard
Test the proxy is running:
curl http://localhost:11435/healthExpected response:
{
"status": "ok",
"keys": [
{ "index": 0, "status": "active", "concurrency": 0, "usageCount": 3, "cooldownRemainingSecs": 0 }
]
}Test a chat completion:
curl -X POST http://localhost:11435/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_KEY" \
-d '{"model":"minimax-m2.7","messages":[{"role":"user","content":"Hi"}],"max_tokens":20}'MIT License β see LICENSE for details.