Skip to content
UsageRail
How it works / Quotas

Quotas and leases

A quota is how many units a customer can use in a period, such as 1,000 requests a month on a free plan. The SDK enforces it with leases: UsageRail gives each of your servers a slice of what the customer has left, and the server counts requests against its slice, so most requests are checked without a network call.

http
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 1036800

{"error":"quota_exceeded","retry_after":1036800}

Setting a quota

A quota belongs to a plan. Under Plans, set Included units per period, then choose what happens when a customer uses up the included units:

ChoiceAt the limitCan you use it today?
Stop their requestsA 429 quota_exceeded until the next period starts. Retry-After is the seconds left in the period.Yes. The usual choice for free plans.
No limitNever blocked. Usage is still counted.Yes.
Bill the overageNever blocked. Usage beyond the included units is billed at the plan's overage price.Not yet: it needs billing through Stripe, which isn't live.
Use prepaid creditsA 402 insufficient_credits once the balance runs out.Not yet: prepaid credits aren't live.

A unit is one billable request unless the route has a weight or the app counts units with track(); see Express. Responses with a 4xx or 5xx status and refused requests use no quota. With Express, Fastify and FastAPI, neither does a request the client abandons before the response starts; Hono, Flask and Django count those by their status.

Periods

For now, every customer's period is a calendar month in the app's time zone, which you'll find under Settings. Once billing through Stripe is live, customers with a subscription will follow their billing period instead.

A request counts toward the period that contains the end of the minute it started in. You can't change the time zone once the app has recorded usage, because the period boundaries would no longer line up with what has already been counted.

How leases work

  1. The first time a server sees a customer whose plan stops at the limit, it asks UsageRail for a lease and the request waits for it, at most 300 ms.
  2. A lease is a number of units that server may hand out on its own. It starts small, 10 units or one request's reservation if that's larger, and grows with the customer's traffic on that server, up to a tenth of the quota or one reservation, whichever is larger. It's never more than what the customer has left.
  3. Before your handler runs, each request reserves its units from the lease. When the request ends, it settles: unused units go back, and a request that turned out not to be billable gives everything back.
  4. When 70% of a lease is used, or when it's 45 seconds old, the server renews it in the background, so a busy customer doesn't wait.
  5. A server stops using a lease after 60 seconds. UsageRail settles a lease when the server reports it closed, or after 30 minutes if the server is gone, and puts any unused units back.

UsageRail never hands out more than the customer has left, counting the leases that are still out, so two servers can't hand out the same units.

When nothing is left, the request gets a 429 quota_exceeded. The server remembers that for 10 seconds before asking again, because another server may still return unused units.

Reservations and units

With route weights, a request reserves and uses the route's weight. When the app counts units with track(), a request reserves the larger of the route's weight and its "Reserve with track()" value, both under Settings > Route weights, and then uses whatever number it passes to track(). Routes you haven't listed reserve 1.

If a request uses more than it reserved, the difference still counts. It's taken from the lease afterwards, and the customer's next requests wait until it's covered. Set the reservation to the most units one request can use, and under-reserving can't take a customer over the quota.

When a customer can go over

A customer can use more than the quota in these cases:

  • A request uses more than it reserved. The extra counts in full, but it can take the customer past the limit.
  • Right after a change to the customer's terms. For about 20 seconds, servers can still use leases from before the change.
  • A server is killed outright (kill -9, out of memory). Usage it hadn't reported is lost and never billed. After 30 minutes UsageRail settles that server's leases as if those units were never used and hands them out again, so the customer can go over by the usage that server hadn't reported: requests that finished after its last successful report, plus any still running when it died. Reports go out every 60 seconds, so for short requests that's normally up to a minute of their traffic on that server.
  • A request runs for more than 29 minutes. Its lease may have been settled already; its usage still counts when it ends.
  • UsageRail can't be reached and you chose to let requests through; see below.

Changes to a customer's terms

Moving a customer to another plan, or the start of a new period, raises the customer's quota version. Every SDK drops the customer's old leases at its next sync and takes new ones, so the new terms apply everywhere within about 20 seconds. Requests already in progress settle against the lease they started with.

When UsageRail is unreachable

If a server needs a lease and UsageRail doesn't answer within 300 ms, the setting under Settings > When UsageRail can't be reached decides:

  • Let requests through, the default. The request is served and counted, so a customer can go over the quota for as long as the outage lasts. Once UsageRail is reachable again, the counts are added to the quota; counts the SDK held for more than 10 minutes are dropped instead and never billed.
  • Refuse them (503). The request gets a 503 quota_unavailable.

After a failed lease request, the server applies this setting straight away for the next 5 seconds instead of waiting again. To set it in code, pass onQuotaUnavailable: "allow" or "deny" to the middleware.