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¶
- Config-driven services — Adding a new API service is a database insert, not a code change
- Transparent proxying — The proxy should be invisible to SDKs; just swap the base URL
- Async logging — Usage tracking never blocks the proxied response (
waitUntil()) - RegBot as source of truth — API keys live in RegBot D1; KeyPool reads them directly, never copies
- 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_log → usage_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.