Rate Limits

Understand your plan's request quotas, rate limit response headers, and how to handle 429 errors gracefully.

Limits by Plan

PlanDaily LimitMonthly LimitRequests/Second
Free 100 3,000 1
Starter 10,000 300,000 10
Pro 100,000 3,000,000 50
Enterprise Unlimited Unlimited 200

Rate Limit Headers

Every API response includes rate limit headers so you can monitor your usage in real time:

HeaderDescription
X-RateLimit-Limit Maximum number of requests allowed in the current daily window.
X-RateLimit-Remaining Number of requests remaining in the current daily window.
X-RateLimit-Reset Unix timestamp (seconds) when the daily limit resets (midnight UTC).
X-RateLimit-Monthly-Limit Your plan's monthly request quota.
X-RateLimit-Monthly-Remaining Number of requests remaining in your monthly quota.
Retry-After Seconds to wait before retrying (only included on 429 responses).

Handling 429 Errors

When you exceed your rate limit, the API returns a 429 Too Many Requests response. Follow these steps to handle it gracefully:

1. Check the Reset Header

Read the X-RateLimit-Reset header to determine when you can make requests again. For monthly limits, check X-RateLimit-Monthly-Remaining.

2. Implement Exponential Backoff

If you receive a 429, wait before retrying. Double the wait time for each consecutive 429:

  • 1st retry: wait 1 second
  • 2nd retry: wait 2 seconds
  • 3rd retry: wait 4 seconds
  • Maximum: wait 60 seconds, then stop retrying

3. Cache Responses

Cache frequently requested data to reduce API calls. Fuel prices and central bank rates change infrequently, so caching for 1 hour is usually safe.

4. Use Batch Endpoints

When fetching data for multiple countries, omit the country parameter to get all data in a single request instead of making 54 separate calls.

5. Upgrade Your Plan

If you consistently hit rate limits, consider upgrading your plan for higher daily and monthly quotas.

Sandbox keys. Test keys (afro_test_*) share the Free plan limits but do not count against your production quota.
Response Headers (200 OK)
HTTP/1.1 200 OK Content-Type: application/json X-RateLimit-Limit: 10000 X-RateLimit-Remaining: 9847 X-RateLimit-Reset: 1742342400 X-RateLimit-Monthly-Limit: 300000 X-RateLimit-Monthly-Remaining: 287453

429 Response
HTTP/1.1 429 Too Many Requests Content-Type: application/json X-RateLimit-Limit: 100 X-RateLimit-Remaining: 0 X-RateLimit-Reset: 1742342400 Retry-After: 3600 { "success": false, "error": { "code": "RATE_LIMIT_EXCEEDED", "message": "Daily limit of 100 requests exceeded. Resets at 2026-03-17T00:00:00Z.", "docs": "https://afrotools.com/docs/api/rate-limits.html" } }

Exponential Backoff (JavaScript)
async function fetchWithRetry(url, opts, retries = 3) { for (let i = 0; i < retries; i++) { const res = await fetch(url, opts); if (res.status !== 429) return res; const wait = res.headers.get("Retry-After") ? parseInt(res.headers.get("Retry-After")) * 1000 : Math.pow(2, i) * 1000; await new Promise(r => setTimeout(r, Math.min(wait, 60000))); } throw new Error("Rate limit exceeded after retries"); }

Check Remaining Quota (Python)
import time, requests def fetch_with_retry(url, headers, retries=3): for i in range(retries): resp = requests.get(url, headers=headers) if resp.status_code != 429: return resp reset = resp.headers.get("X-RateLimit-Reset") if reset: wait = max(0, int(reset) - time.time()) else: wait = min(2 ** i, 60) time.sleep(wait) raise Exception("Rate limit exceeded") # Check usage resp = requests.get( "https://afrotools.com/api/forex", params={"from": "USD", "to": "NGN"}, headers={"x-api-key": "afro_live_your_key"} ) remaining = resp.headers.get("X-RateLimit-Remaining") limit = resp.headers.get("X-RateLimit-Limit") print(f"Daily: {int(limit) - int(remaining)}/{limit}") monthly = resp.headers.get("X-RateLimit-Monthly-Remaining") print(f"Monthly remaining: {monthly}")