●CONNECTORS — Managed MCP connectors gain connector observability in public beta for adoption, errors, latency, and usage●DIRECTORY — Admins can now submit MCP connectors to the directory directly from Claude●SLACK — Team and Enterprise users can tag Claude in Slack to delegate tasks●ENTERPRISE — Claude Enterprise adds richer admin analytics, model-level entitlements, and spend alerts●MODEL — Claude Sonnet 5 is the default across all plans at $2/$10 per million tokens through August 31●LIMITS — Claude Code weekly usage limits are up 50% through July 13; Claude Science applications close July 15●CONNECTORS — Managed MCP connectors gain connector observability in public beta for adoption, errors, latency, and usage●DIRECTORY — Admins can now submit MCP connectors to the directory directly from Claude●SLACK — Team and Enterprise users can tag Claude in Slack to delegate tasks●ENTERPRISE — Claude Enterprise adds richer admin analytics, model-level entitlements, and spend alerts●MODEL — Claude Sonnet 5 is the default across all plans at $2/$10 per million tokens through August 31●LIMITS — Claude Code weekly usage limits are up 50% through July 13; Claude Science applications close July 15
When Claude API Suddenly Starts Returning 429 in Production — Field Notes on Measuring Rate-Limit Headroom from Headers and Throttling Before You Run Dry
Scrambling to add retries after a 429 is always a step behind. Claude API writes how much you have left into every response header. These are field notes on measuring that headroom continuously and throttling yourself before it runs out.
The 429 looked like it came out of nowhere, but it had been announced all along.
One night a batch ran, and my production workers started returning 429 Too Many Requests in a stream. My first move was the obvious one: add exponential backoff, raise the retry count. By morning the errors were down — but throughput had backed up, overall latency had grown, and the monthly token bill had jumped. Retries had only deferred the problem; they never touched the root.
What I'd missed was that Claude API tells you, on every response, how much you have left to send. Reacting after a 429 is always a step behind. These are field notes on measuring rate-limit headroom on every request and throttling my own sending before it runs dry. It happened on a small stack I run as an indie developer, but the reasoning doesn't depend on scale.
Why a design that waits for 429 stays behind
There are two stances toward rate limits: reactive (wait once a 429 arrives) and pre-emptive (slow yourself as headroom thins). Most implementations start reactive. Mine did too.
The weakness of reactive handling is that a 429 is a "too late" signal. The instant you hit the limit, every parallel request in flight at that moment gets rejected together. Backoff can rescue the individual requests, but everyone who crowded the same limit window waits, and total throughput drops in steps. Worse, retries consume the same tokens two and three times over, pushing the bill up rather than down.
Pre-emptive handling adjusts your own send rate before you reach the limit. What it needs is a way to know how close you already are. That's already in your hands.
Headroom is written into every response header
Claude API returns rate-limit state in headers on 200 responses, not just errors. The ones I watch are the remaining amounts and the reset time.
Header
Meaning
Use
anthropic-ratelimit-requests-remaining
Requests you can still send this window
Count-based headroom
anthropic-ratelimit-tokens-remaining
Approx. input tokens you can still send
Token-based headroom (usually runs dry first)
anthropic-ratelimit-tokens-reset
When the token allowance recovers (ISO)
Estimating how long to throttle
retry-after
Seconds to wait on a 429
The reactive last resort
The key point is that these ride on successful responses, not only on 429s. So you can observe headroom during calm periods. I chose to convert this into a headroom ratio rather than a raw remaining count. Expressed as the remaining fraction of the ceiling, it compares across tenants with different quota sizes on the same yardstick.
// src/lib/rateLimitMeter.ts// Compute a headroom ratio from response headers and detect pressure.type Headroom = { requestsRemaining: number; tokensRemaining: number; tokensReset: number | null; // epoch ms tokensHeadroom: number; // 0.0 (empty) to 1.0 (plenty)};// Set the ceiling from your org's plan (or seed it with the max remaining you observe).const TOKEN_LIMIT = Number(process.env.ANTHROPIC_TOKEN_LIMIT ?? 200000);export function readHeadroom(headers: Headers): Headroom { const tokensRemaining = Number( headers.get('anthropic-ratelimit-tokens-remaining') ?? TOKEN_LIMIT ); const requestsRemaining = Number( headers.get('anthropic-ratelimit-requests-remaining') ?? 0 ); const resetRaw = headers.get('anthropic-ratelimit-tokens-reset'); return { requestsRemaining, tokensRemaining, tokensReset: resetRaw ? new Date(resetRaw).getTime() : null, tokensHeadroom: Math.max(0, Math.min(1, tokensRemaining / TOKEN_LIMIT)), };}
Once you reduce it to a single headroom number, all that remains is deciding how to behave when it thins.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦A lightweight hook that computes headroom ratio from anthropic-ratelimit-*-remaining headers on every request
✦A pre-emptive throttle that slows the sender the moment headroom drops below a threshold, instead of waiting after exhaustion
✦How to read attribution logs that name which tenant or job ate the headroom, so you can stop the runaway by name
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Throttle the sender once headroom crosses a threshold
The pre-emptive throttle is simple. Read the headroom ratio from each response, and when it drops below a chosen floor, deliberately insert a gap before the next request you send. I made it two-stage: ease off below 30% headroom, and clamp hard below 10%.
// src/lib/preemptiveThrottle.tsimport { readHeadroom } from './rateLimitMeter';let currentDelayMs = 0; // gap inserted on the send sidefunction decideDelay(headroom: number): number { if (headroom < 0.10) return 2000; // pressure: clamp hard if (headroom < 0.30) return 600; // caution: ease off return 0; // plenty: pass through}export async function callWithHeadroomControl( send: () => Promise<Response>, onMeter: (h: ReturnType<typeof readHeadroom>, tenant: string) => void, tenant: string): Promise<Response> { if (currentDelayMs > 0) { await new Promise((r) => setTimeout(r, currentDelayMs)); } const res = await send(); const h = readHeadroom(res.headers); currentDelayMs = decideDelay(h.tokensHeadroom); // apply to the next send onMeter(h, tenant); // to the metering log (pass tenant for attribution) return res;}
This doesn't eliminate 429s entirely. The aim is not to "fall into" a 429 but to slow yourself only while headroom is thin, riding across the limit window. In practice, after adding this pre-emptive throttle, 429s in my environment dropped from a hundred-odd per week to single digits. Processing time grows only under pressure; during calm periods it passes through, so overall latency actually improved.
You don't need to discard reactive backoff. Keep it as the final catch for 429s the pre-emptive layer couldn't prevent. This two-layer thinking overlaps heavily with resilience against server-side overload (529); the separation covered in production resilience patterns for Claude API 529 overload serves directly as the foundation.
Attribute who ate the headroom
The pre-emptive throttle protects the whole, but on its own it can't tell you "why was tonight so tight?" The last thing I added was attribution. For each tenant (or job ID) passed to onMeter, I record token consumption and pressure occurrences.
Tenant / Job
Weekly tokens
Pressure events (headroom <10%)
Note
Scheduled batch A
~4.2M
38
Clustered at night. Fixable by time-spreading
Conversational API B
~1.1M
2
Healthy
Reprocessing job C
~3.8M
51
Retry runaway. Needs idempotency
Only when I saw this table did it become clear that the main cause of pressure was a time-of-day collision between the scheduled batch and the reprocessing job. The fix lived outside the pre-emptive throttle. Just by shifting the batch into a separate night window and stopping the duplicate execution of the reprocessing job, pressure events fell to under a third the next week. The runaway bill turned out to be that reprocessing job double- and triple-consuming tokens.
Rollout — layer one pre-emptive stage over your reactive path
You keep your existing reactive retries and just add one pre-emptive stage in front. The order is this.
Wrap every Claude API call in callWithHeadroomControl(), always passing tenant
Convert each response's tokens-remaining into a headroom ratio via readHeadroom() and stream it to your metering log
Insert send-side waits at the two-stage 30% / 10% thresholds (tune the numbers to your own traffic)
Aggregate the attribution log weekly and identify the tenants/jobs at the top of pressure events
Kill the root causes of pressure (time-of-day collisions, retry runaways) outside the throttle
You don't have to apply it everywhere at once. I'd recommend starting with just the single path that eats the most tokens, watching how 429s fall for a week, then widening. Start the thresholds conservative and loosen them if headroom stays plentiful.
Wrap-up — a limit is something you read ahead of time, not receive after the fact
To recap, I did three things: read the headroom ratio from response headers every time; throttled the sender the moment headroom crossed a threshold; and attributed consumption and pressure to a tenant or job to name the cause.
Thickening retries to make 429s disappear is the most reachable symptomatic fix. But the state of the limit is written into every response before anything fails. The next time you see a 429 in production, before raising the retry count, check the headroom that was sitting right there in the previous response's headers.
I hope this helps with your own operations. Thank you for reading.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.