← all writing
Tech By Jesse Moraga · Sep 15, 2026 · 2 min read

Rate Limits That Don't Punish Your Best Customer: 429, Retry-After, Per-Tenant Quotas

One noisy tenant should hit the wall alone, and the wall should tell them exactly when to come back.

Global rate limit, one counter, all callers in the same bucket. Someone writes a loop with no backoff and suddenly the account that actually pays you is getting turned away at the door. That's not protection. That's a shared punishment.

  GLOBAL BUCKET (the trap)
  tenant A (runaway loop) ████████████████████  → 429
  tenant B (your best customer) █               → 429  ouch
  tenant C (quiet)  ▌                           → 429  ouch

  PER-TENANT BUCKETS (what you want)
  tenant A ████████████████████ | LIMIT | → 429 + Retry-After: 30
  tenant B █                    |       | → 200
  tenant C ▌                    |       | → 200

Look at tenant B in both diagrams. Same traffic, same second, different answer. The only thing that changed is where the counter lives.

Here's the part people skip. A 429 with no Retry-After header is just a slammed door, and the caller's only sane move is to guess, which usually means retry immediately, which means you get hammered harder the moment you start defending yourself. The status code and the header are one unit. 429 Too Many Requests was defined in RFC 6585 alongside the other additional HTTP status codes, and the spec is explicit that the response may include a Retry-After telling the client how long to wait. That's the whole idea. You're not reporting a failure, you're issuing a scheduling instruction.

I run the back office for my own field-services company, and the API in the middle of it gets called by a phone assistant, an intake flow, follow-up jobs, and field updates from a truck. Different shapes of traffic entirely. The follow-up worker is happy to be told "wait thirty seconds" because it's a queue and it has nowhere to be. The phone assistant is not, because there's a human breathing on the line. So the counters are per tenant and per route class, and the slow batch stuff gets throttled hard while the interactive path keeps its own room. I'm not the type to fix that with a bigger server.

The one opinion I'll pick a fight over: silently slowing a caller down instead of returning 429 is worse than rejecting them. Quiet queuing feels polite. It isn't. It turns your rate limit into a mystery timeout, the client retries on top of the request that's still in flight, and now you're doing the work twice and nobody logged why. Say no out loud. Say when.

And log the 429s per tenant, honestly, because a tenant hitting the wall constantly is telling you something about your own docs or your own limit, and I've changed the limit more than once after looking at that.

A 429 without Retry-After is a tantrum. A 429 with Retry-After is a contract.

Jesse

growth-as-a-service

I build with AI so small businesses can take on the giants. Let me build yours.

Visit Art3ry → art3ry.com