Platform

Rate Limits

Per-plan request quotas, 429 behavior, and client patterns for retries, batching, and caching on the Africa API.

Your plan sets a monthly request quota and a per-minute rate limit, shared across the account's API keys. Exceeding either limit returns 429 Too Many Requests.

Plan Limits

PlanMonthly requestsPer minuteAPI keys
Free1,00051
Analyst250,000603
Team2,000,0001,0005

Monthly counters reset on the first of each calendar month in UTC. Per-minute limits use fixed minute windows. Plans have the same data and endpoint access. See pricing for plan details, and manage billing in the dashboard.

What a 429 Looks Like

The response body tells you which limit you hit:

{ "detail": "rate limit exceeded for plan 'free' (5 requests/minute)" }
{
  "detail": "This request was blocked because the monthly request limit for Free (1,000 requests) was reached. Upgrade to Analyst: https://africa-api.com/dashboard?section=billing"
}
  • Per-minute 429: back off and retry after the next minute window begins.
  • Monthly 429: wait for the counter to reset on the 1st, or choose a plan with a quota above your current usage. Retrying does not restore quota.

Responses do not currently include X-RateLimit-* or Retry-After headers. Check monthly usage in the dashboard, and inspect the detail string to distinguish the two limit errors.

  1. Set explicit request timeouts.
  2. Retry transient failures (429 per-minute, 5xx) with backoff. Stop retrying when the monthly quota is exhausted.
  3. Batch related observations into one /v1/data call instead of many single-resource requests.
  4. Cache GET /v1/countries using ETag and Cache-Control.
  5. Avoid request spikes when hydrating dashboards.

Retry Template (JavaScript)

async function requestWithBackoff(url, init, maxAttempts = 4) {
  let delayMs = 300;

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const response = await fetch(url, init);
    if (response.ok) return response;

    let retryable = response.status >= 500;
    if (response.status === 429) {
      const body = await response.clone().json().catch(() => null);
      // Monthly quota 429s won't clear on retry; return them to the caller.
      retryable = !body?.detail?.includes("monthly request limit");
    }
    if (!retryable || attempt === maxAttempts) return response;

    await new Promise((resolve) => setTimeout(resolve, delayMs + Math.random() * 200));
    delayMs *= 2;
  }
}

Query Design Tips

Make each request count against your quota:

  • Prefer country_codes=ng,ke,za over three separate requests.
  • Prefer metric_keys=population_total,gdp_current_usd where a combined response is acceptable.
  • Use latest=true when you do not need a full time series.
  • One /v1/countries/{code}/signals call replaces several single-domain requests when you need a country snapshot.

On this page