headroomlabs-ai/headroom · error · HTTPException

Budget exceeded for {budget_period} period

Error message

Budget exceeded for {budget_period} period

What it means

After rate limiting, the Anthropic handler asks the cost tracker whether the period budget still has headroom; when check_budget() returns not-allowed the handler releases the pre-upstream semaphore and returns 429 with a detail string built by cost_tracker.budget_denial_detail() — which names the budget period, spend breakdown, and whether enforcement was blocked because part of the spend was booked from Headroom's own token estimate (when the provider returned no usage data). This is a spend-control refusal, not throttling: retrying immediately will keep failing.

Source

Thrown at headroom/proxy/handlers/anthropic.py:1053

                    await self.metrics.record_rate_limited(provider=provider_name)
                    # Unit 4: release the pre-upstream semaphore before we
                    # bail out of the handler via HTTPException — FastAPI's
                    # exception handler will NOT run our ``finally``.
                    await _finalize_pre_upstream()
                    raise HTTPException(
                        status_code=429,
                        detail=f"Rate limited. Retry after {wait_seconds:.1f}s",
                        headers={"Retry-After": str(int(wait_seconds) + 1)},
                    )

            # Budget check
            if self.cost_tracker:
                allowed, remaining = self.cost_tracker.check_budget()
                if not allowed:
                    # Unit 4: release the pre-upstream semaphore before we
                    # bail out of the handler via HTTPException.
                    await _finalize_pre_upstream()
                    raise HTTPException(
                        status_code=429,
                        detail=self.cost_tracker.budget_denial_detail(),
                    )

            # Memory: Get user ID when memory is enabled (fallback to "default" for simple DevEx).
            # Reads `request.headers` directly because the local `headers` dict was
            # stripped of `x-headroom-*` above for the upstream-bound copy (PR-A5).
            memory_user_id: str | None = None
            memory_request_ctx = None
            if self.memory_handler:
                memory_user_id = request.headers.get(
                    "x-headroom-user-id",
                    os.environ.get("USER", os.environ.get("USERNAME", "default")),
                )
                # Per-project memory routing (GH #462). Build the context
                # once here so save / search / inject all resolve against
                # the same workspace. Tier order: explicit project-id /
                # cwd headers → CLI override → system prompt env block.

View on GitHub (pinned to 322425c43b)

Solutions

  1. Read the full denial detail — if it says enforcement was blocked on estimated cost, fix usage reporting (or relax the basis policy) rather than raising the budget.
  2. Raise the period budget limit if the spend is legitimate.
  3. Wait for the period to roll over (daily budgets reset at the period boundary), or split the key/workload across budgets.

Example fix

# before
budget_limit_usd = 5.0  # agent burns through it mid-run -> 429

# after
budget_limit_usd = 25.0  # sized to actual token volume; or reset at period boundary
Defensive patterns

Strategy: fallback

Validate before calling

# Before long runs, query the cost tracker's remaining budget if exposed:
# allowed, remaining = cost_tracker.check_budget()
# skip or degrade (smaller model, less context) when remaining < estimated_request_cost

Try / catch

resp = await client.post("/v1/messages", json=body)
if resp.status_code == 429 and "Budget" in resp.json()["detail"]:
    notify_operator(f"budget exhausted: {resp.json()['detail']}")
    switch_to_budgeted_fallback_model()  # or pause the batch until period reset

Prevention

When it happens

Trigger: The configured spend limit for the current budget period (e.g. daily/monthly) has been reached or exceeded, and a new /v1/messages request arrives; or the budget-basis policy blocks when a request's cost can only be estimated because the provider returned no usage.

Common situations: Long-running agents exhausting a daily cap mid-run; a budget set too low after a model-price change; estimated-cost booking (missing usage in provider responses) pushing measured spend to the limit under a strict basis policy; forgotten budget config left from a test.

Related errors


AI-assisted analysis of headroomlabs-ai/headroom@322425c43b (2026-08-15). Data as JSON: /api/errors/fcc84ad1b8cec910. Report an issue: GitHub.