Repository navigation
[Feature]: add proactive round-robin and broader quota failover to apiKeyPool #1267
Description
Area
Provider authentication / API-key pools / routing
Current status
OpenCodex already supports reactive failover across multiple API keys configured for one provider.
The existing apiKeyPool behavior includes:
- one active key at a time;
- automatic rotation when the active key receives HTTP
429; - cooldown of the exhausted/rate-limited key;
- same-request retry using the replacement key;
- skipping keys that are already cooling down or unavailable;
- persisting the newly selected key as the active key.
So this issue does not track adding basic API-key failover.
The remaining work consists of two enhancements:
- recognized quota-exhaustion failover beyond HTTP
429; - optional proactive round-robin distribution across API keys.
1. Broader quota-exhaustion failover
Some providers report exhausted account or plan quota using signals other than 429.
Examples may include:
402
insufficient_quota
provider-specific quota-exhaustion codes
Some providers may use 403 for quota conditions, but a generic 403 must not automatically trigger key rotation.
403 can also represent:
- invalid permissions;
- account restrictions;
- missing scope;
- authorization failure;
- unsupported model access.
Rotation should therefore use either:
- OpenCodex's normalized provider-error classification; or
- an explicit provider-specific set of tested quota-exhaustion signals.
When quota exhaustion is positively identified:
Key A
-> recognized quota exhaustion
-> mark/cool down Key A
-> select Key B
-> retry the same request
The existing 429 behavior must remain unchanged.
2. Optional proactive round-robin
API-key pools currently use reactive failover:
Request 1 -> Key A
Request 2 -> Key A
Request 3 -> Key A
...
Key A fails -> Key B
Add an opt-in provider-level strategy that can instead distribute new requests proactively:
Request 1 -> Key A
Request 2 -> Key B
Request 3 -> Key C
Request 4 -> Key A
The existing behavior remains the default.
A possible configuration contract is conceptually:
{
"apiKeyPoolStrategy": "failover"
}or:
{
"apiKeyPoolStrategy": "round-robin"
}The exact field name may follow the final configuration conventions.
Selection state
Round-robin requires an explicit runtime-state contract.
Define:
- where the cursor is stored;
- when it advances;
- what happens when a selected key is cooling down;
- what happens when a request fails before reaching the upstream;
- how concurrent requests advance the cursor;
- what happens after proxy restart.
The implementation must not rely on unsynchronized mutation of provider.apiKey.
Concurrency
Concurrent requests must not corrupt selection state or accidentally collapse proactive distribution back onto one key.
For example, with three usable keys and concurrent requests:
A
B
C
should be selected according to a deterministic allocation contract rather than every request reading the same stale cursor before any of them advances it.
Regression coverage should exercise genuinely overlapping selections rather than only sequential calls.
Sticky behavior
If a sticky/session-affinity option is introduced, it must be explicit.
Possible semantics include keeping a key for:
- one request only;
- N requests;
- one conversation/session.
Do not implicitly reuse OAuth account-affinity semantics unless the same contract is actually appropriate for API keys.
Basic round-robin does not require sticky behavior for the first implementation.
Unavailable keys
Both strategies must skip keys whose current state says they cannot safely be selected.
Examples:
cooling down
known quota exhausted
temporarily unavailable
When every key is unavailable, return the existing bounded provider failure rather than looping indefinitely.
The error should not disclose API-key material.
Interaction with reactive failover
Proactive selection and same-request failover are complementary.
For example:
new request
-> round-robin chooses Key B
-> Key B returns recognized quota exhaustion
-> Key B enters cooldown
-> same request retries with next eligible key
The retry path should not incorrectly consume the round-robin cursor multiple times unless that is part of an explicitly documented policy.
Key identity and observability
Usage/request-attempt diagnostics may need to distinguish pool slots so operators can verify distribution and diagnose failures.
Use a safe opaque identifier such as:
key-1
key-2
pool slot id
or equivalent internal identity.
Never expose:
- raw API keys;
- key prefixes that materially reveal the credential;
- secret hashes intended as authentication evidence;
- complete credential values in logs, usage history, diagnostics, or management DTOs.
Management surfaces
If the strategy becomes configurable through the Dashboard/API/CLI, all surfaces should represent the same provider-level value.
At minimum users should be able to distinguish:
Failover
Round robin
with Failover preserving current behavior.
Do not require users to create N separate providers and a combo merely to distribute requests across N credentials for the same provider.
Example use case
Given one provider:
amd
with three API keys:
Key A
Key B
Key C
and:
apiKeyPoolStrategy = round-robin
new requests should distribute across the usable keys without requiring:
amd-01
amd-02
amd-03
combo/amd
If Key B later exhausts its quota, subsequent selection should skip it until its cooldown/availability state permits reuse.
Out of scope
This issue does not require:
- replacing the existing API-key pool;
- changing OAuth account-pool routing;
- changing combo round-robin semantics;
- treating every
402or403from every provider as quota exhaustion; - exposing raw credentials for diagnostics;
- creating separate provider entries per key.
Acceptance criteria
Existing behavior preserved
- API-key pools already exist.
- One key can be active at a time.
- HTTP
429can rotate to another key. - Failed keys can enter cooldown.
- Same-request retry after
429exists. - Unavailable/cooling-down keys can be skipped.
- The replacement can become the active key.
Remaining quota-failover work
- Recognized quota-exhaustion responses beyond
429can trigger same-request rotation. - Quota classification is explicit/provider-aware rather than based on every
403. - Existing
429behavior remains compatible. - All-keys-unavailable behavior is bounded and tested.
Remaining proactive-distribution work
-
failoverremains the default strategy. - A provider can explicitly enable
round-robin. - Sequential requests rotate deterministically across eligible keys.
- Cooling-down/unavailable keys are skipped.
- Concurrent selections are race-safe.
- Restart semantics are defined and tested.
- Interaction between proactive selection and same-request failover is deterministic.
Privacy and observability
- Usage/request attempts can distinguish pool slots safely where needed.
- Raw API-key material never appears in logs, diagnostics, usage history, or management responses.
- Focused regression coverage exists for multiple keys, concurrent requests, quota exhaustion, cooldowns, and all-unavailable state.
- Accepted implementation is merged into
dev.
Activity
Ingwannu commentedon Aug 8, 2026
Thanks — I checked this against the current implementation and the request is valid as a scoped enhancement.
apiKeyPoolis presently an active-key pool with same-request rotation on 429 (src/providers/key-failover.ts/src/server/responses/core.ts); proactive distribution exists for OAuth account pools and combos, but not for API-key pools. So the N-provider workaround you described is real.\n\nThe safest shape is an opt-in provider-level strategy while preserving today’s failover behavior as the default. A future implementation should keep the selection cursor in runtime state, define restart/concurrency semantics, keep key identity secret-safe in usage attempts, and add same-request recovery without blindly treating every 403 as quota exhaustion (403 can also mean invalid scope or authorization). Quota rotation should use the existing normalized provider error classification or an explicit, tested set of status/body signals.\n\nI am leaving this open with that scope. Acceptance should cover deterministic round-robin/sticky behavior, concurrent requests, restart behavior, 429 plus validated quota-exhaustion responses, all-keys-unavailable behavior, and no key material in logs or management DTOs.Wibias commentedon Aug 8, 2026
Clarification on #1267: what already exists vs. what is still missing
The current implementation already covers the core API-key failover behaviour.
What we already have
apiKeyPoolis already an active-key pool for API-key providers:provider.apiKey.429, OpenCodex can rotate to the next available key in the pool.So the basic behaviour:
already exists today.
That also matches observed behaviour in practice: when one configured API key ran out of usable quota and the upstream returned
429, OpenCodex automatically switched to the next key.What #1267 would actually add
The remaining work is narrower and consists of two separate capabilities.
1. Broader quota-exhaustion failover
Today, API-key pool rotation is primarily triggered by
429.Some providers report exhausted account quota differently, for example:
402insufficient_quota403, although403must not be treated generically as quota exhaustion because it can also mean invalid permissions, scope, or authenticationWhat is still needed is same-request key rotation for recognised quota-exhaustion responses beyond
429, using the existing normalised provider error classification or a small explicit and tested set of status/body signals.In other words:
This is an extension of the existing failover system rather than a new key-pool concept.
2. Optional proactive round-robin distribution
This part does not exist for API-key pools today.
Current behaviour is reactive failover:
The proposed opt-in round-robin strategy would instead allow:
This should remain optional so the existing failover behaviour stays the default.
A possible configuration shape is:
{ "apiKeyPool": [ { "id": "key-a", "key": "..." }, { "id": "key-b", "key": "..." } ], "apiKeyPoolStrategy": "round-robin" }with the existing behaviour treated as:
{ "apiKeyPoolStrategy": "failover" }Suggested scope
I would therefore describe #1267 as a scoped enhancement, not as adding API-key rotation from scratch.
Already implemented
429Still needed
429round-robinstrategyAcceptance criteria for the remaining work
429rotation continues to work exactly as today.403responses are not automatically treated as quota exhaustion.round-robincan be enabled explicitly per provider.Bottom line
The feature request is valid, but the distinction matters:
That framing keeps the implementation focused and avoids rebuilding behaviour that is already present.
github-actions commentedon Aug 8, 2026
Automated translation bookkeeping — detected language: English.
[-]Feature: round-robin load balancing across API keys within a single provider (apiKeyPool)[/-][+][Feature]: add proactive round-robin and broader quota failover to apiKeyPool[/+]lidge-jun commentedon Aug 18, 2026
Closing as consolidated into #695. Proactive round-robin and quota-aware failover for
apiKeyPoolis the same policy surface as the generic OAuth account-pool failover tracked there — selection policy, session affinity, and quota-aware rotation should ship as one contract rather than two parallel ones for key pools and account pools. If a key-pool-specific behavior falls out of scope when #695 is designed, it can fork back out into its own issue then.