Sign in →

Rate Limits

Aforo enforces rate limits on all API endpoints to ensure platform stability for all tenants. Rate limits are applied per API key, not per IP address.

Default Limits

API CategoryLimitWindow
Usage Ingestion10,000 events/minPer key
Admin API (products, rate plans, customers)600 req/minPer key
Billing API (invoices, subscriptions)300 req/minPer key
Analytics / Reporting60 req/minPer key
Headless Storefront600 req/minPer storefront key
AI Endpoints20 req/minPer key

Rate Limit Headers

Every API response includes headers showing your current rate limit status:

X-RateLimit-Limit: 600
X-RateLimit-Remaining: 487
X-RateLimit-Reset: 1713362460
X-RateLimit-Window: 60
HeaderDescription
X-RateLimit-LimitTotal requests allowed per window
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when the window resets
X-RateLimit-WindowWindow duration in seconds

429 Too Many Requests

When you exceed the limit, the API returns:

HTTP/1.1 429 Too Many Requests
Content-Type: application/problem+json
Retry-After: 23
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1713362460

{
  "type": "https://errors.aforo.ai/rate-limited",
  "title": "Too Many Requests",
  "status": 429,
  "detail": "Rate limit of 600 requests per minute exceeded. Retry after 23 seconds.",
  "retryAfter": 23
}

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/events/batch
Content-Type: application/json

{
  "events": [
    { "customerId": "cust_1", "metricId": "api-calls", "quantity": 1, "timestamp": "..." },
    { "customerId": "cust_2", "metricId": "api-calls", "quantity": 5, "timestamp": "..." }
  ]
}

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:

  1. Optimize batching — Use batch ingestion for usage events instead of individual calls
  2. Upgrade plan — Higher Aforo plans include higher rate limits
  3. Contact support — For enterprise-scale requirements, contact your account manager for custom limits

Contact support →