Rate limiting
How RestWard rate limits work, choosing values, plan ceilings, and client-side backoff patterns for 429 responses.
RestWard enforces rate limits at multiple levels to protect your APIs and ensure fair usage. Every request that exceeds a limit receives an HTTP 429 Too Many Requests response with standard headers that help clients back off gracefully.
How rate limiting works
RestWard applies four independent limits in order. A request is rejected as soon as it exceeds the first limit it encounters; it never consumes quota from limits checked later in the chain.
| Limit | Scope | Key | Window | When applied |
|---|---|---|---|---|
| Platform global | Entire deployment | ratelimit:platform:global |
Configurable (default 60s) | Every request to every tenant/API |
| Platform tenant | One organization | ratelimit:platform:tenant:{orgId} |
Configurable (default 60s) | Every request to any API of that org |
| Caller quota | One authenticated caller | Per API key, JWT subject, or mTLS cert | 1 minute | All routes of the API |
| Endpoint limit | One route + caller | Per endpoint path + caller | 1 minute | That specific endpoint |
Platform limits (#188) are fleet-wide: the global bucket is shared by every tenant and API in the deployment, and the tenant bucket is shared by every API of one organization. They are checked first so a refused request never consumes caller or endpoint quota.
Caller quota applies to every authenticated caller. API keys have their own
RateLimitPerMinute; JWT and mTLS callers use the method's configured quota (or a default of 60 requests per minute). Anonymous requests have no caller quota.
Endpoint limit is the per-endpoint rate limit you can configure in the dashboard for each endpoint (#149). It is counted per caller on that route.
Headers on every response
Every successful response and every 429 rejection carries the three standard rate-limit headers for the quota closest to running out (the one with the fewest remaining requests):
X-RateLimit-Limit— the quota ceiling for that windowX-RateLimit-Remaining— requests left in the current windowX-RateLimit-Reset— seconds until the window resets
On a 429 response only, you also receive:
Retry-After— seconds to wait before retrying (same value asX-RateLimit-Reset)X-RateLimit-Scope— which limit tripped:global,tenant,caller, orendpoint
Example 429 response
HTTPHTTP/1.1 429 Too Many Requests Content-Type: application/json Retry-After: 42 X-RateLimit-Limit: 60 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 42 X-RateLimit-Scope: caller {"error": "Rate limit exceeded. Try again later."}
Backing store
When a distributed store is configured (Redis), counters are fleet-wide across all API runtime replicas. If the store is unavailable or not configured, the middleware fails open to in-process counters so requests are never rejected due to store unavailability; the quota then applies per replica until the store recovers.
Redis implementation (
RedisDistributedRateLimitStore): a Lua script atomically increments the counter and sets the TTL only on the first request in the window, ensuring consistent counts across concurrent callers and replicas.
Choosing rate limit values
Per-key limits
Each API key has its own RateLimitPerMinute value set when the key is created. You can edit it later in the dashboard or via the API. A new key defaults to 60 requests per minute.
JWT and mTLS method limits
JWT and mTLS authentication methods can also have a configured rate limit. When not set or below 1, they use the same default: 60 requests per minute (DefaultPrincipalRateLimitPerMinute).
Endpoint limits
Each endpoint can have its own RateLimitPerMinute configured in the dashboard. This limit is counted per caller on that specific route, so different consumers hit their own buckets.
Platform limits
Platform administrators can configure two fleet-wide limits in Platform Settings:
- Global rate limit per minute — applies to every request across the entire deployment
- Per-tenant rate limit per minute — applies to every request for one organization across all its APIs
Both use the rate limit window seconds setting (default: 60 seconds) and include a burst allowance that is added to the scaled per-window allowance.
Calculation:
Allowance = (RatePerMinute * WindowSeconds / 60) + BurstAllowance
Plan ceilings and entitlements
Your organization's billing plan may impose ceilings on the maximum rate limit you can configure. The MaxKeyRateLimitPerMinute value in the billing model is the highest value allowed; any higher value you set for API keys or JWT/mTLS authentication methods is automatically lowered to this ceiling.
Example: On a plan with
MaxKeyRateLimitPerMinute: 1000, if you attempt to set a rate limit of 1500 on an API key or a JWT/mTLS method, it will be clamped to 1000 requests per minute.
For current plan limits and how they interact with rate limiting, see the pricing page or your organization's billing settings.
Handling 429 responses (client-side backoff)
When you receive a 429 response, honor the Retry-After header. Do not retry immediately; wait at least that many seconds before sending the next request.
curl
# First request (may hit the limit)
curl -H "X-API-Key: rw_abcd1234..." \
https://YOUR-API-HOSTNAME/api/public/users
# If you receive 429 with Retry-After: 42, wait before retrying
sleep 42
# Retry
curl -H "X-API-Key: rw_abcd1234..." \
https://YOUR-API-HOSTNAME/api/public/usersJavaScript (fetch)
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status !== 429) {
return response;
}
const retryAfter = response.headers.get('Retry-After');
const waitSeconds = retryAfter ? parseInt(retryAfter, 10) : 60;
if (attempt < maxRetries) {
await new Promise(resolve => setTimeout(resolve, waitSeconds * 1000));
}
}
throw new Error('Max retries exceeded');
}
// Usage
fetchWithRetry('https://YOUR-API-HOSTNAME/api/public/users', {
headers: { 'X-API-Key': 'rw_abcd1234...' }
})
.then(r => r.json())
.then(console.log);.NET (HttpClient with Polly)
Install the Polly library for resilient HTTP calls with automatic retry and rate-limit handling.
# Package Manager
dotnet add package Polly
# .NET CLI
dotnet package add Pollyusing System;
using System.Net;
using System.Threading.Tasks;
using Polly;
var retryPolicy = Policy
.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.TooManyRequests)
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: (attempt, outcome, context) =>
outcome.Result?.Headers.RetryAfter?.Delta
?? (outcome.Result?.Headers.RetryAfter?.Date - DateTimeOffset.UtcNow)
?? TimeSpan.FromSeconds(60),
onRetryAsync: (outcome, delay, attempt, context) =>
{
Console.WriteLine($"Rate limited. Waiting {delay.TotalSeconds}s before retry {attempt}");
return Task.CompletedTask;
});
// Usage with HttpClient
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Add("X-API-Key", "rw_abcd1234...");
var response = await retryPolicy.ExecuteAsync(
async () => await httpClient.GetAsync("https://YOUR-API-HOSTNAME/api/public/users"));See also
- API keys & tenant API auth — creating keys, authentication methods, IP allowlists
- Building & publishing APIs — endpoint configuration including rate limits
- Examples: Errors and rate limits — practical examples of handling rate limits in code