Universal LLM Gateway: One API, every LLM. OpenAI/Anthropic-compatible endpoints with multi-provider translation and intelligent load-balancing.
Python
558
862 commits
updated Sep 23, 2026
One proxy. Any LLM provider. Zero code changes.
A self-hosted proxy that provides OpenAI and Anthropic compatible API endpoints for all your LLM providers. Works with any application that supports custom OpenAI or Anthropic base URLs—including Claude Code, Opencode, and more—no code changes required in your existing tools.
This project consists of two components:
/v1/chat/completions (OpenAI) and /v1/messages (Anthropic) endpointsproxy_app.exe — the interactive TUI launcher opens# Download and extract the release for your platform
chmod +x proxy_app
./proxy_app
Using the pre-built image (recommended):
# Pull and run directly
docker run -d \
--name llm-api-proxy \
-p 8000:8000 \
-v $(pwd)/.env:/app/.env:ro \
-v $(pwd)/oauth_creds:/app/oauth_creds \
-v $(pwd)/logs:/app/logs \
-v $(pwd)/usage:/app/usage \
-e SKIP_OAUTH_INIT_CHECK=true \
ghcr.io/mirrowel/llm-api-key-proxy:latest
Using Docker Compose:
# Create your .env file and usage directory first, then:
cp .env.example .env
mkdir usage
docker compose up -d
Important: Create the
usage/directory before running Docker Compose so usage stats persist on the host.
Note: For OAuth providers, complete authentication locally first using the credential tool, then mount the
oauth_creds/directory or export credentials to environment variables.
git clone https://github.com/Mirrowel/LLM-API-Key-Proxy.git
cd LLM-API-Key-Proxy
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
python src/proxy_app/main.py
Tip: Running with command-line arguments (e.g.,
--host 0.0.0.0 --port 8000) bypasses the TUI and starts the proxy directly.
Once the proxy is running, configure your application with these settings:
| Setting | Value |
|---|---|
| Base URL / API Endpoint | http://127.0.0.1:8000/v1 |
| API Key | Your PROXY_API_KEY |
provider/model_nameImportant: Models must be specified in the format provider/model_name. The provider/ prefix tells the proxy which backend to route the request to.
gemini/gemini-2.5-flash ← Gemini API
openai/gpt-4o ← OpenAI API
anthropic/claude-3-5-sonnet ← Anthropic API
openrouter/anthropic/claude-3-opus ← OpenRouter
gemini_cli/gemini-2.5-pro ← Gemini CLI (OAuth)
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:8000/v1",
api_key="your-proxy-api-key"
)
response = client.chat.completions.create(
model="gemini/gemini-2.5-flash", # provider/model format
messages=[{"role": "user", "content": "Hello!"}]
)
print(response.choices[0].message.content)
curl -X POST http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-proxy-api-key" \
-d '{
"model": "gemini/gemini-2.5-flash",
"messages": [{"role": "user", "content": "What is the capital of France?"}]
}'
http://127.0.0.1:8000/v1PROXY_API_KEYprovider/model_name (e.g., gemini/gemini-2.5-flash)In your configuration file (e.g., config.json):
{
"models": [
{
"title": "Gemini via Proxy",
"provider": "openai",
"model": "gemini/gemini-2.5-flash",
"apiBase": "http://127.0.0.1:8000/v1",
"apiKey": "your-proxy-api-key"
}
]
}
Claude Code natively supports custom Anthropic API endpoints. The recommended setup is to edit your Claude Code settings.json:
{
"env": {
"ANTHROPIC_AUTH_TOKEN": "your-proxy-api-key",
"ANTHROPIC_BASE_URL": "http://127.0.0.1:8000",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "gemini/gemini-3-pro",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "gemini/gemini-3-flash",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "openai/gpt-5-mini"
}
}
Now you can use Claude Code with Gemini, OpenAI, or any other configured provider.
from anthropic import Anthropic
client = Anthropic(
base_url="http://127.0.0.1:8000",
api_key="your-proxy-api-key"
)
# Use any provider through Anthropic's API format
response = client.messages.create(
model="gemini/gemini-3-flash", # provider/model format
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}]
)
print(response.content[0].text)
| Endpoint | Description |
|---|---|
GET / | Status check — confirms proxy is running |
POST /v1/chat/completions | Chat completions (OpenAI format) |
POST /v1/messages | Chat completions (Anthropic format) — Claude Code compatible |
POST /v1/messages/count_tokens | Count tokens for Anthropic-format requests |
POST /v1/embeddings | Text embeddings |
GET /v1/models | List all available models with pricing & capabilities |
GET /v1/models/{model_id} | Get details for a specific model |
GET /v1/providers | List configured providers |
POST /v1/token-count | Calculate token count for a payload |
POST /v1/cost-estimate | Estimate cost based on token counts |
Tip: The
/v1/modelsendpoint is useful for discovering available models in your client. Many apps can fetch this list automatically. Add?enriched=falsefor a minimal response without pricing data.
The proxy includes an interactive tool for managing all your API keys and OAuth credentials.
python -m rotator_library.credential_tool
| Type | Providers | How to Add |
|---|---|---|
| API Keys | Gemini, OpenAI, Anthropic, OpenRouter, Groq, Mistral, NVIDIA, Cohere, Chutes | Enter key in TUI or add to .env |
| OAuth | Gemini CLI | Interactive browser login via credential tool |
.env FileCredentials are stored in a .env file. You can edit it directly or use the TUI:
# Required: Authentication key for YOUR proxy
PROXY_API_KEY="your-secret-proxy-key"
# Provider API Keys (add multiple with _1, _2, etc.)
GEMINI_API_KEY_1="your-gemini-key"
GEMINI_API_KEY_2="another-gemini-key"
OPENAI_API_KEY_1="your-openai-key"
ANTHROPIC_API_KEY_1="your-anthropic-key"
Copy
.env.exampleto.envas a starting point.
The proxy is powered by a standalone Python library that you can use directly in your own applications.
asyncio and httpxfrom rotator_library import RotatingClient
client = RotatingClient(
api_keys={"gemini": ["key1", "key2"], "openai": ["key3"]},
global_timeout=30,
max_retries=2
)
async with client:
response = await client.acompletion(
model="gemini/gemini-2.5-flash",
messages=[{"role": "user", "content": "Hello!"}]
)
See the Library README for complete documentation including:
The proxy includes a powerful text-based UI for configuration and management.
| File | Contents |
|---|---|
.env | All credentials and advanced settings |
launcher_config.json | TUI-specific settings (host, port, logging) |
quota_viewer_config.json | Quota viewer remotes + per-provider display toggles |
usage/usage_<provider>.json | Usage persistence per provider |
~/.gemini/)oauth_creds/ directoryOPTIMAL_CONCURRENT_REQUESTS_PER_KEY_<PROVIDER> and MAX_CONCURRENT_REQUESTS_PER_KEY_<PROVIDER>)Gemini CLI:
NVIDIA NIM:
--enable-request-logging--enable-raw-logging (proxy boundary payloads)| Variable | Description | Default |
|---|---|---|
PROXY_API_KEY | Authentication key for your proxy | Required |
OAUTH_REFRESH_INTERVAL | Token refresh check interval (seconds) | 600 |
SKIP_OAUTH_INIT_CHECK | Skip interactive OAuth setup on startup | false |
| Pattern | Description | Example |
|---|---|---|
<PROVIDER>_API_KEY_<N> | API key for provider | GEMINI_API_KEY_1 |
OPTIMAL_CONCURRENT_REQUESTS_PER_KEY_<PROVIDER> | Soft spread-before-stacking target | OPTIMAL_CONCURRENT_REQUESTS_PER_KEY_OPENAI=1 |
MAX_CONCURRENT_REQUESTS_PER_KEY_<PROVIDER> | Hard concurrent request ceiling (<=0 means unlimited) | MAX_CONCURRENT_REQUESTS_PER_KEY_OPENAI=-1 |
ROTATION_MODE_<PROVIDER> | balanced or sequential | ROTATION_MODE_GEMINI=sequential |
IGNORE_MODELS_<PROVIDER> | Blacklist (comma-separated, supports *) | IGNORE_MODELS_OPENAI=*-preview* |
WHITELIST_MODELS_<PROVIDER> | Whitelist (overrides blacklist) | WHITELIST_MODELS_GEMINI=gemini-2.5-pro |
| Variable | Description |
|---|---|
ROTATION_TOLERANCE | 0.0=deterministic, 3.0=weighted random (default) |
CONCURRENCY_MULTIPLIER_<PROVIDER>_PRIORITY_<N> | Concurrency multiplier per priority tier |
QUOTA_GROUPS_<PROVIDER>_<GROUP> | Models sharing quota limits |
OVERRIDE_TEMPERATURE_ZERO | remove or set to prevent tool hallucination |
GEMINI_CLI_QUOTA_REFRESH_INTERVAL | Quota baseline refresh interval in seconds (default: 300) |
Control which models are exposed through your proxy.
# Hide all preview models
IGNORE_MODELS_OPENAI="*-preview*"
# Block all, then allow specific models
IGNORE_MODELS_GEMINI="*"
WHITELIST_MODELS_GEMINI="gemini-2.5-pro,gemini-2.5-flash"
# Block preview models, but allow one specific preview
IGNORE_MODELS_OPENAI="*-preview*"
WHITELIST_MODELS_OPENAI="gpt-4o-2024-08-06-preview"
Logic order: Whitelist check → Blacklist check → Default allow
# Balanced mode defaults to optimal=1 and max=-1, so it spreads first
# but will stack on busy keys instead of blocking when every key is busy.
ROTATION_MODE_OPENAI=balanced
OPTIMAL_CONCURRENT_REQUESTS_PER_KEY_OPENAI=1
MAX_CONCURRENT_REQUESTS_PER_KEY_OPENAI=-1
# Sequential mode defaults to optimal=-1 and max=-1 for sticky/unlimited use.
ROTATION_MODE_GEMINI=sequential
OPTIMAL_CONCURRENT_REQUESTS_PER_KEY_GEMINI=-1
MAX_CONCURRENT_REQUESTS_PER_KEY_GEMINI=-1
# Constrained providers can set optimal and max to the same value.
MAX_CONCURRENT_REQUESTS_PER_KEY_GEMINI_CLI=1
OPTIMAL_CONCURRENT_REQUESTS_PER_KEY_GEMINI_CLI=1
# Mode-specific forms override provider-wide values only for that mode.
MAX_CONCURRENT_REQUESTS_PER_KEY_OPENAI_BALANCED=-1
OPTIMAL_CONCURRENT_REQUESTS_PER_KEY_OPENAI_BALANCED=1
optimal is a soft target used for capacity phases: the rotator prefers credentials below optimal, then stacks on healthy credentials when no below-optimal credential remains. max is the hard safety ceiling; 0 or any negative value means unlimited.
# sequential (default): Use one key until it errors/exhausts, preserving provider-side cache locality
ROTATION_MODE_GEMINI=sequential
# balanced: Distribute load evenly - opt in for per-minute rate limits
ROTATION_MODE_OPENAI=balanced
Paid credentials can handle more concurrent requests. Legacy priority multipliers apply to hard max concurrency; provider-specific optimal multipliers can also raise the soft target where supported:
# Priority 1: 10x concurrency
CONCURRENCY_MULTIPLIER_GEMINI_CLI_PRIORITY_1=10
# Priority 2: 3x
CONCURRENCY_MULTIPLIER_GEMINI_CLI_PRIORITY_2=3
Models sharing quota limits:
# Example: group provider models that share quota
QUOTA_GROUPS_GEMINI_CLI_PRO="gemini-2.5-pro,gemini-3-pro-preview"
Fine-grained control over HTTP timeouts:
TIMEOUT_CONNECT=30 # Connection establishment
TIMEOUT_WRITE=30 # Request body send
TIMEOUT_POOL=60 # Connection pool acquisition
TIMEOUT_READ_STREAMING=180 # Between streaming chunks (3 min)
TIMEOUT_READ_NON_STREAMING=600 # Full response wait (10 min)
Recommendations:
TIMEOUT_READ_STREAMING to 300-360sTIMEOUT_CONNECT to 60sTIMEOUT_READ_NON_STREAMING to 900s+Uses Google OAuth to access internal Gemini endpoints with higher rate limits.
Setup:
python -m rotator_library.credential_tooloauth_creds/gemini_cli_oauth_1.jsonFeatures:
Quota Groups: Models that share quota are automatically grouped:
gemini-2.5-pro, gemini-3-pro-previewgemini-2.0-flash, gemini-2.5-flash, gemini-2.5-flash-litegemini-3-flash-previewAll models in a group deplete the shared quota equally. 24-hour per-model quota windows.
Environment Variables (for stateless deployment):
Single credential (legacy):
GEMINI_CLI_ACCESS_TOKEN="ya29.your-access-token"
GEMINI_CLI_REFRESH_TOKEN="1//your-refresh-token"
GEMINI_CLI_EXPIRY_DATE="1234567890000"
GEMINI_CLI_EMAIL="[email protected]"
GEMINI_CLI_PROJECT_ID="your-gcp-project-id" # Optional
GEMINI_CLI_TIER="standard-tier" # Optional: standard-tier or free-tier
Multiple credentials (use _N_ suffix where N is 1, 2, 3...):
GEMINI_CLI_1_ACCESS_TOKEN="ya29.first-token"
GEMINI_CLI_1_REFRESH_TOKEN="1//first-refresh"
GEMINI_CLI_1_EXPIRY_DATE="1234567890000"
GEMINI_CLI_1_EMAIL="[email protected]"
GEMINI_CLI_1_PROJECT_ID="project-1"
GEMINI_CLI_1_TIER="standard-tier"
GEMINI_CLI_2_ACCESS_TOKEN="ya29.second-token"
GEMINI_CLI_2_REFRESH_TOKEN="1//second-refresh"
GEMINI_CLI_2_EXPIRY_DATE="1234567890000"
GEMINI_CLI_2_EMAIL="[email protected]"
GEMINI_CLI_2_PROJECT_ID="project-2"
GEMINI_CLI_2_TIER="free-tier"
Feature Toggles:
GEMINI_CLI_QUOTA_REFRESH_INTERVAL=300 # Quota refresh interval in seconds (default: 300 = 5 min)
For platforms without file persistence (Railway, Render, Vercel):
Set up credentials locally:
python -m rotator_library.credential_tool
# Complete OAuth flows
Export to environment variables:
python -m rotator_library.credential_tool
# Select "Export [Provider] to .env"
Copy generated variables to your platform:
The tool creates files like gemini_cli_credential_1.env containing all necessary variables.
Set SKIP_OAUTH_INIT_CHECK=true to skip interactive validation on startup.
Customize OAuth callback ports if defaults conflict:
| Provider | Default Port | Environment Variable |
|---|---|---|
| Gemini CLI | 8085 | GEMINI_CLI_OAUTH_PORT |
python src/proxy_app/main.py [OPTIONS]
Options:
--host TEXT Host to bind (default: 0.0.0.0)
--port INTEGER Port to run on (default: 8000)
--enable-request-logging Enable detailed per-request logging
--enable-raw-logging Capture raw proxy I/O payloads
--add-credential Launch interactive credential setup tool
Examples:
# Run on custom port
python src/proxy_app/main.py --host 127.0.0.1 --port 9000
# Run with logging
python src/proxy_app/main.py --enable-request-logging
# Run with raw I/O logging
python src/proxy_app/main.py --enable-raw-logging
# Add credentials without starting proxy
python src/proxy_app/main.py --add-credential
See the Deployment Guide for complete instructions.
Quick Setup:
.env file with your credentialspip install -r requirements.txtuvicorn src.proxy_app.main:app --host 0.0.0.0 --port $PORT.env as a secret fileOAuth Credentials: Export OAuth credentials to environment variables using the credential tool, then add them to your platform's environment settings.
The proxy is available as a multi-architecture Docker image (amd64/arm64) from GitHub Container Registry.
Quick Start with Docker Compose:
# 1. Create your .env file with PROXY_API_KEY and provider keys
cp .env.example .env
nano .env
# 2. Create usage directory (usage_*.json files are created automatically)
mkdir usage
# 3. Start the proxy
docker compose up -d
# 4. Check logs
docker compose logs -f
Important: Create the
usage/directory before running Docker Compose so usage stats persist on the host.
Manual Docker Run:
# Create usage directory if it doesn't exist
mkdir usage
docker run -d \
--name llm-api-proxy \
--restart unless-stopped \
-p 8000:8000 \
-v $(pwd)/.env:/app/.env:ro \
-v $(pwd)/oauth_creds:/app/oauth_creds \
-v $(pwd)/logs:/app/logs \
-v $(pwd)/usage:/app/usage \
-e SKIP_OAUTH_INIT_CHECK=true \
-e PYTHONUNBUFFERED=1 \
ghcr.io/mirrowel/llm-api-key-proxy:latest
Development with Local Build:
# Build and run locally
docker compose -f docker-compose.dev.yml up -d --build
Volume Mounts:
| Path | Purpose |
|---|---|
.env | Configuration and API keys (read-only) |
oauth_creds/ | OAuth credential files (persistent) |
logs/ | Request logs and detailed logging |
usage/ | Usage statistics persistence (usage_*.json) |
Image Tags:
| Tag | Description |
|---|---|
latest | Latest stable from main branch |
dev-latest | Latest from dev branch |
YYYYMMDD-HHMMSS-<sha> | Specific version with timestamp and commit |
OAuth with Docker:
For OAuth providers such as Gemini CLI, you must authenticate locally first:
python -m rotator_library.credential_tool on your local machineoauth_creds/ directory to container, or.env using the export optionOption 1: Authenticate locally, deploy credentials
.env to your serverOption 2: SSH Port Forwarding
# Forward callback ports through SSH
ssh -L 51121:localhost:51121 -L 8085:localhost:8085 user@your-vps
# Then run credential tool on the VPS
Systemd Service:
[Unit]
Description=LLM API Key Proxy
After=network.target
[Service]
Type=simple
WorkingDirectory=/path/to/LLM-API-Key-Proxy
ExecStart=/path/to/python -m uvicorn src.proxy_app.main:app --host 0.0.0.0 --port 8000
Restart=always
[Install]
WantedBy=multi-user.target
See VPS Deployment for complete guide.
| Issue | Solution |
|---|---|
401 Unauthorized | Verify PROXY_API_KEY matches your Authorization: Bearer header exactly |
500 Internal Server Error | Check provider key validity; enable --enable-request-logging for details |
| All keys on cooldown | All keys failed recently; check logs/detailed_logs/ for upstream errors |
| Model not found | Verify format is provider/model_name (e.g., gemini/gemini-2.5-flash) |
| OAuth callback failed | Ensure callback port (8085, 51121, 11451) isn't blocked by firewall |
| Streaming hangs | Increase TIMEOUT_READ_STREAMING; check provider status |
Detailed Logs:
When --enable-request-logging is enabled, check logs/detailed_logs/ for:
request.json — Exact request payloadfinal_response.json — Complete response or errorstreaming_chunks.jsonl — All SSE chunks receivedmetadata.json — Performance metrics| Document | Description |
|---|---|
| Technical Documentation | Architecture, internals, provider implementations |
| Library README | Using the resilience library directly |
| Deployment Guide | Hosting on Render, Railway, VPS |
| .env.example | Complete environment variable reference |
This project is dual-licensed:
src/proxy_app/) — MIT Licensesrc/rotator_library/) — LGPL-3.02,188 followers · starred Dec 2025
241 followers · starred Jan 2026
46 followers · starred Dec 2025
36 followers · starred Mar 2026
Universal LLM Gateway: One API, every LLM. OpenAI/Anthropic-compatible endpoints with multi-provider translation and intelligent load-balancing.
Python
558
862 commits
updated Sep 23, 2026
One proxy. Any LLM provider. Zero code changes.
A self-hosted proxy that provides OpenAI and Anthropic compatible API endpoints for all your LLM providers. Works with any application that supports custom OpenAI or Anthropic base URLs—including Claude Code, Opencode, and more—no code changes required in your existing tools.
This project consists of two components:
/v1/chat/completions (OpenAI) and /v1/messages (Anthropic) endpointsproxy_app.exe — the interactive TUI launcher opens# Download and extract the release for your platform
chmod +x proxy_app
./proxy_app
Using the pre-built image (recommended):
# Pull and run directly
docker run -d \
--name llm-api-proxy \
-p 8000:8000 \
-v $(pwd)/.env:/app/.env:ro \
-v $(pwd)/oauth_creds:/app/oauth_creds \
-v $(pwd)/logs:/app/logs \
-v $(pwd)/usage:/app/usage \
-e SKIP_OAUTH_INIT_CHECK=true \
ghcr.io/mirrowel/llm-api-key-proxy:latest
Using Docker Compose:
# Create your .env file and usage directory first, then:
cp .env.example .env
mkdir usage
docker compose up -d
Important: Create the
usage/directory before running Docker Compose so usage stats persist on the host.
Note: For OAuth providers, complete authentication locally first using the credential tool, then mount the
oauth_creds/directory or export credentials to environment variables.
git clone https://github.com/Mirrowel/LLM-API-Key-Proxy.git
cd LLM-API-Key-Proxy
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
python src/proxy_app/main.py
Tip: Running with command-line arguments (e.g.,
--host 0.0.0.0 --port 8000) bypasses the TUI and starts the proxy directly.
Once the proxy is running, configure your application with these settings:
| Setting | Value |
|---|---|
| Base URL / API Endpoint | http://127.0.0.1:8000/v1 |
| API Key | Your PROXY_API_KEY |
provider/model_nameImportant: Models must be specified in the format provider/model_name. The provider/ prefix tells the proxy which backend to route the request to.
gemini/gemini-2.5-flash ← Gemini API
openai/gpt-4o ← OpenAI API
anthropic/claude-3-5-sonnet ← Anthropic API
openrouter/anthropic/claude-3-opus ← OpenRouter
gemini_cli/gemini-2.5-pro ← Gemini CLI (OAuth)
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:8000/v1",
api_key="your-proxy-api-key"
)
response = client.chat.completions.create(
model="gemini/gemini-2.5-flash", # provider/model format
messages=[{"role": "user", "content": "Hello!"}]
)
print(response.choices[0].message.content)
curl -X POST http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer your-proxy-api-key" \
-d '{
"model": "gemini/gemini-2.5-flash",
"messages": [{"role": "user", "content": "What is the capital of France?"}]
}'
http://127.0.0.1:8000/v1PROXY_API_KEYprovider/model_name (e.g., gemini/gemini-2.5-flash)In your configuration file (e.g., config.json):
{
"models": [
{
"title": "Gemini via Proxy",
"provider": "openai",
"model": "gemini/gemini-2.5-flash",
"apiBase": "http://127.0.0.1:8000/v1",
"apiKey": "your-proxy-api-key"
}
]
}
Claude Code natively supports custom Anthropic API endpoints. The recommended setup is to edit your Claude Code settings.json:
{
"env": {
"ANTHROPIC_AUTH_TOKEN": "your-proxy-api-key",
"ANTHROPIC_BASE_URL": "http://127.0.0.1:8000",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "gemini/gemini-3-pro",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "gemini/gemini-3-flash",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "openai/gpt-5-mini"
}
}
Now you can use Claude Code with Gemini, OpenAI, or any other configured provider.
from anthropic import Anthropic
client = Anthropic(
base_url="http://127.0.0.1:8000",
api_key="your-proxy-api-key"
)
# Use any provider through Anthropic's API format
response = client.messages.create(
model="gemini/gemini-3-flash", # provider/model format
max_tokens=1024,
messages=[{"role": "user", "content": "Hello!"}]
)
print(response.content[0].text)
| Endpoint | Description |
|---|---|
GET / | Status check — confirms proxy is running |
POST /v1/chat/completions | Chat completions (OpenAI format) |
POST /v1/messages | Chat completions (Anthropic format) — Claude Code compatible |
POST /v1/messages/count_tokens | Count tokens for Anthropic-format requests |
POST /v1/embeddings | Text embeddings |
GET /v1/models | List all available models with pricing & capabilities |
GET /v1/models/{model_id} | Get details for a specific model |
GET /v1/providers | List configured providers |
POST /v1/token-count | Calculate token count for a payload |
POST /v1/cost-estimate | Estimate cost based on token counts |
Tip: The
/v1/modelsendpoint is useful for discovering available models in your client. Many apps can fetch this list automatically. Add?enriched=falsefor a minimal response without pricing data.
The proxy includes an interactive tool for managing all your API keys and OAuth credentials.
python -m rotator_library.credential_tool
| Type | Providers | How to Add |
|---|---|---|
| API Keys | Gemini, OpenAI, Anthropic, OpenRouter, Groq, Mistral, NVIDIA, Cohere, Chutes | Enter key in TUI or add to .env |
| OAuth | Gemini CLI | Interactive browser login via credential tool |
.env FileCredentials are stored in a .env file. You can edit it directly or use the TUI:
# Required: Authentication key for YOUR proxy
PROXY_API_KEY="your-secret-proxy-key"
# Provider API Keys (add multiple with _1, _2, etc.)
GEMINI_API_KEY_1="your-gemini-key"
GEMINI_API_KEY_2="another-gemini-key"
OPENAI_API_KEY_1="your-openai-key"
ANTHROPIC_API_KEY_1="your-anthropic-key"
Copy
.env.exampleto.envas a starting point.
The proxy is powered by a standalone Python library that you can use directly in your own applications.
asyncio and httpxfrom rotator_library import RotatingClient
client = RotatingClient(
api_keys={"gemini": ["key1", "key2"], "openai": ["key3"]},
global_timeout=30,
max_retries=2
)
async with client:
response = await client.acompletion(
model="gemini/gemini-2.5-flash",
messages=[{"role": "user", "content": "Hello!"}]
)
See the Library README for complete documentation including:
The proxy includes a powerful text-based UI for configuration and management.
| File | Contents |
|---|---|
.env | All credentials and advanced settings |
launcher_config.json | TUI-specific settings (host, port, logging) |
quota_viewer_config.json | Quota viewer remotes + per-provider display toggles |
usage/usage_<provider>.json | Usage persistence per provider |
~/.gemini/)oauth_creds/ directoryOPTIMAL_CONCURRENT_REQUESTS_PER_KEY_<PROVIDER> and MAX_CONCURRENT_REQUESTS_PER_KEY_<PROVIDER>)Gemini CLI:
NVIDIA NIM:
--enable-request-logging--enable-raw-logging (proxy boundary payloads)| Variable | Description | Default |
|---|---|---|
PROXY_API_KEY | Authentication key for your proxy | Required |
OAUTH_REFRESH_INTERVAL | Token refresh check interval (seconds) | 600 |
SKIP_OAUTH_INIT_CHECK | Skip interactive OAuth setup on startup | false |
| Pattern | Description | Example |
|---|---|---|
<PROVIDER>_API_KEY_<N> | API key for provider | GEMINI_API_KEY_1 |
OPTIMAL_CONCURRENT_REQUESTS_PER_KEY_<PROVIDER> | Soft spread-before-stacking target | OPTIMAL_CONCURRENT_REQUESTS_PER_KEY_OPENAI=1 |
MAX_CONCURRENT_REQUESTS_PER_KEY_<PROVIDER> | Hard concurrent request ceiling (<=0 means unlimited) | MAX_CONCURRENT_REQUESTS_PER_KEY_OPENAI=-1 |
ROTATION_MODE_<PROVIDER> | balanced or sequential | ROTATION_MODE_GEMINI=sequential |
IGNORE_MODELS_<PROVIDER> | Blacklist (comma-separated, supports *) | IGNORE_MODELS_OPENAI=*-preview* |
WHITELIST_MODELS_<PROVIDER> | Whitelist (overrides blacklist) | WHITELIST_MODELS_GEMINI=gemini-2.5-pro |
| Variable | Description |
|---|---|
ROTATION_TOLERANCE | 0.0=deterministic, 3.0=weighted random (default) |
CONCURRENCY_MULTIPLIER_<PROVIDER>_PRIORITY_<N> | Concurrency multiplier per priority tier |
QUOTA_GROUPS_<PROVIDER>_<GROUP> | Models sharing quota limits |
OVERRIDE_TEMPERATURE_ZERO | remove or set to prevent tool hallucination |
GEMINI_CLI_QUOTA_REFRESH_INTERVAL | Quota baseline refresh interval in seconds (default: 300) |
Control which models are exposed through your proxy.
# Hide all preview models
IGNORE_MODELS_OPENAI="*-preview*"
# Block all, then allow specific models
IGNORE_MODELS_GEMINI="*"
WHITELIST_MODELS_GEMINI="gemini-2.5-pro,gemini-2.5-flash"
# Block preview models, but allow one specific preview
IGNORE_MODELS_OPENAI="*-preview*"
WHITELIST_MODELS_OPENAI="gpt-4o-2024-08-06-preview"
Logic order: Whitelist check → Blacklist check → Default allow
# Balanced mode defaults to optimal=1 and max=-1, so it spreads first
# but will stack on busy keys instead of blocking when every key is busy.
ROTATION_MODE_OPENAI=balanced
OPTIMAL_CONCURRENT_REQUESTS_PER_KEY_OPENAI=1
MAX_CONCURRENT_REQUESTS_PER_KEY_OPENAI=-1
# Sequential mode defaults to optimal=-1 and max=-1 for sticky/unlimited use.
ROTATION_MODE_GEMINI=sequential
OPTIMAL_CONCURRENT_REQUESTS_PER_KEY_GEMINI=-1
MAX_CONCURRENT_REQUESTS_PER_KEY_GEMINI=-1
# Constrained providers can set optimal and max to the same value.
MAX_CONCURRENT_REQUESTS_PER_KEY_GEMINI_CLI=1
OPTIMAL_CONCURRENT_REQUESTS_PER_KEY_GEMINI_CLI=1
# Mode-specific forms override provider-wide values only for that mode.
MAX_CONCURRENT_REQUESTS_PER_KEY_OPENAI_BALANCED=-1
OPTIMAL_CONCURRENT_REQUESTS_PER_KEY_OPENAI_BALANCED=1
optimal is a soft target used for capacity phases: the rotator prefers credentials below optimal, then stacks on healthy credentials when no below-optimal credential remains. max is the hard safety ceiling; 0 or any negative value means unlimited.
# sequential (default): Use one key until it errors/exhausts, preserving provider-side cache locality
ROTATION_MODE_GEMINI=sequential
# balanced: Distribute load evenly - opt in for per-minute rate limits
ROTATION_MODE_OPENAI=balanced
Paid credentials can handle more concurrent requests. Legacy priority multipliers apply to hard max concurrency; provider-specific optimal multipliers can also raise the soft target where supported:
# Priority 1: 10x concurrency
CONCURRENCY_MULTIPLIER_GEMINI_CLI_PRIORITY_1=10
# Priority 2: 3x
CONCURRENCY_MULTIPLIER_GEMINI_CLI_PRIORITY_2=3
Models sharing quota limits:
# Example: group provider models that share quota
QUOTA_GROUPS_GEMINI_CLI_PRO="gemini-2.5-pro,gemini-3-pro-preview"
Fine-grained control over HTTP timeouts:
TIMEOUT_CONNECT=30 # Connection establishment
TIMEOUT_WRITE=30 # Request body send
TIMEOUT_POOL=60 # Connection pool acquisition
TIMEOUT_READ_STREAMING=180 # Between streaming chunks (3 min)
TIMEOUT_READ_NON_STREAMING=600 # Full response wait (10 min)
Recommendations:
TIMEOUT_READ_STREAMING to 300-360sTIMEOUT_CONNECT to 60sTIMEOUT_READ_NON_STREAMING to 900s+Uses Google OAuth to access internal Gemini endpoints with higher rate limits.
Setup:
python -m rotator_library.credential_tooloauth_creds/gemini_cli_oauth_1.jsonFeatures:
Quota Groups: Models that share quota are automatically grouped:
gemini-2.5-pro, gemini-3-pro-previewgemini-2.0-flash, gemini-2.5-flash, gemini-2.5-flash-litegemini-3-flash-previewAll models in a group deplete the shared quota equally. 24-hour per-model quota windows.
Environment Variables (for stateless deployment):
Single credential (legacy):
GEMINI_CLI_ACCESS_TOKEN="ya29.your-access-token"
GEMINI_CLI_REFRESH_TOKEN="1//your-refresh-token"
GEMINI_CLI_EXPIRY_DATE="1234567890000"
GEMINI_CLI_EMAIL="[email protected]"
GEMINI_CLI_PROJECT_ID="your-gcp-project-id" # Optional
GEMINI_CLI_TIER="standard-tier" # Optional: standard-tier or free-tier
Multiple credentials (use _N_ suffix where N is 1, 2, 3...):
GEMINI_CLI_1_ACCESS_TOKEN="ya29.first-token"
GEMINI_CLI_1_REFRESH_TOKEN="1//first-refresh"
GEMINI_CLI_1_EXPIRY_DATE="1234567890000"
GEMINI_CLI_1_EMAIL="[email protected]"
GEMINI_CLI_1_PROJECT_ID="project-1"
GEMINI_CLI_1_TIER="standard-tier"
GEMINI_CLI_2_ACCESS_TOKEN="ya29.second-token"
GEMINI_CLI_2_REFRESH_TOKEN="1//second-refresh"
GEMINI_CLI_2_EXPIRY_DATE="1234567890000"
GEMINI_CLI_2_EMAIL="[email protected]"
GEMINI_CLI_2_PROJECT_ID="project-2"
GEMINI_CLI_2_TIER="free-tier"
Feature Toggles:
GEMINI_CLI_QUOTA_REFRESH_INTERVAL=300 # Quota refresh interval in seconds (default: 300 = 5 min)
For platforms without file persistence (Railway, Render, Vercel):
Set up credentials locally:
python -m rotator_library.credential_tool
# Complete OAuth flows
Export to environment variables:
python -m rotator_library.credential_tool
# Select "Export [Provider] to .env"
Copy generated variables to your platform:
The tool creates files like gemini_cli_credential_1.env containing all necessary variables.
Set SKIP_OAUTH_INIT_CHECK=true to skip interactive validation on startup.
Customize OAuth callback ports if defaults conflict:
| Provider | Default Port | Environment Variable |
|---|---|---|
| Gemini CLI | 8085 | GEMINI_CLI_OAUTH_PORT |
python src/proxy_app/main.py [OPTIONS]
Options:
--host TEXT Host to bind (default: 0.0.0.0)
--port INTEGER Port to run on (default: 8000)
--enable-request-logging Enable detailed per-request logging
--enable-raw-logging Capture raw proxy I/O payloads
--add-credential Launch interactive credential setup tool
Examples:
# Run on custom port
python src/proxy_app/main.py --host 127.0.0.1 --port 9000
# Run with logging
python src/proxy_app/main.py --enable-request-logging
# Run with raw I/O logging
python src/proxy_app/main.py --enable-raw-logging
# Add credentials without starting proxy
python src/proxy_app/main.py --add-credential
See the Deployment Guide for complete instructions.
Quick Setup:
.env file with your credentialspip install -r requirements.txtuvicorn src.proxy_app.main:app --host 0.0.0.0 --port $PORT.env as a secret fileOAuth Credentials: Export OAuth credentials to environment variables using the credential tool, then add them to your platform's environment settings.
The proxy is available as a multi-architecture Docker image (amd64/arm64) from GitHub Container Registry.
Quick Start with Docker Compose:
# 1. Create your .env file with PROXY_API_KEY and provider keys
cp .env.example .env
nano .env
# 2. Create usage directory (usage_*.json files are created automatically)
mkdir usage
# 3. Start the proxy
docker compose up -d
# 4. Check logs
docker compose logs -f
Important: Create the
usage/directory before running Docker Compose so usage stats persist on the host.
Manual Docker Run:
# Create usage directory if it doesn't exist
mkdir usage
docker run -d \
--name llm-api-proxy \
--restart unless-stopped \
-p 8000:8000 \
-v $(pwd)/.env:/app/.env:ro \
-v $(pwd)/oauth_creds:/app/oauth_creds \
-v $(pwd)/logs:/app/logs \
-v $(pwd)/usage:/app/usage \
-e SKIP_OAUTH_INIT_CHECK=true \
-e PYTHONUNBUFFERED=1 \
ghcr.io/mirrowel/llm-api-key-proxy:latest
Development with Local Build:
# Build and run locally
docker compose -f docker-compose.dev.yml up -d --build
Volume Mounts:
| Path | Purpose |
|---|---|
.env | Configuration and API keys (read-only) |
oauth_creds/ | OAuth credential files (persistent) |
logs/ | Request logs and detailed logging |
usage/ | Usage statistics persistence (usage_*.json) |
Image Tags:
| Tag | Description |
|---|---|
latest | Latest stable from main branch |
dev-latest | Latest from dev branch |
YYYYMMDD-HHMMSS-<sha> | Specific version with timestamp and commit |
OAuth with Docker:
For OAuth providers such as Gemini CLI, you must authenticate locally first:
python -m rotator_library.credential_tool on your local machineoauth_creds/ directory to container, or.env using the export optionOption 1: Authenticate locally, deploy credentials
.env to your serverOption 2: SSH Port Forwarding
# Forward callback ports through SSH
ssh -L 51121:localhost:51121 -L 8085:localhost:8085 user@your-vps
# Then run credential tool on the VPS
Systemd Service:
[Unit]
Description=LLM API Key Proxy
After=network.target
[Service]
Type=simple
WorkingDirectory=/path/to/LLM-API-Key-Proxy
ExecStart=/path/to/python -m uvicorn src.proxy_app.main:app --host 0.0.0.0 --port 8000
Restart=always
[Install]
WantedBy=multi-user.target
See VPS Deployment for complete guide.
| Issue | Solution |
|---|---|
401 Unauthorized | Verify PROXY_API_KEY matches your Authorization: Bearer header exactly |
500 Internal Server Error | Check provider key validity; enable --enable-request-logging for details |
| All keys on cooldown | All keys failed recently; check logs/detailed_logs/ for upstream errors |
| Model not found | Verify format is provider/model_name (e.g., gemini/gemini-2.5-flash) |
| OAuth callback failed | Ensure callback port (8085, 51121, 11451) isn't blocked by firewall |
| Streaming hangs | Increase TIMEOUT_READ_STREAMING; check provider status |
Detailed Logs:
When --enable-request-logging is enabled, check logs/detailed_logs/ for:
request.json — Exact request payloadfinal_response.json — Complete response or errorstreaming_chunks.jsonl — All SSE chunks receivedmetadata.json — Performance metrics| Document | Description |
|---|---|
| Technical Documentation | Architecture, internals, provider implementations |
| Library README | Using the resilience library directly |
| Deployment Guide | Hosting on Render, Railway, VPS |
| .env.example | Complete environment variable reference |
This project is dual-licensed:
src/proxy_app/) — MIT Licensesrc/rotator_library/) — LGPL-3.02,188 followers · starred Dec 2025
241 followers · starred Jan 2026
46 followers · starred Dec 2025
36 followers · starred Mar 2026