Rate Limits
Aforo enforces rate limits to keep the platform stable for every workspace. The dimension a limit is keyed on depends on the surface: authenticated API traffic is limited per workspace (tenant), the headless storefront API per storefront key (consumer), and public/unauthenticated routes per IP.
Default Limits
The gateway enforces these; the application layer also applies a 300 req/min per-workspace default as a backstop. Limits are not a per-plan entitlement — for per-customer usage caps on your own products, see the subscription-level section below.
Rate Limit Headers
Every API response includes headers showing your current rate limit status:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 213
The application layer sets only X-RateLimit-Limit and X-RateLimit-Remaining. Gateway-fronted routes add the RateLimit-Reset and per-window (-Minute / -Hour) variants.
429 Too Many Requests
When you exceed the limit, the API returns:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 0
{
"success": false,
"errors": [
{ "code": "RATE_LIMITED", "message": "Rate limit exceeded. Max 300 requests per minute per tenant." }
]
}
The Retry-After header is your reliable signal for backoff. (At the gateway, a rejected request may instead return Kong's own { "message": "API rate limit exceeded" } body — handle both by keying on the 429 status, not the body shape.)
Handling 429 in Your Code
Node.js / TypeScript
async function callWithRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn()
} catch (err: any) {
if (err.status === 429 && attempt < maxRetries) {
const retryAfter = parseInt(err.headers?.['retry-after'] ?? '5', 10)
await new Promise(resolve => setTimeout(resolve, retryAfter * 1000))
continue
}
throw err
}
}
throw new Error('Max retries exceeded')
}
Python
import time
import httpx
def call_with_retry(fn, max_retries=3):
for attempt in range(max_retries + 1):
response = fn()
if response.status_code == 429 and attempt < max_retries:
retry_after = int(response.headers.get('retry-after', 5))
time.sleep(retry_after)
continue
response.raise_for_status()
return response
raise Exception("Max retries exceeded")
The official Aforo SDK (@aforo/metering / aforo-metering) handles 429 responses automatically with exponential backoff — you don't need to implement retry logic manually when using the SDK.
Bulk Ingestion
If you need to ingest large volumes of historical data, use the batch endpoint rather than individual events:
POST /v1/ingest/batch
Content-Type: application/json
{
"events": [
{ "customerId": "cust_1", "metricName": "api-calls", "quantity": 1, "occurredAt": "..." },
{ "customerId": "cust_2", "metricName": "api-calls", "quantity": 5, "occurredAt": "..." }
]
}
Batch requests accept up to 1,000 events per call and count as a single request against your rate limit.
For historical data migration (millions of events), use File Upload in the Developer Hub.
Rate Limit Enforcement (Subscription Level)
Separate from API rate limits, Aforo can enforce per-customer usage limits on your API products through rate plans:
- Hard limits (
enforcement: BLOCK) — Reject requests when customer exceeds quota - Soft limits (
enforcement: ALERT) — Allow requests, notify operator
These customer-level limits are enforced at the gateway (Kong, Apigee, etc.) or via the Aforo metering SDK, not at the API level.
Increasing Limits
If the default limits don't meet your needs:
- Optimize batching — Use batch ingestion for usage events instead of individual calls
- Upgrade plan — Higher Aforo plans include higher rate limits
- Contact support — For enterprise-scale requirements, contact your account manager for custom limits