Skip to content

Repository files navigation

poolctl

CIGo Report CardLicense: MITRelease

A small daemon + CLI for managing a pool of tokens or accounts: health checks, rotation, per-token cooldown, optional refresh, and an HTTP API that hands out a healthy token on demand. It can also run as a passthrough reverse proxy that injects a pooled key into every request and rotates keys on 429/401 — point an OpenAI/Anthropic SDK at it and you get a load-balancing, self-healing key gateway with no client changes.

It is generic — a "token" is any opaque string (API key, cookie, OAuth access token) and validation is whatever you configure (an HTTP request or a shell command). Point it at a list, tell it how to check one, and clients ask the daemon for the next usable token instead of juggling the list themselves.

Why

If you rotate many API keys (scraping, LLM calls), run pools of accounts, or share cookies across workers, you keep re-implementing the same logic: skip the rate-limited ones, drop the dead ones, spread load, re-check periodically. poolctl is that logic as one binary — use it two ways:

  • Broker (poolctl serve): clients GET /take a healthy token and POST /release it, reporting success or failure.
  • Gateway (poolctl proxy): clients speak the upstream API directly and poolctl injects the key, forwards, and retries with a different key on a rate-limit or auth failure — transparent to the client.

Demo

Health-check a pool, then serve it as a gateway that injects and rotates keys:

$ poolctl check -c pool.yamlID STATUS LATENCYt8ca5f15f live 3mst5347ccaf live 2mst753be07d live 1ms3/3 live
$ poolctl proxy -c pool.yaml # proxy :8899, API :8787, metrics :9899
# four client requests through the proxy — the upstream key rotates:req1 -> key_alphareq2 -> key_bravoreq3 -> key_charliereq4 -> key_alpha
$ curl -s :9899/metrics | grep poolctl_poolctl_tokens{status="live"} 3poolctl_requests_total 4poolctl_proxy_retries_total 0

Install

go install github.com/North-web-dev/poolctl@latest
# or
git clone https://github.com/North-web-dev/poolctl &&cd poolctl && go build -o poolctl .

Quickstart

tokens.txt — one token per line (id,token or just token):

key1,sk-aaaa
key2,sk-bbbb
sk-cccc

pool.yaml:

tokens_file: tokens.txtrotation: lrucooldown_sec: 30check:
type: httpurl: "https://api.example.com/me"headers: { Authorization: "Bearer {token}" }success_status: [200]server:
addr: ":8787"

Run the daemon and take a token:

poolctl serve -c pool.yaml
curl -s localhost:8787/take # {"id":"key1","token":"sk-aaaa"}

Report success/failure so the pool can cool down or retire it:

curl -s localhost:8787/release -d '{"id":"key1","ok":true}'
curl -s localhost:8787/release -d '{"id":"key2","ok":false}'# -> marked dead

One-shot check of the whole list without running the daemon:

poolctl check -c pool.yaml

LLM / API-gateway mode

Add an upstream block and run poolctl proxy. Every incoming request borrows a healthy token, has it injected as a header, and is forwarded to base_url; the response is streamed straight back (SSE token-by-token). If the upstream returns a retryable status the request is retried with a different token: 429/5xx cools the key down briefly, 401/403 quarantines it until a health check revives it.

tokens_file: keys.txt # one API key per line, optional ,weightrotation: weighted # spread load by each key's quotacooldown_sec: 20check:
type: httpurl: "https://api.openai.com/v1/models"headers: { Authorization: "Bearer {token}" }success_status: [200]upstream:
enabled: truelisten: ":8080"base_url: "https://api.openai.com"auth_header: "Authorization"auth_template: "Bearer {token}"retry_on: [401, 403, 429, 500, 502, 503, 504]max_retries: 2quarantine_sec: 300metrics:
addr: ":9090"# Prometheus scrape endpoint
poolctl proxy -c pool.yaml
# point any client at the proxy — no API key needed client-side:
curl -s localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"hi"}]}'
# OpenAI SDK — just change base_url; the pooled key is injected upstream.fromopenaiimportOpenAIclient=OpenAI(base_url="http://localhost:8080/v1", api_key="unused")

In proxy mode the control API (/take, /status, /reload) still runs on server.addr, so you can watch the pool while it serves traffic.

HTTP API

MethodPathBody / result
GET/take{"id","token"}; 503 if none available
POST/release`{"id","ok":true
GET/statuscounters + per-token status/last_check/last_used/requests/errors
POST/reloadre-read tokens_file
GET/metricsPrometheus text (tokens by status, request/error/retry counters)

Set server.api_key to require an X-API-Key header on every call. /metrics is always unauthenticated so a scraper can reach it.

Config reference

KeyDefaultDescription
tokens_file— (required)file with one token per line (id,token or token)
rotationlrulru, round_robin, random, or weighted
cooldown_sec30rest period after a token is taken and after a failure
recheck_interval_sec300background health re-check interval
check.typehttphttp or command
check.url / method / headersHTTP check; {token} is substituted
check.success_status[200]status codes that count as healthy
check.cmd / success_outputcommand check; healthy if stdout contains success_output
refresh.enabledfalsetry refreshing a token when its check fails
refresh.url / method / headers / bodyrefresh request; {token} substituted
refresh.token_fieldaccess_tokenJSON field with the new token
proxyproxy URL for http checks
server.addr:8787daemon listen address
server.api_keyif set, required in X-API-Key
upstream.enabledfalseenable the passthrough proxy (poolctl proxy)
upstream.listen:8080proxy listen address
upstream.base_urlupstream to forward to, e.g. https://api.openai.com
upstream.auth_header / auth_templateAuthorization / Bearer {token}how the token is injected
upstream.retry_on[401,403,429,500,502,503,504]upstream statuses that trigger a retry
upstream.max_retries2extra attempts (each with a different token)
upstream.quarantine_sec300rest after a 401/403 (hard) failure
upstream.cooldown_seccooldown_secrest after a 429/5xx (soft) failure
upstream.timeout_sec60per-attempt time-to-first-byte timeout (streaming is unbounded)
metrics.addrif set, a dedicated Prometheus /metrics listener
state_filepool_state.jsonpersisted per-token health/cooldown

For weighted rotation, give each token a third CSV field (id,token,weight); higher-weight keys receive proportionally more traffic. A missing weight is 1.

State (health, cooldown, per-token request/error counts) is persisted to state_file and restored on start; token values always come from tokens_file, never the state file.

Command checks

For services without a simple HTTP probe, validate with any command:

check:
type: commandcmd: "curl -s -o /dev/null -w '%{http_code}' -H 'Authorization: Bearer {token}' https://api.example.com/me"success_output: "200"

License

MIT

Disclaimer

Provided as is, without warranty of any kind. poolctl only manages credentials you supply and runs the health checks you configure. You are responsible for holding those credentials lawfully and for complying with the Terms of Service of any endpoint you check against. The authors accept no liability for misuse.

About

Token/API-key pool manager: health checks, rotation, cooldown + a passthrough LLM/API gateway that injects a pooled key and rotates on 429/401

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages