Problem / Use Case
I'm trying to use OpenRouter (both free and paid accounts) as a tier in my combos, but currently OmniRoute has no quota tracking for OpenRouter — unlike Claude Code (5-hour + weekly window tracking), Codex (5-hour + weekly reset-aware rotation), or Antigravity (provider quota view).
This matters because OpenRouter does have real, hittable quota walls — even on free accounts:
- Free-model rate limits (platform-level, per account):
:free model variants are capped at 20 requests/minute.
- Daily window: 50 requests/day if the account has purchased less than $10 in credits (all-time), raised to 1000 requests/day once at least $10 has been purchased.
- Failed attempts still count toward the daily quota.
- Limits are governed globally per account — extra API keys don't help.
- Credit limits (paid usage):
- Account balance can run out (negative balance →
402 errors, which also blocks free models).
- Individual API keys can have optional per-key spending caps (
limit / limit_remaining / limit_reset).
Today, when any of these ceilings is hit, OmniRoute only finds out via a hard 429/402 failure. There is no "Provider Quota" entry for OpenRouter in the dashboard, no reset-aware routing, and no way to drain the free daily window intentionally before falling back — which is exactly the "track & drain every free token" promise OmniRoute makes for other providers.
Proposed Solution
Add OpenRouter to the dashboard quota tracker and make routing reset-aware, using OpenRouter's official monitoring APIs.
1. Poll OpenRouter's monitoring endpoints
GET https://openrouter.ai/api/v1/key (Bearer: the account's API key) — per-key limits + usage:
{
"data": {
"label": "sk-or-v1-...",
"limit": 10.0, // per-key credit cap in USD, null = unlimited
"limit_reset": "monthly", // how the cap resets, null = never
"limit_remaining": 6.42, // remaining credits on the key, null = unlimited
"include_byok_in_limit": true,
"usage": 3.58, // credits used (all time, USD)
"usage_daily": 0.12, // current UTC day
"usage_weekly": 0.87, // current UTC week (starts Monday)
"usage_monthly": 3.58, // current UTC month
"byok_usage": 0, // same trio exists for BYOK usage
"is_free_tier": true // false once the user has ever purchased credits
}
}
GET https://openrouter.ai/api/v1/credits — account-level balance:
- Returns
total_credits (purchased) and total_usage; balance = total_credits - total_usage.
- Note: values are cached on OpenRouter's side and can be up to ~60s stale — poll accordingly (e.g. every 60s or on-demand with a 60s cache).
2. Track the free-model request window locally
OpenRouter does not expose a "free requests remaining today" counter via API — /api/v1/key reports credit spend in USD, not request counts. So the free-tier daily window needs a local counter:
- Count requests routed to
:free model variants per OpenRouter account, in a UTC-day window (and a 20/min rolling window).
- Derive the daily cap from
is_free_tier / purchase history: 50/day (no $10 purchase) vs 1000/day ($10+ purchased). Make the cap user-overridable in the provider profile in case OpenRouter changes thresholds.
- Sync/correct the local counter using the
X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers that OpenRouter attaches to platform-limit 429 error responses (note: successful responses do not carry these headers), and honor Retry-After when present.
- Count failed attempts too — OpenRouter counts them against the daily quota.
3. Surface it in the dashboard (same UX as Codex/Claude/Antigravity)
- Dashboard → Provider Quota: show per-account cards for OpenRouter with:
- Credit balance (account) + per-key
limit_remaining / limit_reset
- Daily / weekly / monthly spend (
usage_daily / usage_weekly / usage_monthly)
- Free-model window:
N / 50 (or N / 1000) requests today + 20 RPM gauge + time until UTC-midnight reset
- Routing integration:
- Mark the account "quota exhausted" (for free variants) when the daily window is drained, and rotate to the next account/tier instead of burning requests into guaranteed 429s.
- Reset-aware re-enable at UTC midnight (daily) and per
limit_reset for keyed credit caps.
- Treat
402 (negative balance / key cap exhausted) as a quota event, not a generic provider failure, so circuit breakers don't misclassify it.
- Multi-account: respect that limits are per account, not per key — multiple keys of the same account must share one quota bucket, while genuinely separate accounts rotate round-robin as usual.
Alternatives Considered
- Status quo (reactive 429/402 fallback): works, but wastes requests (failed attempts count against the free daily quota), adds latency, and gives users zero visibility before the wall.
- Parsing only 429 headers without polling:
X-RateLimit-* headers only appear on error responses, so you'd never see quota state until you're already failing — insufficient on its own, but great as a correction signal (proposed above as the sync mechanism).
- External tools (e.g. CodexBar-style menubar trackers): they prove the two-endpoint approach works, but they don't feed OmniRoute's routing decisions, which is the main value here.
Acceptance Criteria
- OpenRouter accounts appear in Dashboard → Provider Quota with: credit balance, per-key
limit_remaining/limit_reset, daily/weekly/monthly spend, and free-model daily window with reset countdown.
GET /api/v1/key and GET /api/v1/credits are polled with caching (≥30–60s) and graceful degradation when the endpoints fail (quota card shows "unknown", routing unaffected).
- Free-model request counting works per UTC day and per rolling minute, keyed per account (shared across keys of the same account), including failed attempts.
- Local counters are corrected from
X-RateLimit-Remaining/X-RateLimit-Reset on OpenRouter platform 429s, and Retry-After is honored.
- When the free daily window is exhausted, combos skip OpenRouter free variants and fall to the next tier without emitting requests destined to 429; the account is automatically re-enabled after UTC-midnight reset.
402 responses (negative balance / key credit cap) mark the account as credit-exhausted rather than tripping the generic circuit breaker.
- Daily cap (50 vs 1000) is auto-derived but user-overridable in the provider profile.
- Documentation updated (provider setup guide + quota tracker docs).
Area
Provider Support, Analytics / Usage Tracking, Dashboard / UI
Related Provider(s)
OpenRouter
Additional Context
Expected Test Plan
- Unit tests for the OpenRouter quota service:
/api/v1/key + /api/v1/credits response parsing (including null limit fields and BYOK fields), balance math, and cache TTL behavior.
- Unit tests for the free-window counter: UTC-day rollover, 20 RPM rolling window, failed-request counting, header-based correction from mocked 429 responses, and account-level (not key-level) bucketing.
- Integration tests: combo routing skips OpenRouter free variants when the window is drained and resumes after simulated UTC-midnight reset;
402 marks credit-exhausted without tripping the circuit breaker.
- Regression: existing provider quota tests (Codex, Claude Code, Antigravity) remain green; keep coverage threshold at current target.
Problem / Use Case
I'm trying to use OpenRouter (both free and paid accounts) as a tier in my combos, but currently OmniRoute has no quota tracking for OpenRouter — unlike Claude Code (5-hour + weekly window tracking), Codex (5-hour + weekly reset-aware rotation), or Antigravity (provider quota view).
This matters because OpenRouter does have real, hittable quota walls — even on free accounts:
:freemodel variants are capped at 20 requests/minute.402errors, which also blocks free models).limit/limit_remaining/limit_reset).Today, when any of these ceilings is hit, OmniRoute only finds out via a hard
429/402failure. There is no "Provider Quota" entry for OpenRouter in the dashboard, no reset-aware routing, and no way to drain the free daily window intentionally before falling back — which is exactly the "track & drain every free token" promise OmniRoute makes for other providers.Proposed Solution
Add OpenRouter to the dashboard quota tracker and make routing reset-aware, using OpenRouter's official monitoring APIs.
1. Poll OpenRouter's monitoring endpoints
GET https://openrouter.ai/api/v1/key(Bearer: the account's API key) — per-key limits + usage:{ "data": { "label": "sk-or-v1-...", "limit": 10.0, // per-key credit cap in USD, null = unlimited "limit_reset": "monthly", // how the cap resets, null = never "limit_remaining": 6.42, // remaining credits on the key, null = unlimited "include_byok_in_limit": true, "usage": 3.58, // credits used (all time, USD) "usage_daily": 0.12, // current UTC day "usage_weekly": 0.87, // current UTC week (starts Monday) "usage_monthly": 3.58, // current UTC month "byok_usage": 0, // same trio exists for BYOK usage "is_free_tier": true // false once the user has ever purchased credits } }GET https://openrouter.ai/api/v1/credits— account-level balance:total_credits(purchased) andtotal_usage; balance =total_credits - total_usage.2. Track the free-model request window locally
OpenRouter does not expose a "free requests remaining today" counter via API —
/api/v1/keyreports credit spend in USD, not request counts. So the free-tier daily window needs a local counter::freemodel variants per OpenRouter account, in a UTC-day window (and a 20/min rolling window).is_free_tier/ purchase history:50/day(no $10 purchase) vs1000/day($10+ purchased). Make the cap user-overridable in the provider profile in case OpenRouter changes thresholds.X-RateLimit-Limit,X-RateLimit-Remaining, andX-RateLimit-Resetheaders that OpenRouter attaches to platform-limit429error responses (note: successful responses do not carry these headers), and honorRetry-Afterwhen present.3. Surface it in the dashboard (same UX as Codex/Claude/Antigravity)
limit_remaining/limit_resetusage_daily/usage_weekly/usage_monthly)N / 50(orN / 1000) requests today + 20 RPM gauge + time until UTC-midnight resetlimit_resetfor keyed credit caps.402(negative balance / key cap exhausted) as a quota event, not a generic provider failure, so circuit breakers don't misclassify it.Alternatives Considered
X-RateLimit-*headers only appear on error responses, so you'd never see quota state until you're already failing — insufficient on its own, but great as a correction signal (proposed above as the sync mechanism).Acceptance Criteria
limit_remaining/limit_reset, daily/weekly/monthly spend, and free-model daily window with reset countdown.GET /api/v1/keyandGET /api/v1/creditsare polled with caching (≥30–60s) and graceful degradation when the endpoints fail (quota card shows "unknown", routing unaffected).X-RateLimit-Remaining/X-RateLimit-Reseton OpenRouter platform 429s, andRetry-Afteris honored.402responses (negative balance / key credit cap) mark the account as credit-exhausted rather than tripping the generic circuit breaker.Area
Provider Support, Analytics / Usage Tracking, Dashboard / UI
Related Provider(s)
OpenRouter
Additional Context
/api/v1/credits+/api/v1/keypair and notes the ~60s cache staleness: https://github.com/steipete/CodexBar/blob/main/docs/openrouter.mdfinish_reason: "error"instead of an HTTP 429 — the tracker should catch these too.Expected Test Plan
/api/v1/key+/api/v1/creditsresponse parsing (includingnulllimit fields and BYOK fields), balance math, and cache TTL behavior.402marks credit-exhausted without tripping the circuit breaker.