Skip to content

Architecture

Overview

KeyPool is a Cloudflare Worker that acts as a transparent API proxy with key pooling. It sits between your team's SDK calls and upstream API providers, multiplexing requests across a pool of API keys sourced directly from RegBot's D1 database.

Design Principles

  1. Config-driven services — Adding a new API service is a database insert, not a code change
  2. Transparent proxying — The proxy should be invisible to SDKs; just swap the base URL
  3. Async logging — Usage tracking never blocks the proxied response (waitUntil())
  4. RegBot as source of truth — API keys live in RegBot D1; KeyPool reads them directly, never copies
  5. Minimal KV ops — Circuit breaker is opt-in; default path uses 3 KV reads per request

Request Flow

┌─────────────┐     ┌──────────────────────────────────────────────┐
│  Team SDK    │────▶│  KeyPool Worker                              │
│  (Exa, etc.) │     │                                              │
└─────────────┘     │  1. Parse path: /v1/{service}/{upstream_path} │
                    │  2. Authenticate team token (KV cache → D1)   │
                    │  3. Check per-member rate limit (KV)          │
                    │  4. Load service config (KeyPool D1)          │
                    │  5. Fetch active keys (RegBot D1)             │
                    │  6. Filter tripped keys (KV, if enabled)      │
                    │  7. Select key via round-robin (KV counter)   │
                    │  8. Rewrite auth header for upstream          │
                    │  9. Proxy request → upstream API              │
                    │ 10. Stream response back to client            │
                    │ 11. waitUntil: log usage to D1                │
                    │ 12. waitUntil: trip circuit breaker if error  │
                    └──────────────────────────────────────────────┘

Data Stores

KeyPool uses three data stores, each with a clear responsibility:

RegBot D1 (read-only — key source of truth)

KeyPool binds to RegBot's D1 as REGBOT_DB and queries it at request time. RegBot owns the full key lifecycle: registration, balance checking, status updates. When RegBot adds a new credential, KeyPool picks it up on the next request automatically.

Table Read by Purpose
credentials key-selector.ts Active API keys, quota, status

KeyPool D1 (persistent proxy state)

Table Written by Read by Purpose
services Admin API key-selector.ts Service config: base_url, auth_scheme, regbot_service_name
team_tokens Admin API auth.ts Team auth: token hash, scopes, rate limits
usage_log usage-logger.ts (async) Daily cron Append-only request log, pruned after 7 days
usage_daily Daily cron rollup Admin /usage Aggregated stats, kept indefinitely

KV (fast ephemeral state)

Key pattern Purpose TTL Read Write
token:{hash} Cached TeamToken JSON (skip D1 auth lookup) 5 min auth.ts auth.ts on cache miss
rl:{tokenId}:{minute} Per-member request counter for rate limiting 120s rate-limiter.ts rate-limiter.ts
rr:{serviceId} Round-robin rotation counter 24h key-selector.ts key-selector.ts
tripped:{serviceId} Set of tripped credential IDs (circuit breaker) max cooldown key-selector.ts key-selector.ts on upstream error

Per-Request Cost

With circuit breaker disabled (default):

Step Store Reads Writes
Auth token cache KV 1 0 (hit) or 1 (miss → cache)
Rate limit KV 1 1
Round-robin counter KV 1 1
Service config KeyPool D1 1 0
Key list RegBot D1 1 0
Usage log KeyPool D1 0 1 (async, via waitUntil)

Total hot path: 3 KV reads + 2 D1 reads. Usage logging is async and never blocks the response.

With circuit_breaker_enabled = TRUE on a service, add 1 KV read for the tripped set.

Circuit Breaker

Opt-in per service via circuit_breaker_enabled flag. When disabled (default), KeyPool trusts RegBot's status field to filter out bad keys.

When enabled, upstream errors cause the credential ID to be added to a tripped:{serviceId} KV entry (a JSON array of IDs). This is a single KV read per request regardless of pool size.

Upstream Status Cooldown Reason
429 Retry-After header or 60s Rate limited
402 3600s (1 hour) Payment required / exhausted
5xx 30s Transient upstream error

Key Selection Strategies

Strategy When to use
round-robin (default) Even distribution across keys
least-recently-used Maximize cooldown between uses per key
balance-aware Prefer keys with more remaining credit

Auth Scheme Mapping

auth_scheme Header sent to upstream
bearer Authorization: Bearer {key}
x-api-key x-api-key: {key}
xi-api-key xi-api-key: {key}
authorization-raw Authorization: {key}
authorization-token Authorization: Token {key}
query-param ?api_key={key} appended to URL

Cron Jobs

Schedule Task
Daily midnight Roll up usage_logusage_daily, prune logs older than 7 days

Relationship to RegBot

KeyPool reads API keys directly from RegBot's D1 database via cross-D1 binding. See REGBOT-INTEGRATION.md for the full integration details including service name mapping and responsibility split.