---
title: "Rate Limiting"
description:
  "Per-IP, per-API-key, per-tenant, and per-token rate limits at the edge — plus
  monthly quotas. Programmable in TypeScript, configurable in JSON, enforced
  before traffic touches your origin."
canonicalUrl: "https://zuplo.com/features/rate-limiting"
sourceUrl: "https://zuplo.com/features/rate-limiting"
pageType: "feature"
generatedAt: "2026-08-03"
---

# Rate Limiting

> Spike protection, per-tier limits, and monthly quotas enforced at the edge —
> before traffic reaches your origin, with no Redis to run. Zuplo lets you rate
> limit by IP, API key, tenant, or token, and write the rule itself in
> TypeScript.

One abusive client or runaway script shouldn't be able to take down your API or
drive up your origin costs. Enforce limits by IP, key, tenant, or token at the
edge — before the traffic reaches your origin, with no Redis to run.

## Why this matters

Rate limits are the cheapest insurance you can buy. One scraper, one runaway
script, one curious AI agent away from a P0 — and the gateway is the only place
that can stop it before your origin pays the price. The hard part isn't deciding
to throttle; it's deciding fairly, per consumer, per resource, with state that
actually scales.

- **One bad client takes the API down** — A misconfigured cron loop, a runaway
  notebook, an enthusiastic AI agent — the gateway has no idea, your origin
  slows, and every other customer feels it.
- **Free tier eating your margin** — Limits exist in the docs, not in the
  gateway. Your highest-volume users are also your unpaid users. Every
  conversion conversation starts with "please throttle me."
- **Token costs no one can predict** — Your AI assistant charges by tokens; your
  rate limit counts requests. One "summarize this PDF" call costs as much as a
  thousand smaller ones, and you have no way to bill or block.
- **Redis to operate just to throttle** — Distributed counters need shared
  state. So you stand up Redis, a sentinel cluster, replication, and on-call
  rotations — for what should be a feature flag on the gateway.

## What you get

Throttling that fits how your business actually charges:

- **Spike protection in one config block** — Drop `rate-limit-inbound` on a
  route, set requests-per-minute, ship. The gateway absorbs spikes, your origin
  stays calm, and 429s come with a `retry-after` header your clients already
  know how to handle.
- **Throttle by anything you have data on** — Per-customer, per-plan,
  per-tenant, per-region, per-time-of-day — or any combination. The limit is
  yours to define against any attribute on the request, the API key, or your
  business data.
- **Charge usage by what actually costs you** — Most rate limits count one
  request as one unit — but a 5-token chat call doesn't cost the same as a
  5,000-token one. Throttle on tokens, compute, downstream calls, or anything
  else, with each request charging the budget proportional to its real cost.

## Per-consumer tiers: different limits for free, pro, and enterprise — automatically

Most rate-limiting tools box you into a fixed set of dimensions and dropdowns.
Zuplo lets you write the rule itself in TypeScript — full access to the API key,
request, and your own data — so the limit fits the way your business actually
charges. Tier overrides, geo overrides, time-of-day rules, customer-by-customer
carve-outs: all real code, all running at the edge.

```typescript
import {
  CustomRateLimitDetails,
  ZuploContext,
  ZuploRequest,
} from "@zuplo/runtime";

export function rateLimit(
  request: ZuploRequest,
  context: ZuploContext,
): CustomRateLimitDetails | undefined {
  const plan = request.user?.data?.plan ?? "free";
  if (plan === "enterprise") {
    return {
      key: request.user.sub,
      requestsAllowed: 6000,
      timeWindowMinutes: 1,
    };
  }
  if (plan === "pro") {
    return {
      key: request.user.sub,
      requestsAllowed: 600,
      timeWindowMinutes: 1,
    };
  }
  return { key: request.user.sub, requestsAllowed: 60, timeWindowMinutes: 1 };
}
```

Included capabilities:

- `rateLimitBy`: ip / user / function / all
- Per-tier limits via TypeScript
- Hot-tier promotion without redeploy
- strict / async modes
- `retry-after` header on 429
- Custom 429 response body

Related: [Rate Limiting docs](https://zuplo.com/docs/concepts/rate-limiting) ·
[Dynamic Tiers example](https://zuplo.com/examples/dynamic-rate-limits)

## Long-window quotas: monthly caps and multi-meter accounting, in one policy

`complex-rate-limit-inbound` lets one request burn from multiple counters —
tokens, compute units, downstream calls — at amounts you decide. `quota-inbound`
stretches the window from minutes to months for hourly, daily, weekly, and
monthly meters. Both run inside the same edge pipeline as your rate limits.

```typescript
// Inside your route handler
import { ComplexRateLimitInboundPolicy } from "@zuplo/runtime";

const tokens = countTokens(prompt);
ComplexRateLimitInboundPolicy.setIncrements(context, {
  tokens, // 1500 tokens charged to user's bucket
  requests: 1,
});
```

```json
{
  "policyType": "quota-inbound",
  "handler": {
    "export": "QuotaInboundPolicy",
    "module": "$import(@zuplo/runtime)",
    "options": {
      "period": "monthly",
      "quotaBy": "user",
      "allowances": { "requests": 10000, "tokens": 5000000 },
      "quotaAnchorMode": "first-api-call"
    }
  }
}
```

Included capabilities:

- Monthly · weekly · daily · hourly windows
- Named meters (tokens, exports, calls)
- `setIncrements` per request
- Anchor to first call or billing date
- `getUsage()` for portal display
- Composable with rate limits

Related: [Rate Limiting docs](https://zuplo.com/docs/concepts/rate-limiting) ·
[Rate Limiting Quickstart](https://zuplo.com/docs/articles/step-2-add-rate-limiting)

## What makes Zuplo different

Most gateways stop at "requests per minute per IP." Real products need per-plan,
per-token, per-org, per-something-only-you-know.

- **The rule is yours to write** — Most gateways limit you to whatever
  dimensions their dropdowns offer. Zuplo lets you write the rate-limit rule in
  TypeScript with full access to the request, the API key, and any data you have
  — so a tier promotion, a geo carve-out, or a one-off enterprise contract is a
  few lines of code, not a support ticket.
- **One pipeline, every kind of limit** — Per-second spikes, multi-dimension
  counters that bill tokens or compute units, monthly contractual quotas — they
  all attach the same way, share the same auth context, and stack on the same
  route. Stop running rate limits in one product and quotas in another.
- **No Redis, no operations** — Counter state replicates across the edge
  automatically. Nothing to provision, no maxmemory eviction policy to tune, no
  version mismatch between the gateway and a separate cache cluster.
- **Token-aware for AI workloads** — `setIncrements` lets one request consume
  multiple units from a counter. "Charge 1500 tokens to the user's monthly quota
  and 1 request to their per-minute bucket" — one policy block, one source of
  truth.

## What teams use this for

**"We need a free tier with strict limits and a paid tier with generous ones."**
Read the customer's plan from their API key metadata and return the matching
limit — strict for free, generous for paid, custom for enterprise. New plan? Add
a branch. New customer? They inherit their tier's bucket the moment they hit the
gateway.

**"Our LLM costs are out of control because one prompt = many tokens."** Charge
each request to a token budget instead of a request count. From your handler you
tell the gateway how many tokens this request actually used; cheap prompts cost
little, expensive ones cost a lot, and the throttle reflects real cost.

**"I need a 1,000-call monthly quota that resets on the customer's billing
date."** Set a monthly quota and anchor the reset to each customer's billing day
with a custom function. Surface remaining balance in your developer portal so
customers always know where they stand before the next invoice.

**"Block scrapers but don't slow down legitimate traffic."** Stack two limits on
the same route: a tight per-IP burst limit catches anonymous scrapers, a
generous per-API-key limit handles authenticated traffic. Whichever one a
request hits first returns a 429 — your real customers never feel it.

## FAQ

**How do I rate limit an API?** Rate limiting caps the number of requests a
client can make in a time window — protecting your origin from abuse and keeping
costs predictable. The cleanest place to enforce it is at the gateway, before
traffic hits your backend. Zuplo lets you rate limit by IP, API key, customer
tier, tenant, or any custom attribute you attach to a consumer; per-second
bursts and monthly quotas enforce both abuse prevention and billing-aligned
caps.

**Can I set different rate limits per API key or per customer?** Yes.
Per-API-key rate limits are the default: each key gets its own bucket so one
customer's burst doesn't affect another. To vary the limit per tier, store the
plan on the key's metadata at issuance and your TypeScript rate-limit policy
reads it on every request — free customers get strict limits, paid customers get
generous ones, enterprise gets custom. Promote a customer mid-month and the new
limit applies on the next request.

**How do I rate limit AI / LLM API calls by tokens?** Counting requests doesn't
reflect AI cost — a 5-token call shouldn't burn the same budget as a 5,000-token
one. Zuplo lets you rate limit by tokens (or any custom unit your backend
reports). Your handler tells the gateway how many tokens this request actually
consumed, and the throttle reflects real cost. Combine with monthly token quotas
to enforce contracted budgets per customer.

**What's the difference between rate limits and quotas?** Rate limits cap
short-term burst (e.g. 100 requests/minute per IP) — they protect your origin
from abuse. Quotas enforce long-term volume (e.g. 1 million calls per month per
customer) — they back contracted plan tiers. Most production APIs need both.
Zuplo lets you stack them on the same route: rate limits for abuse prevention,
quotas for monetization and contractual caps, with monthly billing-cycle
alignment built in.

**How do I show customers their remaining quota?** Surface it in the developer
portal so customers self-serve. Zuplo's portal includes a usage dashboard out of
the box — current-period consumption per metered feature, progress bars to the
cap, projected month-end usage. Customers see where they stand before the
invoice arrives, debug their own scripts when they spike, and can upgrade
themselves through the portal when they need more capacity.

**What happens when a client hits the rate limit?** The gateway returns 429 Too
Many Requests with a retry-after header so well-behaved clients back off
automatically. You can customize the response body, log the rejection for
analytics, or fan out to a webhook for alerting. For monetization-aligned
overage, switch to soft limits and let traffic through while billing the excess
at your rate-card price.

**Where does the rate-limit counter state live?** In Zuplo's globally replicated
edge runtime — every gateway instance across 300+ data centers sees the same
counter. There's no Redis cluster to operate, no memcached to scale, no race
conditions to debug. The classic pain of rolling-your-own rate limiter
(in-memory counters that scale wrong, central databases that add latency) goes
away entirely.

**What's the best API gateway for rate limiting?** Look for:
rate-limit-by-anything (IP, API key, tenant, custom attribute), monthly quotas
with billing-cycle alignment, programmable rules in code, edge enforcement so
latency stays low, and customer-facing usage visibility. Most gateways stop at
"requests per minute per IP." Zuplo lets you rate limit by tokens, GB, compute
units, or any unit your business cares about — and shows customers their burn in
the developer portal.

## Next steps

- Start a free Zuplo project: https://portal.zuplo.com/signup
- Read the [Rate Limiting docs](https://zuplo.com/docs/concepts/rate-limiting)
- Deploy the
  [Dynamic Tiers example](https://zuplo.com/examples/dynamic-rate-limits)
- Talk to an expert: /schedule-call
