Skip to content

[Feature]: add proactive round-robin and broader quota failover to apiKeyPool #1267

Closed as not planned

Description

@RanceLee233

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:

  1. recognized quota-exhaustion failover beyond HTTP 429;
  2. 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 402 or 403 from 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 429 can rotate to another key.
  • Failed keys can enter cooldown.
  • Same-request retry after 429 exists.
  • Unavailable/cooling-down keys can be skipped.
  • The replacement can become the active key.

Remaining quota-failover work

  • Recognized quota-exhaustion responses beyond 429 can trigger same-request rotation.
  • Quota classification is explicit/provider-aware rather than based on every 403.
  • Existing 429 behavior remains compatible.
  • All-keys-unavailable behavior is bounded and tested.

Remaining proactive-distribution work

  • failover remains 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

  1. Ingwannu commented on Aug 8, 2026

    @Ingwannu
    Owner

    Thanks — I checked this against the current implementation and the request is valid as a scoped enhancement. apiKeyPool is 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.

  2. Wibias commented on Aug 8, 2026

    @Wibias
    Contributor

    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

    apiKeyPool is already an active-key pool for API-key providers:

    • One key is active at a time via provider.apiKey.
    • If the active key receives an HTTP 429, OpenCodex can rotate to the next available key in the pool.
    • The failed key is placed into cooldown.
    • The replacement key can be retried within the same request.
    • If multiple keys are exhausted/cooling down, the pool skips them and continues to the next available key.
    • The newly selected key becomes the active key.

    So the basic behaviour:

    Key A -> quota/rate-limit 429 -> Key B -> continue request
    

    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:

    • HTTP 402
    • insufficient_quota
    • provider-specific quota-exhaustion error codes
    • in some cases 403, although 403 must not be treated generically as quota exhaustion because it can also mean invalid permissions, scope, or authentication

    What 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:

    Key A
      -> recognised quota exhaustion
      -> mark Key A unavailable/cooling down
      -> retry with Key B in the same request
    

    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:

    Request 1 -> Key A
    Request 2 -> Key A
    Request 3 -> Key A
    ...
    Key A fails -> Key B
    

    The proposed opt-in round-robin strategy would instead allow:

    Request 1 -> Key A
    Request 2 -> Key B
    Request 3 -> Key C
    Request 4 -> Key A
    

    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

    • API-key pools
    • active-key selection
    • automatic rotation on 429
    • cooldown handling
    • same-request retry after rotation
    • skipping unavailable keys
    • persistence of the newly active key

    Still needed

    • recognised quota-exhaustion failover beyond 429
    • optional provider-level round-robin strategy
    • deterministic cursor/sticky behaviour
    • concurrency-safe key selection
    • clearly defined restart semantics
    • all-keys-unavailable behaviour
    • secret-safe key identity in usage attempts/logging
    • no raw key material in logs or management DTOs

    Acceptance criteria for the remaining work

    1. Existing failover remains the default and does not change behaviour.
    2. 429 rotation continues to work exactly as today.
    3. Recognised quota-exhaustion responses can trigger same-request rotation.
    4. Generic 403 responses are not automatically treated as quota exhaustion.
    5. round-robin can be enabled explicitly per provider.
    6. Round-robin selection is deterministic under sequential requests.
    7. Concurrent requests do not corrupt or double-advance selection state.
    8. Restart behaviour is documented and tested.
    9. All-keys-unavailable behaviour is explicit and tested.
    10. Usage attempts may identify which pool slot was used, but must never expose raw API-key material.

    Bottom line

    The feature request is valid, but the distinction matters:

    We already have reactive API-key failover. What is missing is broader quota-error recognition and optional proactive round-robin distribution across keys within a single provider.

    That framing keeps the implementation focused and avoids rebuilding behaviour that is already present.

  3. github-actions commented on Aug 8, 2026

    @github-actions
    Contributor

    Automated translation bookkeeping — detected language: English.

  4. changed the title [-]Feature: round-robin load balancing across API keys within a single provider (apiKeyPool)[/-] [+][Feature]: add proactive round-robin and broader quota failover to apiKeyPool[/+] on Aug 9, 2026
  5. added
    enhancementNew feature or request
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    account-poolOAuth, credentials, Codex pool, quota, failover, plans
    on Aug 9, 2026
  6. lidge-jun commented on Aug 18, 2026

    @lidge-jun
    Owner

    Closing as consolidated into #695. Proactive round-robin and quota-aware failover for apiKeyPool is 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansenhancementNew feature or requestproviderProvider adapters, OpenAI-compat presets, upstream API quirks

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions