I was validating a support bot against a pool of five shared Claude subscription accounts, all sitting behind a load balancer — a thing that spreads requests across the accounts so no single one gets hammered. Requests started coming back with a 429, the HTTP status code that means “too many requests, slow down.” My first thought was the obvious one: I’d drained the accounts.
Here’s the thing a non-engineer needs to know first: a Claude subscription has usage windows, like a phone plan that resets its data every so often — in this case a 5-hour bucket. Once you’ve spent the bucket, everything fails until it refills. That’s a quota problem, and it’s cumulative. It builds up over time and then the door slams for everyone.
So I did the dumb, thorough thing. I fired off about twenty probe calls across all five accounts to “confirm” the windows were drained. Which, in hindsight, is embarrassing — I was spending the exact shared resource I was worried about running out of, just to prove it was running out. If your hypothesis needs a twenty-call sweep to confirm it, the hypothesis is probably wrong.
Because then I saw the thing that broke my whole story.
A small request and a large request, to the same account, seconds apart. The small one came back 200 — success. The large one came back 429. Same account. Same 5-hour window. Same second, basically.
A drained window can’t do that. If the bucket were empty, both calls fail — the small one too. The account doesn’t check your request and go “ah, this little one’s fine, but that big one, no.” Quota doesn’t care about size. It cares about how much you’ve already spent.
Which meant the 429 wasn’t about quota at all. It was about the shape of the request. The large payload was tripping something structural on the raw Messages API path we were using with subscription OAuth — a hard limit on request size, not a drained budget. Deterministic, not cumulative. It would fail that big request the first time and the thousandth time, fresh window or not.
That’s the whole tell, and it’s the part worth stealing: same instant, small OK and large 429 means the failure is structural — it’s about shape or size, and it’s deterministic. Genuine quota exhaustion is the opposite: it accumulates over time and kills everything equally, big and small alike.
How to tell structural from cumulative in two calls give me the detail
The diagnostic is one back-to-back pair against a single account — no sweep. Vary only the payload size, hold everything else constant:
# tiny request — a few tokens
curl -s -o /dev/null -w "%{http_code}\n" \
-H "authorization: Bearer $OAUTH" \
-d '{"model":"claude-...","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}' \
https://api.anthropic.com/v1/messages
# same account, seconds later — large payload
curl -s -o /dev/null -w "%{http_code}\n" \
-H "authorization: Bearer $OAUTH" \
-d @big_payload.json \
https://api.anthropic.com/v1/messages200 then 429 on the same account rules out a drained window — you’d need both to fail. It points at a size/shape limit on that path. 429 on both, and refills-after-wait, is a real cumulative quota. The subscription-OAuth Messages route has different structural limits than the first-party API key path, so a payload that’s fine on one can 429 on the other with a fresh budget.
The next time you catch yourself typing “rate limited” in a bug ticket, run the two-call test before you believe it. Change one variable — size — hold the rest still. If the small one lives while the big one dies in the same breath, stop blaming the quota. You’ve got a structural wall, and no amount of waiting is going to move it.