Rate Limiting

The ZB ID API enforces rate limits to protect against brute-force attacks, abuse, and to ensure fair usage across all clients. Each endpoint has specific limits based on the sensitivity of the operation.


Rate limit tiers

Rate limits vary by endpoint and are tuned based on the risk profile of each operation. Authentication endpoints have stricter limits to prevent credential stuffing and brute-force attacks.

Authentication endpoints

  • Name
    Login
    Type
    5 requests / 10 minutes
    Description

    Applies to POST /auth/login. Prevents password brute-forcing. Tracked per login identifier (the email or phone number used to sign in, normalised).

  • Name
    Register
    Type
    10 requests / 60 minutes
    Description

    Applies to POST /auth/register. Prevents mass account creation. Tracked per client IP address.

  • Name
    Token refresh
    Type
    30 requests / 60 minutes
    Description

    Applies to POST /auth/token/refresh. Allows reasonable token rotation. Tracked per client IP address.

  • Name
    Forgot password
    Type
    5 requests / 60 minutes
    Description

    Applies to POST /auth/password/forgot. Throttles reset-link requests. Tracked per client IP address.

OAuth / Client credentials

  • Name
    Client credentials
    Type
    60 requests / minute
    Description

    Applies to POST /oauth/token. Tracked per client_id. The limit is 60 requests per minute.

Rate limit summary table

EndpointLimitWindowScope
Login5 requests10 minutesLogin identifier
Register10 requests60 minutesIP address
Token refresh30 requests60 minutesIP address
Forgot password5 requests60 minutesIP address
Client credentials60 requests1 minuteclient_id

Rate limit scope

Rate limits are scoped to different identifiers depending on the endpoint. This means that one user hitting a limit does not affect other users.

Scope by endpoint

  • Name
    Login
    Type
    Per login identifier
    Description

    Tracked by the email or phone number used to sign in (normalised). Each unique identifier has its own counter and sliding window.

  • Name
    Register, Token refresh, Forgot password
    Type
    Per IP address
    Description

    Tracked by the caller's IP address (the X-Forwarded-For client IP). Each source IP has its own counter.

  • Name
    Client credentials (OAuth)
    Type
    Per client_id
    Description

    Tracked by the client_id in the token request. Each registered OAuth client has its own counter.


Handling 429 responses

When you exceed a rate limit, the API returns a 429 Too Many Requests response. Your application should implement exponential backoff to retry gracefully.

429 Response format

{
  "error": "RATE_LIMIT_EXCEEDED",
  "message": "Too many login attempts",
  "status": 429,
  "timestamp": "2026-05-31T10:30:00.123Z"
}

The message varies by endpoint (Too many login attempts, Too many registration attempts, Too many refresh attempts, Too many password reset requests, or Rate limit exceeded for client: <id>), but the error code is always RATE_LIMIT_EXCEEDED.

Exponential backoff strategy

When you receive a 429 response, wait before retrying using increasing delays:

  1. First retry: Wait 2 seconds
  2. Second retry: Wait 4 seconds
  3. Third retry: Wait 8 seconds
  4. Fourth retry: Wait 16 seconds
  5. Max retries: Stop after 4-5 attempts and surface the error to the user

Adding a small random jitter (e.g., 0 to 500 ms) to each delay helps prevent multiple clients from retrying at exactly the same time.

Exponential backoff

# Simple retry loop with exponential backoff
DELAY=2
MAX_RETRIES=4

for i in $(seq 1 $MAX_RETRIES); do
  RESPONSE=$(curl -s -o /dev/null -w "%{http_code}" \
    -X POST https://id.zb.co.zw/auth/login \
    -H "Content-Type: application/json" \
    -d '{"phone": "+263771234567", "password": "SecurePass123"}')

  if [ "$RESPONSE" -ne 429 ]; then
    echo "Request succeeded with status $RESPONSE"
    break
  fi

  echo "Rate limited. Waiting ${DELAY}s before retry $i..."
  sleep $DELAY
  DELAY=$((DELAY * 2))
done

Implementation details

Redis-based sliding window

ZB ID uses a Redis-backed sliding window algorithm for rate limiting. This provides accurate, per-key tracking with minimal overhead.

How it works:

  • Each rate-limited action (e.g., login attempt for a specific phone number) is recorded as an entry in a Redis sorted set
  • Entries older than the configured window (e.g., 10 minutes for login) are automatically pruned
  • The count of remaining entries determines whether the request is allowed or rejected
  • This approach avoids the "boundary burst" problem that fixed-window counters can have

Graceful degradation

If the Redis instance becomes unavailable, the rate limiter degrades gracefully:

  • Requests are allowed through rather than blocked
  • The API continues to function normally without rate limiting
  • An internal alert is triggered so the operations team can restore Redis
  • Once Redis recovers, rate limiting resumes automatically with a fresh window

This design ensures that a Redis outage never causes a complete service disruption for legitimate users.

Best practices

  1. Cache tokens instead of requesting new ones on every API call
  2. Use refresh tokens to rotate access tokens rather than re-authenticating
  3. Implement backoff in all client libraries to handle 429 responses automatically
  4. Monitor your usage and design around the published limits; contact the ZB ID team if a high-throughput integration needs a higher ceiling

Quick reference

DetailValue
AlgorithmSliding window (Redis sorted sets)
StorageRedis
DegradationFail-open (requests allowed if Redis is down)
ScopePer login identifier, per IP, or per client_id
Response code429 Too Many Requests

Was this page helpful?