{"id":51248990,"url":"https://github.com/caidaoli/ccLoad","last_synced_at":"2026-07-17T17:00:55.563Z","repository":{"id":313845625,"uuid":"1052655059","full_name":"caidaoli/ccLoad","owner":"caidaoli","description":"AI API gateway that ends manual channel switching with smart routing, auto failover, exponential cooldown, multi-URL scheduling, live request monitoring and soft-error detection.","archived":false,"fork":false,"pushed_at":"2026-07-14T00:49:09.000Z","size":10440,"stargazers_count":351,"open_issues_count":2,"forks_count":59,"subscribers_count":4,"default_branch":"master","last_synced_at":"2026-07-14T02:21:35.454Z","etag":null,"topics":["ai","ai-gateway","anthropic","api-proxy","claude-api","claude-code","codex","cost-control","failover","gemini","golang","llm-proxy","load-balancer","monitoring","openai","openai-api","protocol-transform","reverse-proxy","smart-routing","token-counting"],"latest_commit_sha":null,"homepage":"https://www.ccload.xyz","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/caidaoli.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-08T11:10:18.000Z","updated_at":"2026-07-14T00:49:14.000Z","dependencies_parsed_at":"2025-11-27T09:11:09.271Z","dependency_job_id":"a4b61c3b-fbea-4e61-8459-04a5ca8de27d","html_url":"https://github.com/caidaoli/ccLoad","commit_stats":null,"previous_names":["caidaoli/ccload"],"tags_count":540,"template":false,"template_full_name":null,"purl":"pkg:github/caidaoli/ccLoad","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caidaoli%2FccLoad","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caidaoli%2FccLoad/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caidaoli%2FccLoad/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caidaoli%2FccLoad/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/caidaoli","download_url":"https://codeload.github.com/caidaoli/ccLoad/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/caidaoli%2FccLoad/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35588705,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-17T02:00:06.162Z","response_time":116,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","ai-gateway","anthropic","api-proxy","claude-api","claude-code","codex","cost-control","failover","gemini","golang","llm-proxy","load-balancer","monitoring","openai","openai-api","protocol-transform","reverse-proxy","smart-routing","token-counting"],"created_at":"2026-06-29T06:04:24.581Z","updated_at":"2026-07-17T17:00:55.547Z","avatar_url":"https://github.com/caidaoli.png","language":"Go","funding_links":[],"categories":["LLMOps"],"sub_categories":["LLM Gateways \u0026 Proxies"],"readme":"![ccLoad admin dashboard](images/ccload.jpg)\n\n# ccLoad\n\n**AI API gateway for Claude Code, Codex, Gemini, and OpenAI.**\n\n**English | [简体中文](README.zh-CN.md)**\n\n[![Go](https://img.shields.io/badge/Go-1.25+-00ADD8.svg)](https://golang.org)\n[![Gin](https://img.shields.io/badge/Gin-v1.12+-blue.svg)](https://github.com/gin-gonic/gin)\n[![Docker](https://img.shields.io/badge/Docker-Supported-2496ED.svg)](https://hub.docker.com)\n[![Hugging Face](https://img.shields.io/badge/%F0%9F%A4%97%20Hugging%20Face-Spaces-yellow)](https://huggingface.co/spaces)\n[![GitHub Actions](https://img.shields.io/badge/CI%2FCD-GitHub%20Actions-2088FF.svg)](https://github.com/features/actions)\n[![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)\n\n\u003e Smart routing | Automatic failover | Exponential cooldown | Multi-URL scheduling | Protocol transforms | Live monitoring | Cost control\n\nccLoad removes the operational mess of running multiple AI API upstreams. It keeps Claude Code, Codex, Gemini, and OpenAI-compatible clients on one stable gateway, then handles upstream selection, failover, cooldown, protocol conversion, request visibility, and cost limits in the service instead of in every client script.\n\n## 🎯 What ccLoad Solves\n\nCommon failure modes when you run several AI API channels:\n\n- **Manual channel switching**: Different keys, validity windows, quotas, and upstream URLs quickly become hard to manage.\n- **Rate limits and upstream failures**: `429`, `502`, `504`, expired keys, and overloaded providers should not stop the client workflow.\n- **Opaque request status**: Without live request visibility, long streaming requests become guesswork.\n- **HTTP 200 with error content**: Some upstreams return a successful HTTP status while the response body is an actual error.\n- **Cost drift**: Shared gateways need per-channel and per-token limits, not spreadsheet accounting after the bill arrives.\n\nccLoad handles those cases with:\n\n- **Smart routing**: High-priority channels are selected first; channels at the same priority use smooth weighted round-robin.\n- **Automatic failover**: Failed keys, channels, and URLs are skipped according to the classified error type.\n- **Exponential cooldown**: Unhealthy upstreams back off automatically instead of being hammered by retries.\n- **Multi-URL scheduling**: A single channel can use multiple upstream URLs, weighted by observed latency and health.\n- **Protocol transforms**: Anthropic, OpenAI, Gemini, and Codex request/response families can be converted at the gateway.\n- **Live monitoring**: Active requests, logs, token usage, TTFB, cost, and upstream details are visible in the web dashboard.\n- **Soft-error detection**: HTTP 200 responses that are actually errors trigger the same failover path as regular upstream failures. Common cases include:\n  - JSON responses containing `{\"error\": {...}}` structure\n  - Responses with `type` field set to `\"error\"`\n  - Explicit rate limits in SSE `error` events (`rate_limit_exceeded` / `too_many_requests`) are handled as `429`\n  - Plain text messages like `\"当前模型负载过高\"` / `\"Current model load too high\"` (load warnings)\n\n## ✨ Key Features\n\n- 🚀 **High-Performance Architecture** - Gin framework, 1000+ concurrent connections, high-performance caching\n- 🧮 **Local Token Counting** - API-compliant local token estimation, \u003c5ms response, 93%+ accuracy, supports large-scale tool scenarios\n- 🎯 **Smart Error Classification** - Distinguishes Key/Channel/Client errors, soft error detection (200 masquerading as error), SSE rate-limit errors as 429, 1308 quota handling\n- 🔀 **Smart Routing** - Priority + smooth weighted round-robin channel selection, **pre-filters cooled channels**, multi-key load balancing, **health-based dynamic sorting** (confidence factor prevents small sample over-penalization)\n- 🛡️ **Failover** - Automatic failure detection with exponential backoff cooldown (1s → 2s → 4s → ... → 30min)\n- 🔒 **Race-Safe** - Key selector race condition protection, startup config validation, automatic resource cleanup\n- 📊 **Real-time Monitoring** - Built-in trend analysis, logging, and stats dashboard, **Token usage stats** with time range selection and per-token classification\n- 🎯 **Transparent Proxy** - Supports Claude Code, Codex, Gemini, and OpenAI compatible APIs with smart auth detection\n- 📦 **Single Binary Deployment** - No external dependencies, embedded SQLite included\n- 🔒 **Secure Authentication** - Token-based admin interface and API access control\n- 🏷️ **Build Tags** - GOTAGS support, high-performance JSON library enabled by default\n- 🐳 **Docker Support** - Multi-arch images (amd64/arm64), automated CI/CD\n- ☁️ **Cloud Native** - Container deployment support, GitHub Actions auto-build\n- 🤗 **Hugging Face** - One-click deployment to Hugging Face Spaces, free hosting\n- 💰 **Cost Limits** - Per-channel daily cost limits, per-token cost limits\n- 🚦 **Channel RPM Limits** - Per-channel rolling 60-second request caps, 0=unlimited\n- 🚧 **Channel Concurrency Limits** - Per-channel in-flight request caps, 0=unlimited\n- 🔐 **Token Restrictions** - API token cost limits + model restrictions for fine-grained access control\n- ⏱️ **TTFB Monitoring** - Streaming request first byte time tracking for upstream latency diagnosis\n- 🌐 **Multi-URL Load Balancing** - Multiple URLs per channel with latency-weighted random selection\n- 💵 **service_tier Pricing** - OpenAI priority/flex/default tier multipliers for accurate cost accounting\n- 🖼️ **Image Tool Billing** - Responses image_generation/gpt-image-2 cost accounting\n- 📉 **Tiered Pricing** - GPT-5.4/Qwen-Plus/Gemini long-context step pricing, auto-applies lower rate at token thresholds\n- 🔄 **Protocol Transform** - Anthropic/OpenAI/Gemini/Codex cross-protocol conversion, preserving sampling and thinking parameters so one channel serves multiple client protocols\n- 💬 **Conversational Model Testing** - Channel/model/chat testing modes with image upload, reasoning level, built-in search, and chat export\n- 🔍 **Debug Logs** - Upstream request/response raw data capture with sensitive header masking, essential for troubleshooting\n- 🕐 **Scheduled Checks** - Background periodic channel availability probing, auto-detect failed channels\n- 🔄 **Auto Updates** - Checks for new releases every 12 hours by default, configurable from the admin settings page\n- 🧩 **Custom Request Rules** - Per-channel HTTP header \u0026 JSON body rewriting (remove/override/append), with auth header protection, CRLF guard, and capacity caps\n- 🎛️ **Log Column Customization** - Show/hide table columns per preference, settings persist in browser localStorage\n\n## 🏗️ Architecture Overview\n\n```mermaid\ngraph TB\n    subgraph \"Client\"\n        A[User App] --\u003e B[ccLoad Proxy]\n    end\n\n    subgraph \"ccLoad Service\"\n        B --\u003e C[Auth Layer]\n        C --\u003e D[Route Dispatcher]\n        D --\u003e E[Channel Selector]\n        E --\u003e F[Load Balancer]\n\n        subgraph \"Core Components\"\n            F --\u003e G[Channel A\u003cbr/\u003ePriority:10]\n            F --\u003e H[Channel B\u003cbr/\u003ePriority:5]\n            F --\u003e I[Channel C\u003cbr/\u003ePriority:5]\n            G --\u003e G1[URL Selector\u003cbr/\u003eWeighted Random]\n            H --\u003e H1[URL Selector\u003cbr/\u003eWeighted Random]\n            I --\u003e I1[URL Selector\u003cbr/\u003eWeighted Random]\n        end\n\n        subgraph \"Storage Layer\"\n            J[(Storage Factory)]\n            J3[Schema Definition]\n            J4[Unified SQL Layer]\n            J1[(SQLite)]\n            J2[(MySQL)]\n            J5[(PostgreSQL)]\n            J --\u003e J3\n            J3 --\u003e J4\n            J4 --\u003e J1\n            J4 --\u003e J2\n            J4 --\u003e J5\n        end\n\n        subgraph \"Monitoring Layer\"\n            K[Log System]\n            L[Stats Analysis]\n            M[Trend Charts]\n        end\n    end\n\n    subgraph \"Upstream Services\"\n        G1 --\u003e N[Claude API]\n        H1 --\u003e O[Claude API]\n        I1 --\u003e P[Claude API]\n    end\n\n    E \u003c--\u003e J\n    F \u003c--\u003e J\n    K \u003c--\u003e J\n    L \u003c--\u003e J\n    M \u003c--\u003e J\n\n    style B fill:#4F46E5,stroke:#000,color:#fff\n    style F fill:#059669,stroke:#000,color:#fff\n    style E fill:#0EA5E9,stroke:#000,color:#fff\n```\n\n## 🚀 Quick Start\n\nChoose the deployment method that suits you best:\n\n| Method | Difficulty | Cost | Use Case | HTTPS | Persistence |\n|--------|------------|------|----------|-------|-------------|\n| 🐳 **Docker** | ⭐⭐ | VPS required | Production, high performance | Config required | ✅ |\n| 🤗 **Hugging Face** | ⭐ | **Free** | Personal use, quick trial | ✅ Auto | ✅ |\n| 🔧 **Source Build** | ⭐⭐⭐ | Server required | Development, customization | Config required | ✅ |\n| 📦 **Binary** | ⭐⭐ | Server required | Lightweight, simple setup | Config required | ✅ |\n\n### Method 1: Docker Deployment (Recommended)\n\n**Using pre-built images (Recommended)**:\n```bash\n# Option 1: Using docker-compose (Simplest)\ncurl -o docker-compose.yml https://raw.githubusercontent.com/caidaoli/ccLoad/master/docker-compose.yml\ncurl -o .env https://raw.githubusercontent.com/caidaoli/ccLoad/master/.env.example\n# Edit .env file to set password\ndocker-compose up -d\n\n# Option 2: Run image directly\ndocker pull ghcr.io/caidaoli/ccload:latest\ndocker run -d --name ccload \\\n  -p 8080:8080 \\\n  -e CCLOAD_PASS=your_secure_password \\\n  -v ccload_data:/app/data \\\n  ghcr.io/caidaoli/ccload:latest\n```\n\n**Building from source**:\n```bash\n# Clone project\ngit clone https://github.com/caidaoli/ccLoad.git\ncd ccLoad\n\n# Build and run with docker-compose\ndocker-compose -f docker-compose.build.yml up -d\n\n# Or build manually\ndocker build -t ccload:local .\ndocker run -d --name ccload \\\n  -p 8080:8080 \\\n  -e CCLOAD_PASS=your_secure_password \\\n  -v ccload_data:/app/data \\\n  ccload:local\n```\n\n### Method 2: Source Build\n\n```bash\n# Clone project\ngit clone https://github.com/caidaoli/ccLoad.git\ncd ccLoad\n\n# Build project (uses high-performance JSON library by default)\ngo build -tags sonic -o ccload .\n\n# Or use Makefile\nmake build\n\n# Run in development mode\ngo run -tags sonic .\n# Or\nmake dev\n```\n\n### Method 3: Binary Download\n\n```bash\n# Download binary for your platform from GitHub Releases\nwget https://github.com/caidaoli/ccLoad/releases/latest/download/ccload-linux-amd64\nchmod +x ccload-linux-amd64\n./ccload-linux-amd64\n```\n\n### Method 4: Hugging Face Spaces Deployment\n\nHugging Face Spaces provides free container hosting with Docker support, ideal for personal and small team use.\n\n#### Deployment Steps\n\n1. **Login to Hugging Face**\n\n   Visit [huggingface.co](https://huggingface.co) and log into your account\n\n2. **Create New Space**\n\n   - Click \"New\" → \"Space\" in the top right\n   - **Space name**: `ccload` (or custom name)\n   - **License**: `MIT`\n   - **Select the SDK**: `Docker`\n   - **Visibility**: `Public` or `Private` (private requires paid subscription)\n   - Click \"Create Space\"\n\n3. **Create Dockerfile**\n\n   Create a `Dockerfile` in the Space repository:\n\n   ```dockerfile\n   FROM ghcr.io/caidaoli/ccload:latest\n   ENV TZ=Asia/Shanghai\n   ENV PORT=7860\n   ENV SQLITE_PATH=/tmp/ccload.db\n   EXPOSE 7860\n   ```\n\n   Create via:\n\n   **Method A - Web Interface** (Recommended):\n   - Click \"Files\" tab on Space page\n   - Click \"Add file\" → \"Create a new file\"\n   - Enter `Dockerfile` as filename\n   - Paste the content above\n   - Click \"Commit new file to main\"\n\n   **Method B - Git Command Line**:\n   ```bash\n   # Clone your Space repository\n   git clone https://huggingface.co/spaces/YOUR_USERNAME/ccload\n   cd ccload\n\n   # Create Dockerfile\n   cat \u003e Dockerfile \u003c\u003c 'EOF'\n   FROM ghcr.io/caidaoli/ccload:latest\n   ENV TZ=Asia/Shanghai\n   ENV PORT=7860\n   ENV SQLITE_PATH=/tmp/ccload.db\n   EXPOSE 7860\n   EOF\n\n   # Commit and push\n   git add Dockerfile\n   git commit -m \"Add Dockerfile for ccLoad deployment\"\n   git push\n   ```\n\n4. **Configure Environment Variables (Secrets)**\n\n   In Space settings (Settings → Variables and secrets → New secret):\n\n   | Variable | Value | Required | Description |\n   |----------|-------|----------|-------------|\n   | `CCLOAD_PASS` | `your_admin_password` | ✅ **Required** | Admin interface password |\n   | `CCLOAD_API_TOKENS` | `token1\\|production,token2\\|development` | Optional | Pre-seed API access tokens on startup |\n\n   **Note**: API access tokens can be pre-seeded with `CCLOAD_API_TOKENS` or managed in the Web admin interface `/web/tokens.html`.\n\n5. **Wait for Build and Startup**\n\n   After pushing Dockerfile, Hugging Face will automatically:\n   - Pull pre-built image (~30 seconds)\n   - Start application container (~10 seconds)\n   - Total time ~1-2 minutes (3-5x faster than source build)\n\n6. **Access Application**\n\n   After build completes, access via:\n   - **App URL**: `https://YOUR_USERNAME-ccload.hf.space`\n   - **Admin Interface**: `https://YOUR_USERNAME-ccload.hf.space/web/`\n   - **API Endpoint**: `https://YOUR_USERNAME-ccload.hf.space/v1/messages`\n\n   **First Access Note**:\n   - If Space is sleeping, first access takes 20-30 seconds to wake\n   - Subsequent accesses respond immediately\n\n#### Hugging Face Deployment Characteristics\n\n**Advantages**:\n- ✅ **Completely Free**: Public Spaces are permanently free with CPU and storage\n- ✅ **Fast Deployment**: Pre-built image, 1-2 minutes (3-5x faster than source build)\n- ✅ **Auto HTTPS**: No SSL certificate configuration needed\n- ✅ **Auto Restart**: Automatic restart after crashes\n- ✅ **Version Control**: Git-based, easy rollback and collaboration\n- ✅ **Simple Maintenance**: Only 5-line Dockerfile, no source code management\n\n**Limitations**:\n- ⚠️ **Resource Limits**: Free tier provides 2 CPU + 16GB RAM\n- ⚠️ **Sleep Policy**: 48 hours without access triggers sleep, first access takes ~20-30s to wake\n- ⚠️ **Fixed Port**: Must use port 7860\n- ⚠️ **Public Access**: Spaces are public by default, must configure API tokens via Web admin to access /v1/* APIs (otherwise 401)\n\n#### Data Persistence\n\n**Important**: Hugging Face Spaces Storage Policy\n\nDue to Hugging Face Spaces limitations (`/tmp` directory clears on restart), **we strongly recommend using an external MySQL or PostgreSQL database** for complete data persistence:\n\n**Option 1: Hybrid Storage Mode (Recommended, Best Performance)**\n- ✅ **Ultra-fast queries**: Reads go through local SQLite, avoiding remote database latency\n- ✅ **Restart-safe**: Durable data is stored in MySQL/PostgreSQL and restored on startup\n- ✅ **Stats caching**: Smart TTL cache reduces repetitive aggregate queries\n- Configuration: Add one primary DSN (`CCLOAD_MYSQL` or `CCLOAD_POSTGRES`) plus `CCLOAD_ENABLE_SQLITE_REPLICA=1` in Secrets\n\n**Dockerfile Example (Hybrid Mode)**:\n```dockerfile\nFROM ghcr.io/caidaoli/ccload:latest\nENV TZ=Asia/Shanghai\nENV PORT=7860\n# Configure in Secrets: CCLOAD_MYSQL or CCLOAD_POSTGRES, plus CCLOAD_ENABLE_SQLITE_REPLICA=1\nEXPOSE 7860\n```\n\n**Option 2: Pure External Database Mode**\n- ✅ **Complete Persistence**: Channel configs, logs, and stats all preserved\n- ✅ **Restart-Safe**: Data stored externally, unaffected by Space restarts\n- ⚠️ **Database Latency**: Stats page latency depends on the remote database and region\n- Configuration: Add exactly one of `CCLOAD_MYSQL` or `CCLOAD_POSTGRES` in Secrets\n\n**Recommended Free MySQL Services**:\n- [TiDB Cloud Serverless](https://tidbcloud.com/) - Free 5GB storage, MySQL compatible, no connection limits, recommended first choice\n- [Aiven for MySQL](https://aiven.io/) - Free 1GB storage, multi-region support\n\n**MySQL Configuration Example (TiDB Cloud)**:\n1. Register for [TiDB Cloud](https://tidbcloud.com/) account\n2. Create Serverless Cluster (free)\n3. Get connection info, format: `user:password@tcp(host:4000)/database?tls=true`\n4. Add `CCLOAD_MYSQL` variable in Hugging Face Space Secrets\n5. **(Optional) Enable Hybrid Mode**: Add `CCLOAD_ENABLE_SQLITE_REPLICA=1` for best performance\n6. Restart Space, all data will auto-persist to MySQL\n\n**PostgreSQL Configuration Example**:\n```bash\nCCLOAD_POSTGRES=postgres://user:password@host:5432/ccload?sslmode=require\n```\n\nURL and libpq keyword DSNs are supported. Do not set `CCLOAD_MYSQL` and `CCLOAD_POSTGRES` at the same time.\n\n**Dockerfile Example (Pure External Database)**:\n```dockerfile\nFROM ghcr.io/caidaoli/ccload:latest\nENV TZ=Asia/Shanghai\nENV PORT=7860\n# Configure CCLOAD_MYSQL or CCLOAD_POSTGRES in Secrets; SQLITE_PATH is not required\nEXPOSE 7860\n```\n\n**Option 3: Local Storage Only (Not Recommended)**\n- ⚠️ **Data Loss**: `/tmp` clears on Space restart, channel config lost\n- ⚠️ **Manual Recovery**: Must re-import via Web interface or CSV\n- Use case: Temporary testing only\n\n#### Update Deployment\n\nWith pre-built images, updates are simple:\n\n**Image Refresh**:\n- When new version image (`ghcr.io/caidaoli/ccload:latest`) is released\n- Click \"Factory rebuild\" in Space settings to pull latest image\n- Or wait for Hugging Face auto-restart (typically after 48 hours)\n\n**Manual Trigger Update**:\n```bash\n# Add empty commit to trigger rebuild\ngit commit --allow-empty -m \"Trigger rebuild to pull latest image\"\ngit push\n```\n\n**Version Pinning** (Optional):\nTo lock specific version, modify Dockerfile:\n```dockerfile\nFROM ghcr.io/caidaoli/ccload:v2.44.1  # Specify version\nENV TZ=Asia/Shanghai\nENV PORT=7860\nENV SQLITE_PATH=/tmp/ccload.db\nEXPOSE 7860\n```\n\n### Basic Configuration\n\nChoose SQLite, MySQL, or PostgreSQL based on the deployment shape. MySQL and PostgreSQL are mutually exclusive.\n\n**SQLite Mode (Default)**:\n```bash\n# Set environment variables\nexport CCLOAD_PASS=your_admin_password\nexport PORT=8080\nexport SQLITE_PATH=./data/ccload.db\n\n# Or use .env file\necho \"CCLOAD_PASS=your_admin_password\" \u003e .env\necho \"PORT=8080\" \u003e\u003e .env\necho \"SQLITE_PATH=./data/ccload.db\" \u003e\u003e .env\n\n# Start service\n./ccload\n```\n\n**MySQL Mode**:\n```bash\n# 1. Create MySQL database\nmysql -u root -p -e \"CREATE DATABASE ccload CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\"\n\n# 2. Set environment variables\nexport CCLOAD_PASS=your_admin_password\nexport CCLOAD_MYSQL=\"user:password@tcp(localhost:3306)/ccload?charset=utf8mb4\"\nexport PORT=8080\n\n# Or use .env file\necho \"CCLOAD_PASS=your_admin_password\" \u003e .env\necho \"CCLOAD_MYSQL=user:password@tcp(localhost:3306)/ccload?charset=utf8mb4\" \u003e\u003e .env\necho \"PORT=8080\" \u003e\u003e .env\n\n# 3. Start service (auto-creates tables)\n./ccload\n```\n\n**PostgreSQL Mode**:\n```bash\n# 1. Create the database and user in PostgreSQL\n\n# 2. Set environment variables\nexport CCLOAD_PASS=your_admin_password\nexport CCLOAD_POSTGRES=\"postgres://user:password@localhost:5432/ccload?sslmode=disable\"\nexport PORT=8080\n\n# 3. Start service (auto-creates and migrates tables)\n./ccload\n```\n\n**Docker + MySQL**:\n```bash\n# Option 1: docker-compose (Recommended)\ncat \u003e docker-compose.mysql.yml \u003c\u003c 'EOF'\nversion: '3.8'\nservices:\n  mysql:\n    image: mysql:8.0\n    environment:\n      MYSQL_ROOT_PASSWORD: rootpass\n      MYSQL_DATABASE: ccload\n      MYSQL_USER: ccload\n      MYSQL_PASSWORD: ccloadpass\n    volumes:\n      - mysql_data:/var/lib/mysql\n    ports:\n      - \"3306:3306\"\n    healthcheck:\n      test: [\"CMD\", \"mysqladmin\", \"ping\", \"-h\", \"localhost\"]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n\n  ccload:\n    image: ghcr.io/caidaoli/ccload:latest\n    environment:\n      CCLOAD_PASS: your_admin_password\n      CCLOAD_MYSQL: \"ccload:ccloadpass@tcp(mysql:3306)/ccload?charset=utf8mb4\"\n      PORT: 8080\n    ports:\n      - \"8080:8080\"\n    depends_on:\n      mysql:\n        condition: service_healthy\n\nvolumes:\n  mysql_data:\nEOF\n\ndocker-compose -f docker-compose.mysql.yml up -d\n\n# Option 2: Direct run (requires existing MySQL service)\ndocker run -d --name ccload \\\n  -p 8080:8080 \\\n  -e CCLOAD_PASS=your_admin_password \\\n  -e CCLOAD_MYSQL=\"user:pass@tcp(mysql_host:3306)/ccload?charset=utf8mb4\" \\\n  ghcr.io/caidaoli/ccload:latest\n```\n\n**Docker + PostgreSQL** (requires an existing PostgreSQL service):\n```bash\ndocker run -d --name ccload \\\n  -p 8080:8080 \\\n  -e CCLOAD_PASS=your_admin_password \\\n  -e CCLOAD_POSTGRES=\"postgres://user:pass@postgres_host:5432/ccload?sslmode=require\" \\\n  ghcr.io/caidaoli/ccload:latest\n```\n\nAfter service starts, access:\n- Admin Interface: `http://localhost:8080/web/`\n- API Proxy: `POST http://localhost:8080/v1/messages`\n- **API Token Management**: `http://localhost:8080/web/tokens.html` - Configure API access tokens via Web interface\n\n## 📖 Usage Guide\n\n### API Proxy\n\n**Claude API Proxy (Requires Auth)**:\n\nFirst, configure API access token in Web admin interface `http://localhost:8080/web/tokens.html`, then use that token to access API:\n\n```bash\ncurl -X POST http://localhost:8080/v1/messages \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer your-api-token\" \\\n  -H \"x-api-key: your-claude-api-key\" \\\n  -H \"anthropic-version: 2023-06-01\" \\\n  -d '{\n    \"model\": \"claude-sonnet-4-6\",\n    \"max_tokens\": 1024,\n    \"messages\": [\n      {\n        \"role\": \"user\",\n        \"content\": \"Hello, Claude!\"\n      }\n    ]\n  }'\n```\n\n**OpenAI Compatible API Proxy (Chat Completions)**:\n\n```bash\ncurl -X POST http://localhost:8080/v1/chat/completions \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer your-api-token\" \\\n  -d '{\n    \"model\": \"gpt-4o\",\n    \"messages\": [\n      {\n        \"role\": \"user\",\n        \"content\": \"Hello!\"\n      }\n    ]\n  }'\n```\n\n**Codex Alpha Search (Native Passthrough Only)**:\n\n`POST /v1/alpha/search` accepts the native Codex search payload. The `model` field is optional. This endpoint is forwarded only to channels whose resolved upstream protocol is Codex; it is not handled by local cross-protocol transforms.\n\n```bash\ncurl -X POST http://localhost:8080/v1/alpha/search \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Authorization: Bearer your-api-token\" \\\n  -d '{\n    \"query\": \"golang channels\"\n  }'\n```\n\nFor a regular channel base URL, ccLoad appends `/v1/alpha/search`. If the channel uses the trailing `#` exact-URL marker, the configured URL must already point to this endpoint, for example `https://upstream.example.com/v1/alpha/search#`. Responses-only fields `prompt_cache_key` and `prompt_cache_retention` are removed before forwarding.\n\n### Local Token Counting\n\nQuickly estimate request token consumption (no upstream API call needed):\n\n```bash\ncurl -X POST http://localhost:8080/v1/messages/count_tokens \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"model\": \"claude-sonnet-4-6\",\n    \"messages\": [\n      {\"role\": \"user\", \"content\": \"Hello, how are you?\"}\n    ],\n    \"system\": \"You are a helpful assistant.\"\n  }'\n\n# Response example\n# {\n#   \"input_tokens\": 28\n# }\n```\n\n**Features**:\n- ✅ Compliant with Anthropic official API spec\n- ✅ Local computation, \u003c5ms response, no API quota consumption\n- ✅ 93%+ accuracy (compared to official API)\n- ✅ Supports system prompts, tool definitions, large-scale tool scenarios\n- ✅ Requires auth token (configure at `/web/tokens.html`)\n\n### Channel Management\n\nManage channels via Web interface `/web/channels.html` or API:\n\n```bash\n# Add channel (supports multiple URLs, comma-separated)\ncurl -X POST http://localhost:8080/admin/channels \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"name\": \"Claude-API\",\n    \"api_key\": \"sk-ant-api03-xxx\",\n    \"url\": \"https://api.anthropic.com,https://api2.anthropic.com\",\n    \"priority\": 10,\n    \"rpm_limit\": 0,\n    \"max_concurrency\": 0,\n    \"models\": [\"claude-sonnet-4-6\", \"claude-opus-4-6\"],\n    \"enabled\": true\n  }'\n```\n\n\u003e **Multi-URL Note**: The `url` field supports comma-separated multiple URLs. The system uses latency-weighted random selection for optimal URL choice, with automatic cooldown for failed URLs, enabling URL-level load balancing and failover within a single channel.\n\n\u003e **RPM Limit Note**: `rpm_limit` is a per-channel request cap over a rolling 60-second window; `0` means unlimited. Proxy forwarding, manual tests, single-URL tests, and scheduled checks all count toward the cap. Multi-URL failover counts each actual upstream HTTP request. The counter is in-memory: restart clears it, and multiple instances count independently.\n\n\u003e **Concurrency Limit Note**: `max_concurrency` is a per-channel cap on simultaneous in-flight upstream requests; `0` means unlimited. A slot is acquired before the upstream request starts and released when the response body is closed, so streaming requests hold the slot until the stream ends. Over-limit channels are skipped without cooldown. The counter is in-memory and per instance.\n\n### Custom Request Rules (Advanced)\n\nThe \"Advanced\" button in the channel editor opens a secondary modal that lets you rewrite the **HTTP headers** and **JSON request body** forwarded upstream at channel granularity. Typical use cases include `User-Agent` override, forcing API version headers, or tweaking fields like `thinking` / `max_tokens`. Rules apply in configured order and take effect for all subsequent requests on that channel as soon as they are saved.\n\n**Action matrix**:\n\n| Target | `remove` | `override` | `append` |\n|---|---|---|---|\n| HTTP Header | Delete the named header (supports token-level removal on multi-value headers such as `Anthropic-Beta`) | `Header.Set` replaces all values | `Header.Add` appends a value (multi-value semantics) |\n| JSON Body | Delete a field/array element by dotted path | Set the value at a path, creating intermediate nodes as needed | Not supported (ambiguous in JSON) |\n\n**JSON path syntax**:\n- Dotted path + numeric array index: `thinking.budget_tokens`, `messages.0.role`, `generation_config.temperature`\n- Values accept any JSON literal: number `0.7`, boolean `true`, string `\"claude-opus-4-6\"`, object `{\"type\":\"adaptive\"}`, array `[\"a\",\"b\"]`\n\n**Safety constraints** (hard-enforced server-side even if the frontend is bypassed):\n- **Auth header blacklist**: any rule targeting `Authorization`, `x-api-key`, or `x-goog-api-key` (case-insensitive) is silently ignored and logged via `slog.Warn`\n- **CRLF injection guard**: header names/values must not contain `\\r\\n`\n- **Non-JSON body passthrough**: requests without `application/json` content type, empty bodies, or bodies that fail to deserialize are forwarded untouched without blocking\n- **Capacity caps**: ≤ 32 header rules and ≤ 32 body rules per channel, each value ≤ 8 KB; violations return HTTP 400\n\n**Typical example**:\n```jsonc\n{\n  \"custom_request_rules\": {\n    \"headers\": [\n      { \"action\": \"override\", \"name\": \"User-Agent\", \"value\": \"claude-cli/1.0 (custom)\" },\n      { \"action\": \"remove\",   \"name\": \"Anthropic-Beta\", \"value\": \"context-1m-2025-08-07\" },\n      { \"action\": \"append\",   \"name\": \"Accept\", \"value\": \"application/json\" }\n    ],\n    \"body\": [\n      { \"action\": \"override\", \"path\": \"thinking\", \"value\": {\"type\":\"adaptive\"} },\n      { \"action\": \"override\", \"path\": \"max_tokens\", \"value\": 4096 },\n      { \"action\": \"remove\",   \"path\": \"stop_sequences\" }\n    ]\n  }\n}\n```\n\n\u003e **Interaction with built-in logic**: Custom rules run **after** the anyrouter `anthropic-beta` injection and anyrouter adaptive-thinking fallback, so they can override or remove those fields. Generated Anthropic requests use `thinking.type=adaptive` plus `output_config.effort` for thinking depth; anyrouter `/v1/messages` additionally fills missing thinking and normalizes legacy `thinking.type=enabled`. Authentication headers remain unmodifiable at all times.\n\n### Batch Data Management\n\nSupports CSV format for channel config import/export:\n\n**Export Config**:\n```bash\n# Web interface: Visit /web/channels.html, click \"Export CSV\" button\n# API call:\ncurl -H \"Authorization: Bearer your_token\" \\\n  http://localhost:8080/admin/channels/export \u003e channels.csv\n```\n\n**Import Config**:\n```bash\n# Web interface: Visit /web/channels.html, click \"Import CSV\" button\n# API call:\ncurl -X POST -H \"Authorization: Bearer your_token\" \\\n  -F \"file=@channels.csv\" \\\n  http://localhost:8080/admin/channels/import\n```\n\n**CSV Format Example**:\n```csv\nname,api_key,url,priority,models,enabled\nClaude-API-1,sk-ant-xxx,https://api.anthropic.com,10,\"[\\\"claude-sonnet-4-6\\\"]\",true\nClaude-API-2,sk-ant-yyy,https://api.anthropic.com,5,\"[\\\"claude-opus-4-6\\\"]\",true\n```\n\n**Features**:\n- Auto column name mapping (Chinese/English)\n- Smart data validation with error messages\n- Incremental import and overwrite update\n- UTF-8 encoding, Excel compatible\n\n## 📊 Monitoring Metrics\n\nCheck out the awesome admin dashboard 👇\n\n![ccLoad Dashboard](images/ccload-dashboard.jpeg)\n![ccLoad Logs](images/ccload-logs.jpg)\n*Real-time Monitoring Dashboard: Claude Code, Codex, OpenAI, and Gemini platform metrics at a glance*\n\n**Core Features**:\n- 📈 **24-hour Trend Charts** - Request volumes clearly visualized with peaks and valleys\n- 🔴 **Real-time Error Logs** - Instantly detect which channel has issues\n- 📊 **Channel Call Statistics** - See which channels are performing well with data-backed insights\n- 💬 **Model Testing Workbench** - Test by channel, by model, or in a chat-style model testing workflow:\n  - Upload or paste images in chat mode to verify multimodal requests directly\n  - Toggle reasoning level, built-in search, and streaming to inspect transformed upstream behavior\n  - Export conversations as Markdown or HTML for review, incident notes, or regression records\n- ⚡ **Performance Metrics** - Latency, success rates, and bottleneck detection\n- 💰 **Token Usage Stats** - Know exactly where your budget goes:\n  - Custom time range selector for flexible analysis\n  - Per API token ID classification for multi-tenant billing\n  - Supports Gemini/OpenAI cache token visualization\n\n- 🎛️ **Log Column Customization** - Click the gear icon to show/hide columns, settings auto-saved to browser\n\n**UI Highlights**:\n- 🎨 Modern gradient purple theme for comfortable viewing\n- 📱 Responsive design works great on mobile and desktop\n- ⚡ Real-time data refresh without manual page reload\n- 📊 Multi-dimensional stat cards show key metrics on one screen\n  - Cached query optimization\n  - Gemini/OpenAI Cache Token (Cache Read) display\n\n## 🔧 Tech Stack\n\n### Core Dependencies\n\n| Component | Version | Purpose | Performance Advantage |\n|-----------|---------|---------|----------------------|\n| **Go** | 1.25.0+ | Runtime | Native concurrency, built-in min function |\n| **Gin** | v1.12.0 | Web Framework | High-performance HTTP routing |\n| **modernc/sqlite** | v1.51.0 | Embedded Database | Pure Go, zero CGO dependency, single file (default) |\n| **MySQL** | v1.10.0 | RDBMS | Optional, for high-concurrency production |\n| **PostgreSQL (pgx)** | v5.10.0 | RDBMS | Optional, supports URL and libpq DSNs |\n| **Sonic** | v1.15.1 | JSON Library | 2-3x faster than stdlib |\n| **godotenv** | v1.5.1 | Env Config | Simplified config management |\n\n### Architecture Features\n\n**Modular Architecture**:\n- **Proxy Module Split** (SRP):\n  - `proxy_handler.go`: HTTP entry, concurrency control, route selection\n  - `proxy_forward.go`: Core forwarding logic, request building, response handling\n  - `proxy_error.go`: Error handling, cooldown decisions, retry logic\n  - `proxy_util.go`: Constants, type definitions, utility functions\n  - `proxy_stream.go`: Streaming responses, first byte detection\n  - `proxy_gemini.go`: Gemini API special handling\n  - `proxy_sse_parser.go`: SSE parser (defensive handling, Gemini/OpenAI cache token parsing)\n  - `proxy_debug.go`: Upstream request/response debug capture (with sensitive header masking)\n- **Admin Module Split** (SRP):\n  - `admin_channels.go`: Channel CRUD\n  - `admin_stats.go`: Stats analysis API\n  - `admin_cooldown.go`: Cooldown management API\n  - `admin_csv.go`: CSV import/export\n  - `admin_types.go`: Admin API type definitions\n  - `admin_auth_tokens.go`: API access token CRUD (with token stats, cost limits, model restrictions)\n  - `admin_settings.go`: System settings management\n  - `admin_models.go`: Model list management\n  - `admin_testing.go`: Channel testing (with protocol transform testing)\n  - `admin_debug_log.go`: Debug log API (sensitive header masking + base64 binary encoding)\n  - `channel_check_scheduler.go`: Scheduled channel check scheduler\n  - `detection_log.go`: Detection result to LogEntry builder\n- **Protocol Transform System** (2026-04 new):\n  - `protocol/types.go`: Four protocol definitions (Anthropic/OpenAI/Gemini/Codex)\n  - `protocol/registry.go`: Request/response transformer registry\n  - `protocol/builtin/`: 18 built-in transform implementations (streaming and non-streaming)\n  - Preserves sampling/limit/stop/seed parameters; Gemini `thinkingConfig.thinkingLevel` maps to the target protocol's reasoning/thinking config\n  - Two modes: `upstream` (default, handled natively by upstream) / `local` (local translation)\n  - Channel config: `ProtocolTransformMode` + `ProtocolTransforms`\n  - Codex `/v1/alpha/search` is native passthrough only and never enters local protocol translation\n- **Cooldown Manager** (DRY):\n  - `cooldown/manager.go`: Unified cooldown decision engine\n  - Eliminates duplicate code, unified cooldown logic\n  - Distinguishes network vs HTTP error classification\n  - Recognizes structured quota/model cooldown responses and cools down until the upstream reset time\n  - Built-in single-key channel auto-upgrade logic\n- **Multi-URL Selector** (URLSelector):\n  - `url_selector.go`: Smart URL selection within a single channel\n  - Explore-first: Unvisited URLs get priority to collect latency data\n  - Weighted random: Weight = 1/EWMA latency, lower latency = higher selection probability\n  - Independent cooldown: Failed URLs cool down independently without affecting other URLs\n  - BaseURL tracking: Active requests, logs, and UI carry upstream URL throughout\n- **Storage Layer Refactor** (2025-12 optimization, eliminated 467 lines of duplicate code):\n  - `storage/schema/`: Unified schema definition (supports SQLite/MySQL/PostgreSQL differences)\n  - `storage/sql/`: Common SQL implementation layer shared by SQLite, MySQL, and PostgreSQL\n  - `storage/factory.go`: Factory pattern auto-selects database\n  - Composite index optimization, stats query performance improved\n- **OpenAI service_tier Pricing** (2026-03 new):\n  - `util.OpenAIServiceTierMultiplier()`: Returns multiplier for priority/flex/default tiers\n  - `LogEntry.ServiceTier`: Persisted to database, log cost column shows tier annotation\n  - Supports GPT-5.4, GPT-5.4-pro, and other latest model pricing\n- **Responses image_generation Tool Billing** (2026-05 new):\n  - Parses Responses API `tool_usage.image_gen` and the `image_generation` tool model\n  - Bills `gpt-image-2` by text input, image input, and image output tokens\n  - Streaming/non-streaming proxy paths and channel tests share the same usage parser to keep cost accounting consistent\n- **Tiered Pricing**:\n  - GPT-5.4: Input price auto-steps down after token threshold\n  - Qwen-Plus: Lower price tier kicks in after threshold\n  - Gemini long-context: Price doubles above threshold\n  - Cache discounts: Claude/Opus independent multipliers, OpenAI cache hit 50% discount\n\n**Multi-level Cache System**:\n- Channel config cache (60s TTL)\n- Round-robin pointer cache (in-memory)\n- Cooldown state inline (channels/api_keys tables store directly)\n- Error classification cache (1000 capacity)\n\n**Async Processing Architecture**:\n- Log system (1000 buffer + single worker, guarantees FIFO order)\n- Token/log cleanup (background goroutine, periodic maintenance)\n\n**Unified Response System**:\n- `StandardResponse[T]` generic struct (DRY)\n- `ResponseHelper` utility class with 9 shortcut methods\n- Auto-extracts app-level error codes, unified JSON format\n\n**Connection Pool Optimization**:\n- SQLite: 10 connections for memory mode / 5 for file mode, 5-minute lifetime\n- HTTP client: 100 max connections, 30s timeout, keepalive optimization\n- TLS: Session cache (1024 capacity), reduces handshake latency\n\n## 🔧 Configuration\n\n### Environment Variables\n\n| Variable | Default | Description |\n|----------|---------|-------------|\n| `CCLOAD_PASS` | None | Admin password (**Required**, exits if not set) |\n| `CCLOAD_API_TOKENS` | None | Pre-seed API access tokens on startup. Format: `token1,token2` or `token1\\|production,token2\\|development`; existing tokens are not overwritten |\n| `API_TOKENS` | None | Compatibility alias for `CCLOAD_API_TOKENS`; startup fails if both variables are set with different values |\n| `CCLOAD_MYSQL` | None | MySQL DSN (optional, format: `user:pass@tcp(host:port)/db?charset=utf8mb4`)\u003cbr/\u003e**Mutually exclusive with `CCLOAD_POSTGRES`** |\n| `CCLOAD_POSTGRES` | None | PostgreSQL DSN (optional, URL or libpq keywords, e.g. `postgres://user:pass@host:5432/db?sslmode=disable`)\u003cbr/\u003e**Mutually exclusive with `CCLOAD_MYSQL`** |\n| `CCLOAD_ENABLE_SQLITE_REPLICA` | `0` | Hybrid storage mode switch (`1`=enable, needs MySQL or Postgres primary DSN) |\n| `CCLOAD_SQLITE_LOG_DAYS` | `7` | Days of logs to restore from primary DB on startup in hybrid mode (-1=all, 0=no logs) |\n| `CCLOAD_ALLOW_INSECURE_TLS` | `0` | Disable upstream TLS cert validation (`1`=enable; ⚠️for troubleshooting/controlled intranet only) |\n| `PORT` | `8080` | Service port |\n| `GIN_MODE` | `release` | Run mode (`debug`/`release`) |\n| `GIN_LOG` | `true` | Gin access log switch (`false`/`0`/`no`/`off` to disable) |\n| `TRUSTED_PROXIES` | Private ranges + Loopback + `100.64.0.0/10` | Trusted proxy CIDRs (comma-separated); `none` = trust no proxies |\n| `SQLITE_PATH` | `data/ccload.db` | SQLite database file path (SQLite mode only) |\n| `SQLITE_JOURNAL_MODE` | `WAL` | SQLite Journal mode (WAL/TRUNCATE/DELETE, recommend TRUNCATE for containers) |\n| `CCLOAD_MAX_CONCURRENCY` | `1000` | Max concurrent requests (limits simultaneous proxy requests) |\n| `CCLOAD_MAX_BODY_BYTES` | `10485760` | Max request body bytes (10MB, Images API auto-expands to 20MB) |\n| `CCLOAD_COOLDOWN_AUTH_SEC` | `300` | Auth error (401/402/403) initial cooldown (seconds) |\n| `CCLOAD_COOLDOWN_SERVER_SEC` | `120` | Server error (5xx) initial cooldown (seconds) |\n| `CCLOAD_COOLDOWN_TIMEOUT_SEC` | `60` | Timeout error (597/598) initial cooldown (seconds) |\n| `CCLOAD_COOLDOWN_RATE_LIMIT_SEC` | `60` | Rate limit error (429) initial cooldown (seconds) |\n| `CCLOAD_COOLDOWN_MAX_SEC` | `1800` | Exponential backoff cooldown max (seconds, 30 minutes) |\n| `CCLOAD_COOLDOWN_MIN_SEC` | `10` | Exponential backoff cooldown min (seconds) |\n| `CCLOAD_HOST_OVERRIDES` | None | DNS override: pin upstream domains to fixed IPs, bypassing DNS resolution. Format: `host1=ip1,host2=ip2`, e.g. `anyrouter.top=47.246.23.200`. TLS SNI/cert/Host header unaffected |\n\n\u003e If the service sits behind a reverse proxy or load balancer, set `TRUSTED_PROXIES` explicitly so spoofed `X-Forwarded-For` values cannot affect client IP detection or login rate limiting.\n\n#### Hybrid Storage Mode (Primary DB + SQLite Cache)\n\nHuggingFace Spaces and similar environments lose local data on restart, but remote MySQL/Postgres can have high query latency. Hybrid mode offers the best of both worlds:\n\n- **Primary Storage (MySQL or PostgreSQL)**: Write operations go to the primary first, ensuring data persistence\n- **SQLite Local Cache**: Read operations go through local SQLite, latency \u003c1ms\n- **Startup Recovery**: Restore data from primary to SQLite, supports restoring logs by days\n- **Log Special Handling**: Write to SQLite first (fast), then async sync to primary (backup)\n\n```bash\n# Enable hybrid mode (MySQL primary)\nexport CCLOAD_MYSQL=\"user:pass@tcp(host:3306)/db?charset=utf8mb4\"\nexport CCLOAD_ENABLE_SQLITE_REPLICA=1\nexport CCLOAD_SQLITE_LOG_DAYS=7  # Restore last 7 days of logs (optional)\n\n# Or PostgreSQL primary\nexport CCLOAD_POSTGRES=\"postgres://user:pass@host:5432/db?sslmode=disable\"\nexport CCLOAD_ENABLE_SQLITE_REPLICA=1\n```\n\n**Storage Modes**:\n| Mode | Configuration | Use Case |\n|------|---------------|----------|\n| Pure SQLite | Don't set primary DSN | Local dev, single instance |\n| Pure MySQL | Set `CCLOAD_MYSQL` | Standard production |\n| Pure PostgreSQL | Set `CCLOAD_POSTGRES` | Standard production (PG) |\n| Hybrid Mode | Primary DSN + `CCLOAD_ENABLE_SQLITE_REPLICA=1` | HuggingFace Spaces / high-latency primary |\n\n### Web Admin Configuration (Hot Reload Supported)\n\nThese settings have been migrated to database, managed via Web interface `/web/settings.html`, changes take effect immediately without restart:\n\n| Setting | Default | Description |\n|---------|---------|-------------|\n| `log_retention_days` | `7` | Log retention days (-1 for permanent, 1-365 days) |\n| `max_key_retries` | `3` | Max key retries within single channel |\n| `upstream_first_byte_timeout` | `0` | Upstream first valid stream content timeout (seconds, 0=disabled, stream only) |\n| `non_stream_timeout` | `120` | Non-stream request timeout (seconds, 0=disabled) |\n| `anthropic_first_byte_timeout` | `0` | Anthropic first valid stream content timeout (seconds, 0=use global `upstream_first_byte_timeout`) |\n| `anthropic_non_stream_timeout` | `0` | Anthropic non-stream request timeout (seconds, 0=use global `non_stream_timeout`) |\n| `codex_first_byte_timeout` | `0` | Codex first valid stream content timeout (seconds, 0=use global `upstream_first_byte_timeout`) |\n| `codex_non_stream_timeout` | `0` | Codex non-stream request timeout (seconds, 0=use global `non_stream_timeout`) |\n| `openai_first_byte_timeout` | `0` | OpenAI first valid stream content timeout (seconds, 0=use global `upstream_first_byte_timeout`) |\n| `openai_non_stream_timeout` | `0` | OpenAI non-stream request timeout (seconds, 0=use global `non_stream_timeout`) |\n| `gemini_first_byte_timeout` | `0` | Gemini first valid stream content timeout (seconds, 0=use global `upstream_first_byte_timeout`) |\n| `gemini_non_stream_timeout` | `0` | Gemini non-stream request timeout (seconds, 0=use global `non_stream_timeout`) |\n| `enable_health_score` | `false` | Enable health-based dynamic channel sorting |\n| `success_rate_penalty_weight` | `100` | Success rate penalty weight (see below) |\n| `health_score_window_minutes` | `30` | Success rate stats time window (minutes) |\n| `health_score_update_interval` | `30` | Success rate cache update interval (seconds) |\n| `health_min_confident_sample` | `20` | Confidence sample threshold (full penalty at this sample size) |\n| `enable_ttfb_score` | `false` | Enable relative first-byte latency penalty; requires `enable_health_score` |\n| `ttfb_penalty_weight` | `20` | TTFB penalty when average first-byte latency is 2× the candidate median at full confidence |\n| `ttfb_max_slow_ratio` | `2` | Upper bound for relative TTFB slowness (`avg_ttfb / median_ttfb - 1`) |\n| `ttfb_min_confident_sample` | `10` | TTFB confidence sample threshold |\n| `channel_check_interval_hours` | `5` | Scheduled channel check interval (hours, supports decimals, 0=disabled) |\n| `model_catalog_sync_interval_hours` | `6` | Syncs the models.dev catalog every 6 hours; `0` disables network sync. At startup, the last-good cache is used, with the embedded catalog as fallback; channel `cost_multiplier` still applies. |\n| `auto_update_interval_hours` | `12` | Auto-update check interval (hours, 0=disabled, minimum enabled value is 1) |\n\nPer-protocol timeouts apply to the runtime upstream protocol: if a transformed request is forwarded to OpenAI, ccLoad reads `openai_*_timeout`; when that value is `0`, it falls back to the global timeout.\n\n#### Auto Updates\n\nccLoad supports in-process auto updates. It checks releases every 12 hours by default, trying `ghproxy.net` first and GitHub directly if that source fails. A source is accepted only when release detection, binary download, checksum download, and SHA256 verification all succeed. The interval can be changed from the Web admin settings page via `auto_update_interval_hours`; set it to `0` to disable automatic update checks.\n\nTo use only a private mirror, set `CCLOAD_RELEASE_BASE_URL` to a complete latest-download base such as `https://mirror.example/caidaoli/ccLoad/releases/latest/download`. An explicit value disables the built-in fallback sources. This setting affects release downloads only; it does not configure `HTTP_PROXY` or `HTTPS_PROXY` for upstream API traffic.\n\n#### Dynamic Channel Sorting\n\nWhen `enable_health_score` is enabled, ccLoad calculates an effective priority from recent channel health. The success-rate penalty is always active; the relative first-byte latency penalty is added only when `enable_ttfb_score=true`:\n\n```\nfailure_confidence = min(1.0, sample_count / health_min_confident_sample)\nfailure_penalty = failure_rate × success_rate_penalty_weight × failure_confidence\n\nrelative_slowness = clamp(avg_ttfb / candidate_median_ttfb - 1, 0, ttfb_max_slow_ratio)\nttfb_confidence = min(1.0, ttfb_sample_count / ttfb_min_confident_sample)\nttfb_penalty = relative_slowness × ttfb_penalty_weight × ttfb_confidence\n\neffective_priority = base_priority - failure_penalty - ttfb_penalty\n```\n\n**Confidence factors** prevent new or low-traffic channels from receiving a full penalty from a few samples. TTFB scoring compares successful first-byte samples against the median of the current candidate channels. Channels at or faster than the median receive no TTFB penalty, and scoring is skipped when fewer than two candidates have valid TTFB data.\n\n**Success-rate-only example** (`enable_ttfb_score=false`, `success_rate_penalty_weight = 100`, `health_min_confident_sample = 20`):\n\n| Channel | Base Priority | Success Rate | Samples | Confidence | Penalty | Effective Priority |\n|---------|---------------|--------------|---------|------------|---------|-------------------|\n| A | 100 | 95% | 100 | 1.0 | 5 | **95** |\n| B | 90 | 70% | 80 | 1.0 | 30 | **60** |\n| C | 80 | 60% | 4 | 0.2 | 8 | **72** |\n| D | 70 | 100% | 50 | 1.0 | 0 | **70** |\n\nBase priority order: A \u003e B \u003e C \u003e D\n**Effective priority order: A (95) \u003e C (72) \u003e D (70) \u003e B (60)**\n\n#### API Access Token Configuration\n\n**Important**: API access tokens are normally managed in the Web admin interface; Docker and CI deployments can pre-seed them with an environment variable.\n\n- Visit `http://localhost:8080/web/tokens.html` for token management\n- Set `CCLOAD_API_TOKENS=token1|production,token2|development` to create missing tokens on startup\n- Provisioning is idempotent: existing tokens keep their description, limits, model/channel restrictions, and statistics\n- Only missing tokens are created; existing tokens are never modified\n- Supports add, delete, view tokens\n- All tokens stored in database with persistence\n- Without any tokens configured, all `/v1/*` and `/v1beta/*` APIs return `401 Unauthorized`\n\n⚠️ **Security notes**:\n- In production, prefer Docker Secrets, Kubernetes Secrets, or platform encrypted Secrets over plain environment variables\n- In CI/CD, do not print full environment variables to logs\n- After provisioning, remove `CCLOAD_API_TOKENS` from deployment config if automatic recovery is no longer needed\n- Restrict access to container inspect output, orchestration dashboards, and deployment configuration\n\n**Advanced Token Features** (2026-01 New):\n- **Cost Limits**: Set cost limits per token (USD), requests rejected with 429 when exceeded\n- **Model Restrictions**: Restrict which models a token can access for fine-grained access control\n- **First Byte Time**: Records streaming request TTFB (milliseconds) for upstream latency diagnosis\n\n#### Behavior Summary\n\n- `CCLOAD_PASS` not set: Program fails to start and exits (secure default)\n- No API access tokens configured: All `/v1/*` and `/v1beta/*` APIs return `401 Unauthorized`. Configure tokens via Web interface `/web/tokens.html`\n- Public endpoints: `GET /health` (health check) and `GET /public/summary` (stats summary) require no auth, all others require auth token\n\n### Docker Images\n\nProject supports multi-arch Docker images:\n\n- **Supported Architectures**: `linux/amd64`, `linux/arm64`\n- **Image Registry**: `ghcr.io/caidaoli/ccload`\n- **Available Tags**:\n  - `latest` - Latest stable version\n  - `v2.44.1` - Specific release tag, matching the GitHub Release tag\n\nThe official GHCR runtime image is Alpine-based. On container startup, it downloads and verifies the latest Linux release binary, trying `ghproxy.net` first and GitHub directly on failure. After ccLoad starts, its in-process updater uses the same source order. Set `CCLOAD_RELEASE_BASE_URL` to a complete `.../releases/latest/download` URL to use only a custom mirror. The default in-process check interval is 12 hours and can be changed with `auto_update_interval_hours` in the Web admin settings.\n\n### Image Tag Guide\n\n```bash\n# Pull latest version\ndocker pull ghcr.io/caidaoli/ccload:latest\n\n# Pull specific version\ndocker pull ghcr.io/caidaoli/ccload:v2.44.1\n\n# Specify architecture (Docker usually auto-selects)\ndocker pull --platform linux/amd64 ghcr.io/caidaoli/ccload:latest\ndocker pull --platform linux/arm64 ghcr.io/caidaoli/ccload:latest\n```\n\n### Database Structure\n\n**Storage Architecture (Factory Pattern)**:\n```\nstorage/\n├── store.go         # Store interface (unified contract)\n├── factory.go       # NewStore() auto-selects database\n├── schema/          # Unified schema definition layer (2025-12 new)\n│   ├── tables.go    # Table definitions (DefineXxxTable functions)\n│   └── builder.go   # Schema builder (supports SQLite/MySQL/PostgreSQL differences)\n├── sql/             # Common SQL implementation layer (2025-12 refactor, eliminated 467 lines)\n│   ├── store_impl.go      # SQLStore core implementation\n│   ├── config.go          # Channel config CRUD\n│   ├── apikey.go          # API key CRUD\n│   ├── cooldown.go        # Cooldown management\n│   ├── log.go             # Log storage\n│   ├── metrics.go             # Metrics stats\n│   ├── metrics_filter.go      # Filter intersection support\n│   ├── metrics_aggregate_rows.go  # Aggregate row processing\n│   ├── metrics_finalize.go    # Finalization processing\n│   ├── auth_tokens.go         # API access tokens\n│   ├── auth_token_stats.go    # Token statistics\n│   ├── web_sessions.go    # Role-aware Web sessions\n│   ├── system_settings.go # System settings\n│   └── helpers.go         # Helper functions\n└── sqlite/          # SQLite specific (test files only)\n```\n\n**Database Selection Logic**:\n- `CCLOAD_MYSQL` set → MySQL primary (fatal if also set with `CCLOAD_POSTGRES`)\n- `CCLOAD_POSTGRES` set → PostgreSQL primary\n- Neither set → SQLite (default)\n- Primary DSN + `CCLOAD_ENABLE_SQLITE_REPLICA=1` → Hybrid (primary write + SQLite read cache)\n\n**Core Table Structure** (SQLite / MySQL / PostgreSQL shared):\n- `channels` - Channel config (cooldown data inline, UNIQUE constraint on name, with protocol transform config, scheduled check config, RPM/concurrency limit config)\n- `api_keys` - API keys (key-level cooldown inline, multi-key strategies)\n- `logs` - Request logs (with base_url upstream URL tracking)\n- `debug_logs` - Debug logs (upstream request/response raw data, independent cleanup policy)\n- `key_rr` - Round-robin pointers (channel_id → idx)\n- `auth_tokens` - Auth tokens (with cost limits, model restrictions, first byte time tracking)\n- `web_sessions` - Role-aware Web sessions bound to an optional API token\n- `system_settings` - System config (hot reload support)\n\n**Architecture Features** (✅ 2025-12 through 2026-04 continuous improvements):\n- ✅ **Unified SQL Layer** (refactor): SQLite, MySQL, and PostgreSQL share `storage/sql/` implementation\n- ✅ **Unified Schema Definition** (new): `storage/schema/` defines table structures, supports database differences\n- ✅ Factory pattern unified interface (OCP, easy to extend new storage)\n- ✅ Cooldown data inline (deprecated separate cooldowns table, reduces JOIN overhead)\n- ✅ Performance index optimization (channel selection latency ↓30-50%, key lookup latency ↓40-60%)\n- ✅ Composite index optimization (stats query performance improved)\n- ✅ Foreign key constraints (cascade delete, ensures data consistency)\n- ✅ Multi-key support (sequential/round_robin strategies)\n- ✅ Auto migration (auto creates/updates table structure on startup)\n- ✅ Token stats enhancement (time range selection, per-token ID classification, cache optimization)\n- ✅ **service_tier cost tracking**: Logs persist service_tier field, cost column shows tier label\n- ✅ **Responses image tool cost tracking**: `image_generation` tool costs are included in logs, stats, and cost limit accounting\n- ✅ **Tiered pricing engine**: GPT-5.4/Qwen-Plus/Gemini long-context step billing\n- ✅ **Log UX improvements**: Cost column formats to 3 decimal places (empty for zero), IP column shows full address on hover\n- ✅ **Protocol transform system**: Anthropic/OpenAI/Gemini/Codex four-protocol cross-conversion, upstream/local modes\n- ✅ **Debug logs**: Upstream request/response raw data capture, sensitive header masking, independent cleanup policy\n- ✅ **Scheduled channel checks**: Background periodic channel availability probing, configurable check model per channel\n- ✅ **Channel RPM limits**: Per-channel rolling 60-second request caps, `0` means unlimited, over-limit channels are skipped\n- ✅ **Channel concurrency limits**: Per-channel in-flight request caps, `0` means unlimited, over-limit channels are skipped\n\n**Backward Compatible Migration**:\n- Auto-detects and fixes duplicate channel names\n- Intelligently adds UNIQUE constraints, ensures data integrity\n- Runs automatically on startup, no manual intervention needed\n- Log database merged into main database (single data source)\n\n## 🛡️ Security Considerations\n\n- Production must set strong password `CCLOAD_PASS`\n- Configure API access tokens via Web admin `/web/tokens.html` to protect API endpoint access\n- API keys used only in memory, not logged\n- Only random Web-session tokens are stored in client localStorage, with a 24-hour expiry\n- Recommend using HTTPS reverse proxy\n- Docker images run as non-root user for enhanced security\n\n### Token Authentication System\n\nccLoad uses token-based authentication for simple and efficient secure access control.\n\n**Auth Methods**:\n- **Web Interface**: Login with an admin password or API token and receive a 24-hour Web-session token\n- **API Endpoints**: Support `Authorization: Bearer \u003ctoken\u003e` header auth\n\n**Core Features**:\n- ✅ **Scoped Web Sessions**: API-token sessions are read-only and server-bound to their own usage data\n- ✅ **Immediate Revocation**: Disabling, deleting, or expiring an API token invalidates its Web session\n- ✅ **Credential Isolation**: Plaintext API tokens are never stored in browser storage\n- ✅ **Server-side Authorization**: Channel management, token management, settings, and debug data remain admin-only\n\n**Usage Example**:\n```bash\n# 1. Login to get token\ncurl -X POST http://localhost:8080/login \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"mode\":\"admin\",\"password\":\"your_admin_password\"}' | jq\n\n# Response example:\n# {\n#   \"status\": \"success\",\n#   \"token\": \"abc123...\",  # 64-char hex token\n#   \"expiresIn\": 86400     # 24 hours (seconds)\n# }\n\n# 2. Use token to access admin API\ncurl http://localhost:8080/admin/channels \\\n  -H \"Authorization: Bearer \u003cyour_token\u003e\"\n\n# 3. Logout (optional, token auto-expires after 24 hours)\ncurl -X POST http://localhost:8080/logout \\\n  -H \"Authorization: Bearer \u003cyour_token\u003e\"\n```\n\n## 🔄 CI/CD\n\nProject uses GitHub Actions for automated CI/CD:\n\n- **Trigger Conditions**: Push version tags (`v*`) or manual trigger\n- **Build Output**: Multi-arch Docker images pushed to GitHub Container Registry\n- **Version Management**: Auto-generates semantic version tags\n- **Cache Optimization**: Uses GitHub Actions cache to accelerate builds\n\n## 🤝 Contributing\n\nIssues and Pull Requests welcome!\n\n### Development Checks\n\nUse `sonic` for Go commands. Before sending a change, run the checks that match the touched area:\n\n```bash\ngo test -tags sonic ./internal/...\nmake race-fast      # high-value race subset\nmake race           # full race suite\nmake verify-web     # frontend node:test checks\ngolangci-lint run ./...\n```\n\n`make race-fast` keeps the common race-sensitive packages fast enough for local iteration; use `make race` before larger or concurrency-sensitive changes. Override `RACE_P` or `RACE_PARALLEL` only when the machine needs a different parallelism cap.\n\n### Troubleshooting\n\n**Port In Use**:\n```bash\n# Find and kill process using port 8080\nlsof -i :8080 \u0026\u0026 kill -9 \u003cPID\u003e\n```\n\n**Container Issues**:\n```bash\n# View container logs\ndocker logs ccload -f\n# Check container health status\ndocker inspect ccload --format='{{.State.Health.Status}}'\n```\n\n**Config Validation**:\n```bash\n# Test service health (lightweight health check, \u003c5ms)\ncurl -s http://localhost:8080/health\n# Or view stats summary (returns business data, 50-200ms)\ncurl -s http://localhost:8080/public/summary\n# Check environment variable config\nenv | grep CCLOAD\n```\n\n## 📄 License\n\nMIT License\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcaidaoli%2FccLoad","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcaidaoli%2FccLoad","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcaidaoli%2FccLoad/lists"}