ZuploZuplo
LoginStart for Free
  • Documentation
  • API Reference
Introduction
Getting Started
    Develop on the web portal
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingDynamic MCP Server - Quickstart
    Develop locally with the CLI
      1 - Setup Your Gateway2 - Rate Limiting3 - API Key Auth4 - Deploy5 - Dynamic Rate LimitingDynamic MCP Server - Quickstart
Concepts
Development
Policies
Handlers
API Keys
Rate Limiting
    Getting startedHow it works
    Policies
    Guides
      Dynamic rate limitingCombining policiesPer-user rate limitsMonitoring & troubleshooting
MCP Server
MCP Gateway
AI Gateway
Developer Portal
Monetization
Deploying & Source Control
Analytics
Observability
Networking & Infrastructure
Account Management
Programming API
Build with AI
Zuplo CLI
Migration Guides
Platform LimitsSecuritySupportTrust & ComplianceChangelog
powered by Zudoku
Guides

Dynamic rate limiting

Static rate limits apply the same threshold to every caller. Dynamic rate limiting lets you determine limits at request time — so premium customers get higher throughput, free-tier users get a lower ceiling, and internal services can bypass limits entirely.

Dynamic rate limiting works with both the Rate Limiting policy and the Complex Rate Limiting policy.

How it works

When you set rateLimitBy to "function", the policy calls a TypeScript function you provide on every request. That function returns a CustomRateLimitDetails object that tells the rate limiter:

  • key — The string used to group requests into buckets (e.g., a user ID or API key consumer name).
  • requestsAllowed (optional) — Overrides the policy's default requestsAllowed for this request.
  • timeWindowMinutes (optional) — Overrides the policy's default timeWindowMinutes for this request.

Returning undefined skips rate limiting for that request entirely.

Create a rate limit function

Create a new module (for example modules/rate-limit.ts) with a function that inspects the request and returns the appropriate limits.

The following example reads a customerType field from the authenticated user's metadata and applies different limits per tier:

modules/rate-limit.ts
import { CustomRateLimitDetails, ZuploContext, ZuploRequest, } from "@zuplo/runtime"; export function rateLimit( request: ZuploRequest, context: ZuploContext, policyName: string, ): CustomRateLimitDetails | undefined { const user = request.user; // Premium customers get 1000 requests per minute if (user.data.customerType === "premium") { return { key: user.sub, requestsAllowed: 1000, timeWindowMinutes: 1, }; } // Free customers get 50 requests per minute if (user.data.customerType === "free") { return { key: user.sub, requestsAllowed: 50, timeWindowMinutes: 1, }; } // Default for any other customer type return { key: user.sub, requestsAllowed: 100, timeWindowMinutes: 1, }; }

When using API key authentication, the user.data object contains the metadata you set when creating the API key consumer. When using JWT authentication, it contains the decoded token claims.

Configure the policy

Wire the function into the rate limiting policy by setting rateLimitBy to "function" and pointing the identifier option at your module:

config/policies.json
{ "name": "my-dynamic-rate-limit-policy", "policyType": "rate-limit-inbound", "handler": { "export": "RateLimitInboundPolicy", "module": "$import(@zuplo/runtime)", "options": { "rateLimitBy": "function", "requestsAllowed": 100, "timeWindowMinutes": 1, "identifier": { "export": "rateLimit", "module": "$import(./modules/rate-limit)" } } } }

The requestsAllowed and timeWindowMinutes values in the policy configuration serve as defaults. Your function can override them per request, or omit them to use the defaults.

Common patterns

Tier-based limits from API key metadata

Store a plan or customerType field in your API key consumer metadata, then branch on it in your rate limit function. This is the simplest approach and requires no external lookups.

Route-based limits

Use request.url or request.params to apply different limits to different endpoints. For example, a search endpoint might allow 10 requests per minute while a read endpoint allows 100.

Code
export function rateLimit( request: ZuploRequest, context: ZuploContext, policyName: string, ): CustomRateLimitDetails | undefined { const isSearch = new URL(request.url).pathname.includes("/search"); return { key: request.user.sub, requestsAllowed: isSearch ? 10 : 100, timeWindowMinutes: 1, }; }

Method-based limits

Apply different limits to read operations (GET) vs. write operations (POST, PUT, DELETE). Write-heavy endpoints often need tighter limits to protect backends:

Code
export function rateLimit( request: ZuploRequest, context: ZuploContext, policyName: string, ): CustomRateLimitDetails | undefined { const isWrite = ["POST", "PUT", "DELETE", "PATCH"].includes(request.method); return { key: request.user.sub, requestsAllowed: isWrite ? 20 : 200, timeWindowMinutes: 1, }; }

Database-driven limits

For limits that change frequently or are managed outside your gateway configuration, look them up from a database at request time. Use the ZoneCache to avoid hitting the database on every request.

See Per-user rate limiting with a database for a complete example using Supabase and ZoneCache.

Skip rate limiting for specific requests

Return undefined to bypass rate limiting entirely. This is useful for health checks, internal services, or admin users:

Code
export function rateLimit( request: ZuploRequest, context: ZuploContext, policyName: string, ): CustomRateLimitDetails | undefined { if (request.user.data.role === "admin") { return undefined; } return { key: request.user.sub, requestsAllowed: 100, timeWindowMinutes: 1, }; }

Testing

To verify that dynamic limits are applied correctly, create API key consumers with different metadata values (for example, one with {"customerType": "premium"} and one with {"customerType": "free"}).

Make requests with each key until you receive a 429 Too Many Requests response. For example, with a free-tier key limited to 50 requests per minute:

TerminalCode
# Replace with your API URL and key for i in $(seq 1 55); do curl -s -o /dev/null -w "%{http_code}\n" \ -H "Authorization: Bearer YOUR_API_KEY" \ https://your-api.zuplo.dev/your-route done

The first 50 requests return 200. Requests 51-55 return 429 with a Retry-After header. Repeat with the premium key and confirm the higher limit applies.

Rate limit counters are per-environment. Preview and development environments have their own counters separate from production, so testing does not affect production limits.

Related resources

  • How rate limiting works — Full explanation of rateLimitBy modes and configuration options
  • Rate Limiting policy reference
  • Per-user rate limiting with a database — Advanced example with database lookups and caching
Edit this page
Last modified on June 11, 2026
Complex Rate LimitingCombining policies
On this page
  • How it works
  • Create a rate limit function
  • Configure the policy
  • Common patterns
    • Tier-based limits from API key metadata
    • Route-based limits
    • Method-based limits
    • Database-driven limits
    • Skip rate limiting for specific requests
  • Testing
  • Related resources
TypeScript
JSON
TypeScript
TypeScript
TypeScript