429 enforced_spend_limit_reached: why retrying never works
A 429 with no retry-after can be a monthly spend cap, not a rate limit. What Anthropic's page says, and how Sume separates 402, 429 rate_limited and queue_full.

A 429 with no retry-after header can be a spend cap rather than a rate limit. Anthropic's rate-limit page says that once an organization reaches its monthly cap, requests return HTTP 429 with error_code enforced_spend_limit_reached, and retrying, including SDK automatic retries, fails until access resumes. Sume reports an empty balance as 402 insufficient_credits instead.
Anthropic facts are from its Rate limits page (cited under Sources) and Sume facts from Errors and rate limits, both read 2026-09-30.
How do I tell a spend cap from a rate limit?
Look for the header and the code. Anthropic's page lists the monthly caps by tier (Start $500 USD, Build $1,000 USD, Scale $200,000 USD) and says the cap response has no retry-after header, while an ordinary rate-limit 429 carries one.
| Situation | Status and code | Retry-after |
|---|---|---|
| Anthropic: rate limit exceeded | 429 | Sent |
| Anthropic: monthly spend cap reached | 429, enforced_spend_limit_reached | Not sent |
| Sume: request rate exceeded | 429 rate_limited | Use it when present |
| Sume: queue capacity full | 429 queue_full | Use it when present |
| Sume: balance too low | 402 insufficient_credits | Not a wait |
What does Sume return when the money runs out?
Sume returns 402 insufficient_credits before provider work starts, when it cannot reserve the estimated cost from the workspace balance. The docs list the next action as upgrading the plan, waiting for included Gen$, or submitting a cheaper request, and the quota job error category says to add funds or lower the request cost. Waiting does not fix it, so a 402 is not a retry case.
Sume keeps the 429s for two other things: rate_limited for request volume, and queue_full when the workspace has no accepted generation capacity left. Both clear as time passes or jobs finish.
What should a retry loop do with each one?
Retry a 429 only when it carries retry-after, and never retry a 402. For paid submits, reuse the same Idempotency-Key on every retry so a repeat cannot bill twice.
def should_retry(status: int, headers: dict, error_code: str) -> bool:
if status == 402:
return False # insufficient_credits: fund the wallet first
if status == 429:
return "retry-after" in headers # a cap has none
return False
print(should_retry(429, {"retry-after": "12"}, "rate_limited"))
print(should_retry(429, {}, "enforced_spend_limit_reached"))
print(should_retry(402, {}, "insufficient_credits"))What are the limits of this comparison?
The caps and tiers above are Anthropic's on the day read; check its page before relying on them. Sume's docs do not describe a monthly spend cap, only the balance and reservation described in the spend-limit post.
Sources
Related posts
More in Developers
- WAV 16-bit PCM vs 32-bit float: what audio detach returns
Sume audio detach writes wav as 16-bit PCM (pcm_s16le) by default. If your camera records 32-bit float, here is what that means and which options you get.
- Face swap API: quality has no default, unlike avatar videos
Avatar videos default to plus when quality is omitted. Sume's Beta face swap has no default: quality is required and takes standard, plus or max.
- Firecrawl cache: maxAge 2 days vs Sume's fresh: true
Firecrawl scrape caches for 2 days by default (maxAge 172800000 ms). Sume's scrape has a boolean fresh, default false; fresh: true bypasses cached content.
- Firecrawl includePaths regex vs Sume's literal path prefixes
Firecrawl's includePaths are regex on the URL pathname. Sume's include_paths are literal prefixes starting with /, max 10 items. How to rewrite one.
Written by Sume