Skip to content

feat(providers): OpenRouter quota tracking #6842

Description

@steveepreston

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    backlogTracked for future work; not blockingenhancementNew feature or requestproviders

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions