Securityquotasrate limitsmeteringentitlementsbilling

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.

Follow the failure

Frame the contract

API design starts with a consumer, a design question and a guarantee — never with a URL.

Design question
Is the caller being slowed because the platform needs protecting right now, or stopped because they have consumed what their plan entitles them to — and does the contract distinguish the two?
Consumers
Integrations that must react differently to "wait 7 seconds" versus "you are done until the 1st"; finance and product teams whose pricing is enforced by the quota; and customer success, who field the "why did our integration die at month-end" call.
The promise
Two clearly separated clauses: burst limits with 429 + Retry-After for pacing, and metered quotas with visible consumption, proactive threshold warnings, a distinct exceeded-error, and a documented overage policy — so no consumer is surprised by either.
RequirementConsumersResource ModelStyleContractValidationAuthorizationErrorsIdempotencyPaginationVersioningObservabilityEvolutionTrade-offs

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.

The same "no", two different contracts
DimensionRate limitQuota
QuestionHow fast, right now?How much, this period?
ProtectsPlatform capacity, tenant fairnessThe pricing model, the entitlement
WindowSeconds to minutes, rollingDay or billing month, calendar-anchored
Rejection429 + Retry-After: 7429 with quota_exceeded code (some APIs use 402/403 — pick one and document it)
Correct client recoveryBack off seconds, resume; smooth the burstStop; alert a human; upgrade, buy overage, or wait for reset
ResetContinuous (bucket refills)A dated moment: first of month, midnight UTC — timezone is contract
Comms dutyHeaders on every responseUsage 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.

The quota's visibility surface — what prevents the month-end surprise
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 codequota_exceeded with reset_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_limited vs quota_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.

  1. 1
    Provider → contract: implements a rate limiter and labels the pricing page "1M requests/month" with no meter behind it.
  2. 2
    Finance → provider: asks for enforcement; a hard 100% cutoff ships as a config change, unannounced, mid-quarter.
  3. 3
    Consumer → month 3: a growing integration crosses 1M on the 26th; every request starts failing with the same 429 shape their backoff loop already handles.
  4. 4
    Consumer's SDK → API: backs off and retries dutifully for days — the error said retry, so no human was ever alerted to upgrade.
  5. 5
    Customer → 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.
What breaks
  • 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.

Design the contract
  • • 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.
Observe in production
  • • 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.
Evolve without breaking
  • • 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.
What it costs
  • • 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.

Misconceptions

Claim
“A quota is just a rate limit with a longer window.”
Reality
The window difference changes everything downstream: recovery (seconds vs a billing conversation), reset semantics (rolling vs calendar), visibility duties (headers vs meters and warnings), and who fixes it (the retry loop vs a human with a credit card). Implementing one mechanism for both breaks both.
Claim
“Enforcement at 100% is a billing detail, not API design.”
Reality
It is the consumer's most catastrophic failure mode of the whole surface — total loss of service, unfixable by code. The error shape, the warnings that precede it, and the documented behavior are contract clauses that determine whether month-end is an email or an outage.
Claim
“Consumers watch their dashboards, so threshold warnings are redundant.”
Reality
The integration was written in week one and has run unattended since; nobody watches a dashboard for a system that has never failed. Push notifications at 80/90% exist because the person who can upgrade the plan is never the process consuming the quota.