A zero-MITM reverse proxy that pools & rotates Kiro accounts — and exposes them through Anthropic, OpenAI, and native kiro-cli APIs.
Point any Claude Code, Codex, opencode, or kiro-cli client at one endpoint. KiroPool rotates a pool of Kiro accounts behind it, swaps credentials transparently, meters credits per API key, and tracks each account's subscription quota — all without certificates, forks, or client-side login.
- Why KiroPool
- Features
- How it works
- Quick start
- Docker setup
- Connecting clients
- Pooling accounts
- Subscription tiers & quota
- Admin panel
- Configuration reference
- Running remotely
- Project layout
- Star history
- License
Kiro subscriptions are per-account, and every account has a finite credit quota that resets on a schedule. If you drive Kiro hard through a coding agent, a single account runs dry fast.
KiroPool puts many accounts behind one endpoint. Clients authenticate with a lightweight pool API key — they never see or manage Kiro credentials. The proxy picks a healthy account for every request, swaps in that account's real token, streams the response straight back, and meters the credits it cost. When an account is exhausted, KiroPool routes around it.
Because it uses kiro-cli's public endpoint-override settings rather than intercepting TLS, there is no cert to install, no MITM, and nothing that breaks when Kiro ships a new CLI version.
| Feature | |
|---|---|
| 🔁 | Account pooling & rotation — round-robin or smart (quota-aware) strategy that skips exhausted accounts. |
| 🧩 | Three API surfaces — native kiro-cli, Anthropic Messages (/v1/messages), and OpenAI Chat Completions (/v1/chat/completions). |
| 🔑 | Per-key credit metering — issue kpp_… API keys with individual credit limits; hit the limit → HTTP 402. |
| 📊 | Subscription quota tracking — polls GetUsageLimits and surfaces remaining vs. total credit per account (Pro, Pro+, Pro Max, Power). |
| 🧠 | Thinking / reasoning passthrough — streams reasoningContentEvent so Claude Code, opencode, and Codex show model thinking. |
| 🛠️ | Tool calling — bidirectional mapping between Anthropic/OpenAI tool calls and Kiro toolUses/toolResults. |
| 🎯 | Model mapping — valid Kiro modelIds pass through verbatim; Anthropic/OpenAI names are mapped; unknowns fall back to auto. |
| 🔐 | Multiple auth methods — IAM Identity Center (SSO), AWS Builder ID, external IdP (Microsoft Entra, etc.), and Kiro API keys. |
| 📥 | One-click import — read credentials directly from a local kiro-cli SQLite store (pure-Go, no CGO). |
| 🖥️ | Embedded admin panel — React + Tailwind dashboard baked into the binary at /admin, with a live SSE event stream. |
| 📦 | Single static binary — the frontend is //go:embed-ed; ship one file, or one small Docker image. |
| 🌍 | Multi-region — us-east-1, eu-central-1, us-gov-east-1, us-gov-west-1. |
kiro-cli exposes three settings that override its service endpoints. Point them at KiroPool and the CLI sends plain HTTP straight to the proxy — no certificate trust required.
| Setting | Upstream it targets | Used for |
|---|---|---|
api.krs.service |
runtime.{region}.kiro.dev |
Chat (GenerateAssistantResponse) |
api.cps.service |
management.{region}.kiro.dev |
Profiles, usage limits |
api.codewhisperer.service |
legacy q.amazonaws.com |
Telemetry (optional) |
kiro-cli / Claude Code / Codex ──HTTP──▶ KiroPool ──HTTPS──▶ runtime.{region}.kiro.dev
(native client) │
├─ Swap Authorization + profileArn + tokentype
├─ Rotate account (round-robin / smart quota-aware)
├─ Tee the response stream → meter credits (meteringEvent)
└─ Refresh tokens + poll GetUsageLimits for quota
The client stays 100% native — system prompts, tools, thinking, agentic loops, and context management are all handled by the client. KiroPool only rotates accounts and counts credits.
Verified against
kiro-cli2.12.2: chat through the proxy returns correctly and metered credits match the CLI's own count (0.16 ≈ 0.1619).
Prerequisites: Go 1.25+ (to build from source), or Docker (see below). At least one Kiro account.
git clone https://github.com/dongp06/kiro-cli-pool-proxy.git
cd kiro-cli-pool-proxy
go build -o kiro-pool-proxy .The admin UI is prebuilt and embedded, so this produces a single self-contained binary. To rebuild the UI after frontend changes:
cd frontend
npm install
npm run build # emits to ../proxy/webdist (embedded on next `go build`)
npx tsc --noEmit # Vite does not typecheck — run this separatelyRunning once with no config writes a template you can edit:
./kiro-pool-proxy --config config.json
# → "Created template config … edit it with your accounts, then re-run."Or import an already-logged-in kiro-cli account (reads its SQLite store):
go run ./cmd/import-local./kiro-pool-proxy --config config.json╔══════════════════════════════════════════════════════════════╗
║ KiroPool · Kiro account pool + Anthropic/OpenAI gateway ║
╠══════════════════════════════════════════════════════════════╣
║ Listen: 0.0.0.0:5000 ║
║ Accounts: 3 ║
║ Strategy: smart ║
╠══════════════════════════════════════════════════════════════╣
║ Admin panel: http://<SERVER_IP>:5000/admin ║
╚══════════════════════════════════════════════════════════════╝
./set-endpoints.sh http://127.0.0.1:5000 us-east-1
kiro-cli chat # every request now flows through the poolRestore defaults any time with ./set-endpoints.sh --reset.
The image is a pure Go build (the admin SPA is already embedded), so it's small and fast to build. Nothing but a Go toolchain is needed at build time.
git clone https://github.com/dongp06/kiro-cli-pool-proxy.git
cd kiro-cli-pool-proxy
mkdir -p data
./kiro-pool-proxy --config data/config.json # or copy an existing config.json into ./data
# edit data/config.json with your accounts
docker compose up -d --builddocker-compose.yml maps port 5000 and mounts ./data so your config.json and usage counters persist across restarts.
services:
kiro-pool-proxy:
build: .
image: kiro-pool-proxy:latest
container_name: kiro-pool-proxy
restart: unless-stopped
ports:
- "5000:5000"
volumes:
- ./data:/app/data
healthcheck:
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:5000/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 5sdocker build -t kiro-pool-proxy .
docker run -d --name kiro-pool-proxy \
-p 5000:5000 \
-v "$(pwd)/data:/app/data" \
kiro-pool-proxyThe container reads its config from /app/data/config.json. Put your config.json in the mounted ./data directory before starting.
Security: if you expose the container beyond localhost, set both
adminPassword(protects/admin) and apoolKey/ API keys (protect the proxy endpoints). See Running remotely.
KiroPool serves three API surfaces on the same port. All of them accept a pool API key (kpp_…) created in the admin panel — clients never handle real Kiro credentials.
./set-endpoints.sh http://SERVER_IP:5000 us-east-1
# or manually:
kiro-cli settings api.krs.service '{"endpoint":"http://SERVER_IP:5000","region":"us-east-1"}'
kiro-cli settings api.cps.service '{"endpoint":"http://SERVER_IP:5000","region":"us-east-1"}'Zero-login clients (no Kiro account of their own) can use setup-client.sh SERVER_URL REGION KEY, which points the endpoints and seeds a placeholder token so the CLI's local login gate passes. The proxy swaps in a real pooled account.
export ANTHROPIC_BASE_URL=http://SERVER_IP:5000
export ANTHROPIC_API_KEY=kpp_xxxxxxxxxxxxxxxx # created in the admin panel
claudeexport OPENAI_BASE_URL=http://SERVER_IP:5000/v1
export OPENAI_API_KEY=kpp_xxxxxxxxxxxxxxxx
codexBoth surfaces support streaming (stream: true) and non-streaming, tool calling, and thinking/reasoning passthrough. Every request's credit cost is charged to the API key; when a key exceeds its creditLimit the proxy returns HTTP 402.
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
Liveness check |
POST |
/v1/messages (also /anthropic/v1/messages) |
Anthropic Messages |
POST |
/v1/messages/count_tokens |
Token counting |
POST |
/v1/chat/completions (also /openai/v1/chat/completions) |
OpenAI Chat Completions |
GET |
/v1/models, /openai/v1/models |
Model list |
GET |
/setup-client.sh |
Serves the client setup script |
| — | /admin, /admin/api/* |
Admin panel + API (SSE at /admin/api/events) |
An account is one Kiro identity plus its credentials. Add accounts three ways:
- Admin panel → Accounts → Add account (pick the auth method, paste tokens).
- Import from local
kiro-cli→go run ./cmd/import-local, or the Import kiro-cli button. - Edit
config.jsondirectly and restart.
authMethod |
Requires | Token refresh endpoint |
|---|---|---|
idc |
refreshToken, clientId, clientSecret, region |
oidc.{region}.amazonaws.com/token |
social |
refreshToken (AWS Builder ID) |
prod.us-east-1.auth.desktop.kiro.dev/refreshToken |
external_idp |
refreshToken, clientId, tokenEndpoint (Microsoft Entra, etc.) |
custom IdP endpoint |
api_key |
accessToken (ksk_…) |
not refreshed |
A
profileArnis required for chat and forGetUsageLimitsonidc/social/external_idpaccounts.api_keyaccounts use the region-bound GET form instead.
round-robin— cycle through enabled accounts in order.smart— prefer the account with the most remaining quota, and skip any that are exhausted.
Switch strategies live in Settings (applies to the next request) or via "strategy" in the config.
Each Kiro account carries a subscription tier. KiroPool reads the tier and its credit quota from the GetUsageLimits control-plane call and shows remaining vs. total credit per account in the admin UI.
| Tier | subscriptionType |
|---|---|
| Free | FREE |
| Pro | PRO |
| Pro+ | PRO_PLUS |
| Pro Max / Power | POWER |
KiroPool tracks whatever
subscriptionTypethe account reports and does not hard-code the tier list. The values above are the ones confirmed from the Kiro control plane; higher-tier plans (marketed as "Pro Max" / "Power") reportPOWERwith a larger creditusageLimit.
How quota is read. GetUsageLimits returns a usageBreakdownList. KiroPool picks the CREDIT breakdown first, then AGENTIC_REQUEST, then falls back to the first entry:
usageLimit— total credit the tier grants this period.usageCurrent— credit consumed so far.- remaining =
usageLimit − usageCurrent(computed for the UI meter). nextResetUnix— when the quota rolls over.
Example: a Power plan may report resourceType="CREDIT" with 5998 / 10000. The smart strategy uses these numbers to prefer accounts with the most headroom and to skip exhausted ones. The account card's quota meter turns amber at 70% and red at 85% usage.
Open http://SERVER_IP:5000/admin for the dashboard (React 18 + Tailwind 4, dark/light, EN/VI):
- KPI cards — total accounts, requests, credits, and aggregate quota.
- Accounts — per-account quota meter (remaining/total credit), credits, requests, status, and enable/disable/delete. Search and filter by state.
- Add / import accounts — auth-method-aware form, plus one-click
kiro-cliSQLite import. - API keys — create
kpp_…keys with optional per-key credit limits. - Connection helper — copy-paste commands to point any client at the proxy.
- Strategy selector — toggle
round-robin↔smart. - Live logs — realtime SSE stream (
log,quota,accountsevents).
Protect it with a password:
{ "adminPassword": "your-secure-password" }Leaving it empty means no auth — only acceptable when bound to 127.0.0.1. Account tokens are never returned to the UI; they are sanitized server-side.
{
"listenAddr": "0.0.0.0:5000",
"strategy": "smart",
"poolKey": "",
"adminPassword": "",
"apiKeys": [
{ "id": "key-1", "name": "laptop", "key": "kpp_…", "creditLimit": 0, "enabled": true }
],
"accounts": [
{
"id": "account-1",
"email": "user@example.com",
"accessToken": "eyJ…",
"refreshToken": "eyJ…",
"clientId": "…",
"clientSecret": "…",
"authMethod": "idc",
"region": "us-east-1",
"profileArn": "arn:aws:codewhisperer:us-east-1:123456789:profile/xxxxxxxx",
"enabled": true
}
]
}| Field | Description |
|---|---|
listenAddr |
127.0.0.1:5000 (local) or 0.0.0.0:5000 (remote). |
strategy |
round-robin or smart (quota-aware). |
poolKey |
Optional shared secret; clients send it as the X-Pool-Key header. |
adminPassword |
Protects /admin. Empty = no auth (localhost only). |
apiKeys[] |
Pool API keys clients present as the bearer token. creditLimit: 0 = unlimited. |
accounts[] |
Pooled Kiro accounts. See Pooling accounts. |
Never commit real tokens or
config.jsonwith credentials.config.jsonanddata/are git-ignored and Docker-ignored for this reason. Rotate any secret that leaks.
Because this is a plain HTTP proxy (no MITM), remote deployment is straightforward:
# On the SERVER — config.json: "listenAddr": "0.0.0.0:5000", set a poolKey / API keys
./kiro-pool-proxy
sudo ufw allow 5000/tcp
# On the CLIENT
./set-endpoints.sh http://SERVER_IP:5000 us-east-1
kiro-cli chatNo certificate to copy or trust. For exposure over the public internet, either:
- put KiroPool behind TLS (nginx/Caddy) and use an
https://…endpoint, or - tunnel over SSH:
ssh -N -L 5000:127.0.0.1:5000 user@SERVER.
Always set adminPassword and a poolKey (or API keys) when the proxy is reachable from anything other than localhost.
kiro-cli-pool-proxy/
├── main.go # Entry point (plain reverse-proxy)
├── Dockerfile # Pure-Go multi-stage build
├── docker-compose.yml # Compose service (port 5000, ./data volume)
├── config/config.go # Account model + usage accounting
├── auth/
│ ├── refresh.go # Token refresh (idc / social / external_idp)
│ └── usage.go # GetUsageLimits quota reader
├── pool/pool.go # Account selection + cooldown + quota-aware
├── proxy/
│ ├── server.go # HTTP router + tee streaming
│ ├── rewrite.go # profileArn swap + region endpoint table
│ ├── eventstream.go # AWS Event Stream parser (meteringEvent)
│ └── webdist/ # Embedded admin SPA (//go:embed)
├── frontend/ # React 18 + TS + Vite 6 + Tailwind 4 admin UI
├── cmd/import-local/ # Import accounts from kiro-cli SQLite
├── set-endpoints.sh # Point kiro-cli at the proxy
└── setup-client.sh # Zero-login client setup
If KiroPool saves you some credits, a star helps.
Released under the MIT License.