The Key Said Eight Dollars

Our AI review sweep showed '$8.00 remaining' in the preflight check and dispatched 27 requests anyway. They all failed. The account had been overdrawn for four days.

September 24, 2026
Bob
5 min read

The preflight check ran. It reported $8.00 remaining. The sweep dispatched 27 review requests. Every single one came back 402.

This was four days after the account hit zero.

Two Levels of “Remaining”

OpenRouter’s billing lives at two distinct scopes:

  • API key level: each key has a per-period daily limit. Hit the limit, add credits, the key resets the next day. The /api/v1/key endpoint returns limit_remaining for this.

  • Account level: the master credit balance. All keys in the account share this pool. When the pool reaches zero, every key returns 402 regardless of its individual daily limit. The /api/v1/credits endpoint tracks this.

Our review_key_usage() function was calling /api/v1/key. It got back limit_remaining: $8.00 — the key’s daily budget, which had barely been touched that day — and declared the budget healthy.

The account had been at total_credits=5305, total_usage=5305.20 since sometime Sunday. Every request was failing at the account layer before it ever reached the key layer. The key still had its full $8 daily allowance in the sense that the allowance tracking record showed $8. That record was just irrelevant.

What the Fix Looks Like

The fix is small:

def fetch_account_credits(api_key: str) -> float | None:
    """Account-level credit balance (not the per-key daily limit)."""
    resp = requests.get(
        "https://openrouter.ai/api/v1/credits",
        headers={"Authorization": f"Bearer {api_key}"},
        timeout=10,
    )
    if resp.status_code != 200:
        return None
    data = resp.json()
    return data.get("data", {}).get("total_credits", 0.0) - data.get("data", {}).get("total_usage", 0.0)


def effective_remaining(api_key: str) -> float | None:
    """Minimum of key daily limit and account balance — whichever gate is tighter."""
    key_remaining = fetch_key_remaining(api_key)    # /api/v1/key → limit_remaining
    account_remaining = fetch_account_credits(api_key)  # /api/v1/credits
    if key_remaining is None or account_remaining is None:
        return None
    return min(key_remaining, account_remaining)

The preflight now logs:

key_remaining: $8.00 (key daily limit)
account balance: $0.0000
effective remaining: $0.00
VERDICT: account credits DEPLETED — add credits at openrouter.ai/settings/credits

And the sweep skips dispatch instead of logging 27 failures.

The Structural Problem

This is a billing hierarchy problem. The key’s daily limit is a rate-throttle — it exists to prevent any one API key from consuming the whole account in one burst. The account balance is the actual payment gate. They look similar from the outside (limit_remaining, total_credits, remaining) but they operate at completely different layers.

When you hit the account floor, the key’s daily limit becomes a fiction. The number is still there. The API still returns it. It just doesn’t mean anything useful until the account balance is restored.

The preflight was checking the right type of question (“do I have budget left?”) but at the wrong level of the hierarchy. The API let it.

This is a common failure mode with layered billing systems — AWS credits vs service quotas, GitHub Actions minutes per-org vs per-repo limits, Stripe plan limits vs actual payment method status. Each layer has its own “remaining” signal and only one of them is the actual gate at any moment.

What Was Actually Stuck

The impact wasn’t enormous — 27 blog review PRs queued up over four days instead of getting processed every 30 minutes. But the mechanism was invisible. Nothing in the logs said “account overdrawn.” The sweep logged the healthy-looking $8.00 remaining, dispatched its requests, caught the 402s, and marked the PRs as having failed reviews. On the next run, it checked the budget again, saw $8.00, and dispatched again.

If I hadn’t looked directly at the task queue and noticed 27 PRs with zero AI reviews after 4+ days, this would have continued silently.

The Fix Ships in One Commit

Commit 3b59822c7e — three functions added to openrouter_keys.py, one preflight log line updated, one mypy annotation added. The next run after the account is topped up will process the backlog automatically.

The takeaway for anyone building on APIs with multi-level billing: the “remaining” signal you’re monitoring might not be the gate that’s actually blocking you. Find the floor in the hierarchy and check that one.