|
| 1 | +# claude-api |
| 2 | + |
| 3 | +> Proxy server buat nge-pool multiple Anthropic API key dengan auto rotation, retry cerdas, dan monitoring dashboard. Terinspirasi dari arsitektur copilot-api. |
| 4 | +
|
| 5 | +[](https://github.com/el-pablos/claude-api/actions/workflows/ci.yml) |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | +--- |
| 10 | + |
| 11 | +## kenapa perlu ini? |
| 12 | + |
| 13 | +kalo kamu punya beberapa API key Anthropic dan pengen: |
| 14 | + |
| 15 | +- **auto rotate** key saat kena rate limit |
| 16 | +- **load balance** request ke multiple key |
| 17 | +- **monitor** penggunaan tiap key dari dashboard |
| 18 | +- **drop-in replacement** — cukup ganti `ANTHROPIC_BASE_URL` di Claude Code |
| 19 | + |
| 20 | +maka ini solusinya. |
| 21 | + |
| 22 | +--- |
| 23 | + |
| 24 | +## arsitektur |
| 25 | + |
| 26 | +``` |
| 27 | +┌─────────────────────────────────────────────────┐ |
| 28 | +│ Client │ |
| 29 | +│ (Claude Code / Anthropic SDK) │ |
| 30 | +└────────────────────┬────────────────────────────┘ |
| 31 | + │ |
| 32 | + │ ANTHROPIC_BASE_URL=http://localhost:4141 |
| 33 | + ▼ |
| 34 | +┌─────────────────────────────────────────────────┐ |
| 35 | +│ claude-api proxy │ |
| 36 | +│ │ |
| 37 | +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ |
| 38 | +│ │ Logger │→│ Auth │→│ Error │ │ |
| 39 | +│ │Middleware │ │Middleware│ │ Handler │ │ |
| 40 | +│ └──────────┘ └──────────┘ └──────────┘ │ |
| 41 | +│ │ │ |
| 42 | +│ ┌─────────▼──────────┐ │ |
| 43 | +│ │ Account Pool │ │ |
| 44 | +│ │ Manager │ │ |
| 45 | +│ │ │ │ |
| 46 | +│ │ ┌──┐ ┌──┐ ┌──┐ │ │ |
| 47 | +│ │ │K1│ │K2│ │K3│ │ ← API Keys │ |
| 48 | +│ │ └──┘ └──┘ └──┘ │ │ |
| 49 | +│ │ │ │ |
| 50 | +│ │ Strategies: │ │ |
| 51 | +│ │ • round-robin │ │ |
| 52 | +│ │ • weighted │ │ |
| 53 | +│ │ • least-used │ │ |
| 54 | +│ │ • priority │ │ |
| 55 | +│ │ • random │ │ |
| 56 | +│ └─────────┬──────────┘ │ |
| 57 | +│ │ │ |
| 58 | +│ ┌─────────▼──────────┐ │ |
| 59 | +│ │ Proxy Handler │ │ |
| 60 | +│ │ + Retry Logic │ │ |
| 61 | +│ └─────────┬──────────┘ │ |
| 62 | +└─────────────────────┼───────────────────────────┘ |
| 63 | + │ |
| 64 | + ▼ |
| 65 | + ┌────────────────────┐ |
| 66 | + │ api.anthropic.com │ |
| 67 | + └────────────────────┘ |
| 68 | +``` |
| 69 | + |
| 70 | +--- |
| 71 | + |
| 72 | +## flowchart request |
| 73 | + |
| 74 | +```mermaid |
| 75 | +flowchart TD |
| 76 | + A[Client Request] --> B[Logger Middleware] |
| 77 | + B --> C[Auth Check] |
| 78 | + C --> D[Account Pool Manager] |
| 79 | + D --> E{Ada account active?} |
| 80 | + E -->|Ya| F[Select API Key by Strategy] |
| 81 | + E -->|Tidak| G[Return 503] |
| 82 | + F --> H[Forward ke Anthropic API] |
| 83 | + H --> I{Response OK?} |
| 84 | + I -->|200| J[Return ke Client] |
| 85 | + I -->|429 Rate Limit| K[Mark Rate Limited] |
| 86 | + K --> L{Retry tersisa?} |
| 87 | + L -->|Ya| D |
| 88 | + L -->|Tidak| G |
| 89 | + I -->|401/403 Auth Error| M[Mark Invalid] |
| 90 | + M --> L |
| 91 | + I -->|5xx Server Error| N[Mark Failed] |
| 92 | + N --> L |
| 93 | +``` |
| 94 | + |
| 95 | +--- |
| 96 | + |
| 97 | +## data model |
| 98 | + |
| 99 | +```mermaid |
| 100 | +erDiagram |
| 101 | + POOL_STATE { |
| 102 | + array accounts |
| 103 | + int currentIndex |
| 104 | + string lastSelectedId |
| 105 | + object config |
| 106 | + } |
| 107 | +
|
| 108 | + API_KEY_ACCOUNT { |
| 109 | + string id PK |
| 110 | + string name |
| 111 | + string apiKey |
| 112 | + string status |
| 113 | + object usage |
| 114 | + object rateLimit |
| 115 | + object metadata |
| 116 | + object health |
| 117 | + int inFlight |
| 118 | + } |
| 119 | +
|
| 120 | + USAGE { |
| 121 | + int total |
| 122 | + int success |
| 123 | + int failed |
| 124 | + } |
| 125 | +
|
| 126 | + RATE_LIMIT { |
| 127 | + int hit |
| 128 | + timestamp resetAt |
| 129 | + } |
| 130 | +
|
| 131 | + METADATA { |
| 132 | + timestamp createdAt |
| 133 | + timestamp lastUsedAt |
| 134 | + int priority |
| 135 | + int weight |
| 136 | + } |
| 137 | +
|
| 138 | + POOL_STATE ||--o{ API_KEY_ACCOUNT : contains |
| 139 | + API_KEY_ACCOUNT ||--|| USAGE : has |
| 140 | + API_KEY_ACCOUNT ||--|| RATE_LIMIT : has |
| 141 | + API_KEY_ACCOUNT ||--|| METADATA : has |
| 142 | +``` |
| 143 | + |
| 144 | +--- |
| 145 | + |
| 146 | +## cara install |
| 147 | + |
| 148 | +```bash |
| 149 | +# clone repo |
| 150 | +git clone https://github.com/el-pablos/claude-api.git |
| 151 | +cd claude-api |
| 152 | + |
| 153 | +# install dependencies |
| 154 | +npm install |
| 155 | + |
| 156 | +# copy env example |
| 157 | +cp env.example .env |
| 158 | + |
| 159 | +# edit .env — minimal set ENCRYPTION_KEY |
| 160 | +# ENCRYPTION_KEY harus 32 karakter atau lebih |
| 161 | + |
| 162 | +# jalankan development |
| 163 | +npm run dev |
| 164 | + |
| 165 | +# atau production |
| 166 | +npm start |
| 167 | +``` |
| 168 | + |
| 169 | +--- |
| 170 | + |
| 171 | +## cara pakai |
| 172 | + |
| 173 | +### 1. tambah API key via dashboard |
| 174 | + |
| 175 | +buka `http://localhost:4141/dashboard`, klik "Add Account", masukin: |
| 176 | + |
| 177 | +- **Name**: label buat key (misal `work-key-1`) |
| 178 | +- **API Key**: `sk-ant-api03-xxxxx` |
| 179 | +- **Priority**: 1-100 (default 50) |
| 180 | +- **Weight**: 1-10 (default 1) |
| 181 | + |
| 182 | +### 2. atau via API |
| 183 | + |
| 184 | +```bash |
| 185 | +curl -X POST http://localhost:4141/api/dashboard/accounts \ |
| 186 | + -H "Content-Type: application/json" \ |
| 187 | + -H "Authorization: Bearer your-secret-key" \ |
| 188 | + -d '{ |
| 189 | + "name": "key-1", |
| 190 | + "apiKey": "sk-ant-api03-your-key-here", |
| 191 | + "priority": 50, |
| 192 | + "weight": 1 |
| 193 | + }' |
| 194 | +``` |
| 195 | + |
| 196 | +### 3. arahkan Claude Code ke proxy |
| 197 | + |
| 198 | +```bash |
| 199 | +# set environment variable |
| 200 | +export ANTHROPIC_BASE_URL=http://localhost:4141 |
| 201 | +export ANTHROPIC_API_KEY=dummy-key |
| 202 | + |
| 203 | +# jalankan Claude Code seperti biasa |
| 204 | +claude |
| 205 | +``` |
| 206 | + |
| 207 | +atau di config Claude Code: |
| 208 | + |
| 209 | +```json |
| 210 | +{ |
| 211 | + "apiBaseUrl": "http://localhost:4141" |
| 212 | +} |
| 213 | +``` |
| 214 | + |
| 215 | +--- |
| 216 | + |
| 217 | +## konfigurasi |
| 218 | + |
| 219 | +semua konfigurasi via environment variable: |
| 220 | + |
| 221 | +| Variable | Default | Deskripsi | |
| 222 | +| --------------------- | --------------------------- | ---------------------------------------------------------------------------- | |
| 223 | +| `PORT` | `4141` | Port server | |
| 224 | +| `HOST` | `0.0.0.0` | Host binding | |
| 225 | +| `API_SECRET_KEY` | - | Secret key untuk dashboard API | |
| 226 | +| `ENCRYPTION_KEY` | - | Key enkripsi credential (min 32 chars) | |
| 227 | +| `POOL_STRATEGY` | `round-robin` | Strategi pool: `round-robin`, `weighted`, `least-used`, `priority`, `random` | |
| 228 | +| `MAX_RETRIES` | `3` | Max retry per request | |
| 229 | +| `RATE_LIMIT_COOLDOWN` | `60000` | Cooldown rate limit (ms) | |
| 230 | +| `CLAUDE_BASE_URL` | `https://api.anthropic.com` | Target Anthropic API | |
| 231 | +| `CLAUDE_API_TIMEOUT` | `300000` | Timeout request (ms) | |
| 232 | +| `DASHBOARD_ENABLED` | `true` | Enable dashboard | |
| 233 | +| `DASHBOARD_USERNAME` | `admin` | Username dashboard | |
| 234 | +| `DASHBOARD_PASSWORD` | - | Password dashboard (kosong = no auth) | |
| 235 | + |
| 236 | +--- |
| 237 | + |
| 238 | +## pool strategies |
| 239 | + |
| 240 | +### round-robin (default) |
| 241 | + |
| 242 | +request didistribusi merata ke semua key secara berurutan. key yang rate limited otomatis di-skip. |
| 243 | + |
| 244 | +### weighted |
| 245 | + |
| 246 | +mirip round-robin tapi key dengan weight lebih tinggi dapat lebih banyak request. key dengan weight 3 dapat 3x lebih banyak dari weight 1. |
| 247 | + |
| 248 | +### least-used |
| 249 | + |
| 250 | +selalu pilih key yang paling sedikit sedang memproses request (in-flight). cocok kalau response time bervariasi. |
| 251 | + |
| 252 | +### priority |
| 253 | + |
| 254 | +selalu coba key dengan priority tertinggi dulu. turun ke priority lebih rendah kalau yang tinggi lagi not available. |
| 255 | + |
| 256 | +### random |
| 257 | + |
| 258 | +pilih key secara acak dari yang available. simple dan unpredictable. |
| 259 | + |
| 260 | +--- |
| 261 | + |
| 262 | +## API reference |
| 263 | + |
| 264 | +### proxy endpoints |
| 265 | + |
| 266 | +| Method | Path | Deskripsi | |
| 267 | +| ------ | -------------- | ------------------------------- | |
| 268 | +| `POST` | `/v1/messages` | Proxy ke Anthropic Messages API | |
| 269 | +| `GET` | `/v1/models` | List available models | |
| 270 | + |
| 271 | +### health endpoints |
| 272 | + |
| 273 | +| Method | Path | Deskripsi | |
| 274 | +| ------ | ------------------ | ---------------------------- | |
| 275 | +| `GET` | `/health` | Simple health check | |
| 276 | +| `GET` | `/health/detailed` | Detailed pool + metrics info | |
| 277 | +| `GET` | `/health/live` | Kubernetes liveness probe | |
| 278 | +| `GET` | `/health/ready` | Kubernetes readiness probe | |
| 279 | + |
| 280 | +### dashboard API |
| 281 | + |
| 282 | +| Method | Path | Deskripsi | |
| 283 | +| -------- | ---------------------------------------------- | ------------------- | |
| 284 | +| `GET` | `/api/dashboard/stats` | Pool statistics | |
| 285 | +| `GET` | `/api/dashboard/accounts` | List semua account | |
| 286 | +| `GET` | `/api/dashboard/accounts/:id` | Detail account | |
| 287 | +| `POST` | `/api/dashboard/accounts` | Tambah account baru | |
| 288 | +| `PUT` | `/api/dashboard/accounts/:id` | Update account | |
| 289 | +| `DELETE` | `/api/dashboard/accounts/:id` | Hapus account | |
| 290 | +| `POST` | `/api/dashboard/accounts/:id/disable` | Disable account | |
| 291 | +| `POST` | `/api/dashboard/accounts/:id/enable` | Enable account | |
| 292 | +| `POST` | `/api/dashboard/accounts/:id/reset-rate-limit` | Reset rate limit | |
| 293 | +| `GET` | `/api/dashboard/metrics` | Real-time metrics | |
| 294 | +| `GET` | `/api/dashboard/logs` | Recent request logs | |
| 295 | +| `GET` | `/api/dashboard/config` | Pool config | |
| 296 | +| `PUT` | `/api/dashboard/config` | Update config | |
| 297 | + |
| 298 | +--- |
| 299 | + |
| 300 | +## testing |
| 301 | + |
| 302 | +```bash |
| 303 | +# semua test |
| 304 | +npm test |
| 305 | + |
| 306 | +# unit test aja |
| 307 | +npm run test:unit |
| 308 | + |
| 309 | +# dengan coverage |
| 310 | +npm run test:coverage |
| 311 | + |
| 312 | +# watch mode |
| 313 | +npm run test:watch |
| 314 | +``` |
| 315 | + |
| 316 | +--- |
| 317 | + |
| 318 | +## troubleshooting |
| 319 | + |
| 320 | +**semua key kena rate limit** |
| 321 | + |
| 322 | +- proxy return 503 dengan pesan "No available accounts in pool" |
| 323 | +- tunggu cooldown period (default 60 detik) atau tambah key baru |
| 324 | +- cek dashboard buat liat status tiap key |
| 325 | + |
| 326 | +**key di-mark invalid** |
| 327 | + |
| 328 | +- biasanya karena API key salah atau expired |
| 329 | +- cek key di Anthropic Console |
| 330 | +- enable kembali via dashboard setelah fix |
| 331 | + |
| 332 | +**streaming tidak jalan** |
| 333 | + |
| 334 | +- pastikan client support SSE |
| 335 | +- proxy forward streaming response as-is dari Anthropic |
| 336 | + |
| 337 | +**dashboard tidak bisa diakses** |
| 338 | + |
| 339 | +- cek `DASHBOARD_ENABLED=true` |
| 340 | +- kalau pake password, set `DASHBOARD_PASSWORD` di .env |
| 341 | + |
| 342 | +--- |
| 343 | + |
| 344 | +## development |
| 345 | + |
| 346 | +```bash |
| 347 | +# dev mode (auto-reload) |
| 348 | +npm run dev |
| 349 | + |
| 350 | +# typecheck |
| 351 | +npm run typecheck |
| 352 | + |
| 353 | +# build |
| 354 | +npm run build |
| 355 | +``` |
| 356 | + |
| 357 | +--- |
| 358 | + |
| 359 | +## tech stack |
| 360 | + |
| 361 | +- **Runtime**: Node.js 20+ |
| 362 | +- **Framework**: Hono |
| 363 | +- **Language**: TypeScript |
| 364 | +- **Testing**: Vitest |
| 365 | +- **Dashboard**: Alpine.js + Tailwind CSS + Chart.js |
| 366 | +- **CI/CD**: GitHub Actions |
| 367 | + |
| 368 | +--- |
| 369 | + |
| 370 | +## kontributor |
| 371 | + |
| 372 | +| | Nama | Role | |
| 373 | +| --- | ------------- | -------------------- | |
| 374 | +| | **el-pablos** | Creator & Maintainer | |
| 375 | + |
| 376 | +--- |
| 377 | + |
| 378 | +## license |
| 379 | + |
| 380 | +MIT License - bebas dipakai, dimodifikasi, dan didistribusikan. |
0 commit comments