Repository navigation
[Feature]: OpenRouter — automatic key rotation & model failover when the free-tier quota is exhausted #4024
Copy link
Copy link
Closed
Labels
account-poolOAuth, credentials, Codex pool, quota, failover, plansOAuth, credentials, Codex pool, quota, failover, plansenhancementNew feature or requestNew feature or requestproviderProvider adapters, OpenAI-compat presets, upstream API quirksProvider adapters, OpenAI-compat presets, upstream API quirks
Description
Area
Provider adapters
What are you trying to accomplish?
Use OpenRouter free-tier models (openrouter/minimax/minimax-m3:free, openrouter/openrouter/free, etc.) behind a combo/failover target without manual intervention when a key's weekly free quota is exhausted (OpenRouter replies with 429 rate_limit_error, Weekly/Monthly Limit Exhausted... will reset at <date>).
What prevents this today?
OCX already has two related mechanisms:
apiKeyPoolmulti-key failover (src/providers/key-failover.ts): on a 429,rotateKeyOn429()moves to the next key in the pool and puts the exhausted key into cooldown (default 60 s, cap 10 min,Retry-Afterrespected).- Combo failover across targets: a combo can fail over to another provider/model.
The gap for the OpenRouter free-tier case specifically:
- The free-quota reset horizon is weekly (
Your limit will reset at 2026-09-09 03:30:06), butMAX_COOLDOWN_MScaps a key's cooldown at 10 minutes. After 10 minutes the exhausted key becomes active again, and roughly every other request goes to a dead key, failing with 429 and only then rotating — the wasted attempt repeats all week until the quota actually resets. - The OpenRouter error body carries the exact reset timestamp (
will reset at <date>), but the cooldown logic appears to only honor theRetry-AfterHTTP header; the body-parsed reset date is not used to extend the cooldown. - There is no per-key quota-state notion (e.g.,
exhausted_until: <ts>), so an exhausted key cannot be parked for the rest of the window the upstream itself declares.
What should OpenCodex do?
- Parse the OpenRouter 429 JSON body for
rate_limit_errorand extract the reset timestamp from the message; park the key in cooldown until that timestamp (bounded, e.g. up to 7 days, configurable). - Optionally: a generic
quotaExhaustedCooldownMs/quotaExhaustedUntilReset: trueknob underretryOn429(or a sibling policy) so other providers that report reset windows in the error body get the same behavior. - With a single key in the pool, the same detection should feed combo-level failover: mark the target as quota-exhausted so the combo routes to the next target for the remainder of the window instead of burning one failed attempt per request.
Example usage or interface
{
"providers": {
"openrouter": {
"authMode": "key",
"apiKeyPool": [
{ "id": "key-a", "key": "sk-or-..." },
{ "id": "key-b", "key": "sk-or-..." }
],
"retryOn429": {
"enabled": true,
"quotaResetFromBody": true,
"maxQuotaCooldownMs": 604800000
}
}
}
}Expected behavior: after the upstream answers 429 Weekly/Monthly Limit Exhausted ... will reset at 2026-09-09 03:30:06, key-a is parked until that timestamp and all traffic goes to key-b (or the combo's next target) without per-request 429 tax.
Alternatives or workarounds
- Manually pausing/removing the exhausted key in the dashboard until the reset date.
- Using a short combo retry budget so every request pays one 429 before failing over (current de-facto behavior; wasteful on long windows).
- External cron that swaps keys in the config when the weekly reset hits.
Checks
- I searched existing issues and documentation.
- This request describes a concrete OpenCodex workflow rather than merely naming a desired technology.
- I removed secrets and personal data.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
account-poolOAuth, credentials, Codex pool, quota, failover, plansOAuth, credentials, Codex pool, quota, failover, plansenhancementNew feature or requestNew feature or requestproviderProvider adapters, OpenAI-compat presets, upstream API quirksProvider adapters, OpenAI-compat presets, upstream API quirks
Activity
Ingwannu commentedon Sep 8, 2026
Thanks — I checked dev@29bb221c3. The bounded-cooldown limitation is real: src/providers/key-failover.ts clamps Retry-After to ten minutes, defaults to 60 seconds when the header is absent, and rotateKeyOn429 receives a header rather than an error-body reset field. A pool with fewer than two entries returns without recording a key cooldown. The proposed quotaResetFromBody/maxQuotaCooldownMs settings are not implemented today.
One important distinction: rotation persists the selected replacement key. Expiry of another key's cooldown does not by itself switch back to it, so the code does not establish "every other request" failing while a healthy replacement remains selected. We need to distinguish key-local quota, account-wide quota, and a model/provider-specific limit before parking either a key or an entire combo target.
Please share only the upstream hostname (no full URL/query), exact model ID, sanitized error type/message containing the reset time, whether Retry-After was present and its value, and whether the pool keys share one upstream account. Remove keys, account IDs and unrelated payloads. A reset time without an explicit timezone must not be interpreted as the OCX host's local time.
I am keeping this as a feature/design issue, not marking it fixed. A safe direction is to respect a verified quota scope and reset deadline, then use an already-authorized fallback or surface exhaustion; rotating keys must not be treated as bypassing a shared account allowance. I am not proposing a global increase to the ordinary transient-429 cooldown or arbitrary parsing of every provider's error text.
lidge-jun commentedon Sep 8, 2026
리뷰 · 우선순위 52 / 80
이 이슈는 OpenRouter 무료 티어 주간/월간 쿼타가 바닥났을 때, OCX가 키를 너무 빨리 다시 집어 들어서 요청마다 429 세금만 내는 문제를 고쳐 달라는 요청이다. 작성자가 쓰는 모델은
openrouter/minimax/minimax-m3:free,openrouter/openrouter/free같은 free 계열이고, 업스트림은rate_limit_error와 함께Weekly/Monthly Limit Exhausted... will reset at <date>문구를 준다. 원하는 결과는 단순하다. 그 reset 시각까지 키(또는 콤보 타깃)를 주차장에 두고, 풀의 다른 키나 콤보의 다음 타깃으로만 보내라.지금
devHEAD29bb221c3의src/providers/key-failover.ts를 다시 읽으면, 작성자 진단의 핵심은 맞다.MAX_COOLDOWN_MS가 10분으로 고정돼 있고,parseRetryAfterMs/rotateKeyOn429는 HTTPRetry-After헤더만 본다. 본문 날짜는 안 읽는다. 헤더가 없으면 기본 60초 쿨다운이다. 풀 항목이 2개 미만이면 키 쿨다운을 기록하지 않고 null을 돌려, 호출 쪽이 429를 그대로 올린다. 그래서 “주간 reset인데 10분마다 죽은 키를 다시 시도”하는 체감은 실제로 난다. 다만 OWNER(Ingwannu) 댓글이 이미 중요한 교정을 했다. 회전이 선택한 대체 키를 persist하면, 다른 키 쿨다운이 끝나도 자동으로 죽은 키로 돌아가며 “요청마다 번갈아 실패”하는 그림과는 다르다. 그래서 설계를 짜려면 키 단위 쿼타 / 계정 단위 쿼타 / 모델·프로바이더 한도를 먼저 갈라야 한다. 공유 계정 허용량을 키 로테이션으로 우회하는 구현은 받으면 안 된다.점수 52인 이유. 무료 OpenRouter 풀 사용자에게는 실사용 통증이고, account-pool·provider 라벨과도 맞다. 그런데 지금
dev가 최적화하는 tip은 릴리스 기록 마감(#4021) 이후 제품 스택(#4002 등)과 하드락/쿼타 회귀 쪽이다. 이 이슈는 새 설정 면(quotaResetFromBody/maxQuotaCooldownMs)과 프로바이더별 본문 파서가 필요한 설계 이슈이지, 즉시 머지할 한 줄 패치가 아니다. #3376(reset window를 스케줄 입력으로), #3375(OAuth pool lifecycle), 이미 닫힌 Go 풀 #2060과도 이웃이지만 중복은 아니다. OWNER가 요청한 재현 패킷(호스트명만, 모델 ID, 살균된 에러, Retry-After 유무, 키가 같은 업스트림 계정인지)이 아직 이슈에 안 붙어 있다. timezone 없는 reset 시각을 호스트 로컬로 읽으면 안 된다는 주의도 이미 적혀 있다.라인 / 경로로 보면 문제 지점은 이렇다.
라인 26 (
src/providers/key-failover.ts) -MAX_COOLDOWN_MS = 10 * 60_000. 주간 free 한도를 표현할 수 없다라인 57–75 (
src/providers/key-failover.ts) - Retry-After만 파싱하고 상한을 10분에 자른다. 본문will reset at는 경로가 없다rotateKeyOn429/ 풀 크기 가드 - 키가 하나면 쿨다운을 남기지 않고 null. 콤보 failover로 “타깃 고갈”을 넘기는 연결도 없다설정 스키마 - 제안된
retryOn429.quotaResetFromBody/maxQuotaCooldownMs는 현재 config/types에 없다. 넣으면 프로바이더 공통 정책인지 OpenRouter 전용 파서인지부터 정해야 한다이슈 본문 “every other request” 서술 - persist된 선택 키가 있으면 그 서술이 과하다. 재현 시 selected key / cooldown map을 같이 적어 달라고 OWNER가 이미 요청했다
메인테이너의 판단이 필요한 지점
retryOn429공통 훅으로 열지너의 추천
enhancement로 유지하고, 지금 바로 구현 PR을 받지 마세요. OWNER 댓글대로 살균된 재현 패킷(호스트명, 모델 ID, 에러 type/message, Retry-After, 키들이 같은 계정인지)을 이슈에 붙인 뒤, “검증된 쿼타 스코프 + reset 마감 → 이미 인가된 fallback 또는 고갈 표시” 설계만 확정하세요. 일반 transient 429의 10분 상한을 전역으로 올리는 패치나, 임의 프로바이더 에러 문자열 파싱은 거절하는 편이 맞다. 구현 시에는
key-failover.ts에 body-reset 경로를 명시적으로 넣고, 공유 계정 키면 rotate 대신 타깃 고갈로 올리도록 테스트를 강제하세요.이 댓글은 grok-bot이 작성했습니다
feat(providers): park a key until the reset instant the upstream decl…
feat(providers): park a key until the reset instant the upstream decl…
feat(providers): park a key until the reset instant the upstream decl…
feat(providers): park a key until the reset instant the upstream decl…
fix(providers): harden OpenRouter quota reset cooldowns (#4980)