Quotas vs Rate Limits
A rate limit protects the platform second by second; a quota is an entitlement over a billing period. 100 requests/second and 1M requests/month are different promises with different rejections, resets and communication duties — conflating them breaks both.
Frame the contract
API design starts with a consumer, a design question and a guarantee — never with a URL.
Two promises that only look alike
Both mechanisms say "no" to a request, which tempts teams to implement one and call it both. But they answer different questions. The rate limit answers "how fast may you go *right now*?" — its job is platform protection and tenant fairness, its window is seconds, and its rejection means *pace yourself*: the correct client response is to wait seconds and continue (The Rate-Limit Contract covers that machinery). The quota answers "how much do you get *this period*?" — its job is entitlement and pricing enforcement, its window is a month (or day), and its rejection means *you have consumed your plan*: no amount of waiting seconds fixes it; the fixes are upgrade, overage, or the 1st of the month.
The consumer-side code for the two rejections is completely different, which is why the contract must make them distinguishable mechanically — not by parsing an English message. A retry loop that treats quota-exhaustion like a rate limit will back off and retry for six days, burning error budget and masking the real problem from the humans who could fix it (upgrade the plan). A dashboard that treats a burst 429 like quota exhaustion tells a customer they are out of capacity when they are merely bursty. Same status family, opposite recoveries.
| Dimension | Rate limit | Quota |
|---|---|---|
| Question | How fast, right now? | How much, this period? |
| Protects | Platform capacity, tenant fairness | The pricing model, the entitlement |
| Window | Seconds to minutes, rolling | Day or billing month, calendar-anchored |
| Rejection | 429 + Retry-After: 7 | 429 with quota_exceeded code (some APIs use 402/403 — pick one and document it) |
| Correct client recovery | Back off seconds, resume; smooth the burst | Stop; alert a human; upgrade, buy overage, or wait for reset |
| Reset | Continuous (bucket refills) | A dated moment: first of month, midnight UTC — timezone is contract |
| Comms duty | Headers on every response | Usage API + proactive warnings at 80/90/100% |
Metering is a product surface, not a counter
A quota is only as trustworthy as its visibility. The consumer needs to see consumption *before* the wall: a usage endpoint (GET /usage returning consumed, entitled, reset date, and the breakdown by operation class), usage headers on responses if consumption is per-call, and — the clause that prevents the month-end incident — proactive threshold notifications at 80% and 90%, delivered over Webhooks: The Inverted Contract or email to someone who can act. A quota the consumer discovers by hitting it is a quota designed to generate an outage: the integration that dies at 100% on the 28th was observably going to die there since the 19th, and only the provider's silence made it a surprise.
Metering also has honesty obligations the simple counter hides. What *counts* — do failed requests consume quota? 429s? Webhook deliveries you send to them? (Common answer: successful billable operations only; whatever you choose, write it down, because consumers reconcile your meter against their logs.) How fresh is the meter — distributed counting is usually eventually consistent, and a meter minutes behind means a consumer can overshoot before the quota trips; state the lag and enforce with the same tolerance. And when the plan changes mid-period, does the quota prorate, reset, or stack? Every one of these is a support ticket pre-written; the contract's job is answering them before the meter runs.
GET /v1/usage
→ {
"period": { "start": "2026-08-01", "reset": "2026-09-01T00:00Z" },
"requests": { "used": 812_400, "included": 1_000_000 },
"exports": { "used": 41, "included": 50 },
"meter_freshness_seconds": 120
}
notifications (webhook event `usage.threshold`):
at 80% → integration owner at 90% → + billing contact
at 100% → the documented behavior begins:
hard stop | overage billing | grace-then-stop (pick ONE,
per plan, and say which — this line is the pricing page)What happens at 100% is the real contract
The exceeded-quota behavior is where engineering and pricing meet, and vagueness here is expensive in both directions. Hard stop is predictable and brutal: enforcement is honest, but a monitoring integration that stops mid-incident because it crossed 1M requests is a customer conversation you will lose. Overage billing keeps traffic flowing and converts the wall into a bill — with runaway-cost risk on the consumer side, so it needs spend caps the *consumer* controls, or you have moved the surprise from availability to invoice. Grace-then-stop (soft cap at 100%, hard at 120%) buys time for the upgrade conversation at the cost of teaching persistent over-consumers that the number is soft. Different products legitimately choose differently — often per plan tier, hard-stopping free plans and billing overage on paid. What is not legitimate is not choosing: the at-100% behavior discovered in production is a churn event.
Keep the failure identities separate all the way through the An Error Taxonomy Clients Can Branch On: rate_limited (retryable in seconds, machine recovery) versus quota_exceeded (not retryable this period, human recovery — with reset_at and an upgrade link in the error body). And because quota trouble is *predictable* in a way burst trouble is not, the evolution duties differ too: quota changes are pricing changes — lowering an entitlement or starting to count something that was free is a commercial breaking change that needs notice measured in billing cycles, not deploy windows. The meter, the thresholds, the reset date and the at-100% behavior together *are* the quota; the counter is the least of it.
- Distinct error code —
quota_exceededwithreset_at, current plan, and the upgrade/overage path; never reuse the burst-limit shape. - Consumer-controlled caps — with overage billing, spend limits belong to the consumer, or the surprise moves from outage to invoice.
- Reset semantics in writing — calendar-anchored, timezone stated; "monthly" without a timezone is a support ticket generator.
- Plan-change rules — prorate, reset or stack, decided and documented before the first mid-period upgrade asks.
- Quota changes = pricing changes — notice in billing cycles; consumers built cost models on the old numbers.
Key points
- Rate limits protect the platform over seconds; quotas enforce entitlements over billing periods — same "no", opposite client recoveries.
- Make the two mechanically distinguishable (
rate_limitedvsquota_exceeded), or client retry loops will wait out a quota for days while nobody upgrades the plan. - A quota needs a visibility surface: usage API, meter freshness stated, and proactive 80/90% warnings — a quota discovered at 100% is a designed outage.
- Define what counts against the meter (failed calls? 429s?) and its consistency lag; consumers reconcile your meter against their logs.
- The at-100% behavior — hard stop, overage billing, grace — is the real pricing contract; choose per tier, document it, and give consumers spend caps when overage bills.
- Quota changes are pricing changes: notice in billing cycles, not deploy windows.
Follow the failure
How the contract fails or gets misused, hop by hop — and what it costs when it completes.
- 1Provider → contract: implements a rate limiter and labels the pricing page "1M requests/month" with no meter behind it.
- 2Finance → provider: asks for enforcement; a hard 100% cutoff ships as a config change, unannounced, mid-quarter.
- 3Consumer → month 3: a growing integration crosses 1M on the 26th; every request starts failing with the same 429 shape their backoff loop already handles.
- 4Consumer's SDK → API: backs off and retries dutifully for days — the error said retry, so no human was ever alerted to upgrade.
- 5Customer → churn: the outage postmortem reads "our vendor cut us off with no warning and their error told our code to keep retrying"; both sentences are true.
- Month-end integration outages: consumers hit an invisible wall at their busiest, with recovery gated on a sales conversation instead of a header.
- Retry loops that misread quota exhaustion as burst throttling mask the failure from humans for days while burning the provider's gateway capacity.
- Unbounded overage or ambiguous meters convert the failure into surprise invoices and reconciliation disputes — billing-grade trust damage from an engineering shortcut.
Design, observe, evolve
A contract decision is incomplete until you know how you would notice it failing and how it changes later.
- • Separate the mechanisms end to end: distinct error codes, distinct headers, distinct docs sections — burst pacing and entitlement are different clauses.
- • Ship the usage API and 80/90/100% webhook notifications with the quota, not after the first incident; the meter's freshness and counting rules go in the docs.
- • Choose the at-100% behavior per plan tier deliberately (hard stop, overage with consumer-set caps, or bounded grace) and put it on the pricing page.
- • Anchor resets to a stated timezone and define plan-change proration before launch.
- • Consumers approaching quota (>80%) with no upgrade activity: the churn-risk list and the outreach trigger, one query.
- • Retry patterns following `quota_exceeded`: clients that keep hammering are misclassifying the error — fix the SDK or the error shape before month-end.
- • Meter drift: reconcile the billing counter against request logs continuously; a meter that disagrees with what you invoice is an incident in the making.
- • Raising quotas or adding meter visibility is additive; lowering entitlements or newly counting a free operation is a commercial breaking change — billing-cycle notice, grandfathering decided explicitly.
- • Moving from unenforced ("soft") quotas to enforcement runs observe-first: months of warning notifications on real usage before the first request is refused.
- • New metered dimensions (compute-seconds alongside request counts) ship in the usage API and on the pricing page simultaneously — a meter consumers cannot see is a bill they will dispute.
- • Real metering is a distributed-counting system with freshness, durability and reconciliation duties — dramatically more machinery than a token bucket, funded by billing accuracy rather than uptime.
- • Proactive warnings and self-serve caps reduce surprise outages and also reduce accidental overage revenue; the trustworthy contract is sometimes the less lucrative one this quarter.
- • Hard stops are operationally simple and commercially harsh; grace and overage are gentler and manufacture edge cases (stacking, proration, cap races) that support inherits forever.