Click Airtime

Rate Limits

Understand Click Airtime API rate limits and implement proper throttling to avoid request rejections.

Rate limits protect the Click Airtime API from abuse and ensure fair access for all users. This guide explains the limits for each API version and how to handle rate-limited responses.

Rate Limit Overview

Version 2 API

Endpoint CategoryRate LimitWindow
Authentication (/enterprise/auth/*)20 requestsPer minute
Read operations (GET)300 requestsPer minute
Write operations (POST, PUT, DELETE)120 requestsPer minute
Transaction creation60 requestsPer minute
Catalog lookup120 requestsPer minute

Version 1 API

EndpointRate LimitWindow
POST /adp/topup60 requestsPer minute
GET /adp/balances120 requestsPer minute
GET /adp/transactions120 requestsPer minute

Enterprise plans may have higher rate limits based on your subscription tier. Contact sales@clickairtime.com to discuss custom limits for high-volume integrations.

Rate Limit Headers

The V2 API includes rate limit information in the response headers:

X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Reset: 1705312800
HeaderDescription
X-RateLimit-LimitMaximum number of requests allowed in the current window
X-RateLimit-RemainingNumber of requests remaining in the current window
X-RateLimit-ResetUnix timestamp when the rate limit window resets

Handling Rate Limits

When you exceed the rate limit, the API returns a 429 Too Many Requests response:

{
  "success": false,
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded. Please retry after 45 seconds.",
    "statusCode": 429
  }
}

The response includes a Retry-After header with the number of seconds to wait.

Implementation

async function requestWithRateLimiting(fn) {
  const response = await fn();

  if (response.status === 429) {
    const retryAfter = parseInt(response.headers.get('Retry-After') || '60', 10);
    console.log(`Rate limited. Retrying after ${retryAfter} seconds...`);
    await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
    return fn(); // Retry once
  }

  return response;
}

// Proactive rate limiting using response headers
class RateLimitedClient {
  constructor() {
    this.remaining = Infinity;
    this.resetAt = 0;
  }

  async request(fn) {
    // Wait if we know we are out of requests
    if (this.remaining <= 0) {
      const waitMs = Math.max(0, (this.resetAt * 1000) - Date.now());
      if (waitMs > 0) {
        console.log(`Rate limit reached. Waiting ${Math.ceil(waitMs / 1000)}s...`);
        await new Promise(resolve => setTimeout(resolve, waitMs));
      }
    }

    const response = await fn();

    // Update rate limit state from headers
    this.remaining = parseInt(response.headers.get('X-RateLimit-Remaining') || 'Infinity', 10);
    this.resetAt = parseInt(response.headers.get('X-RateLimit-Reset') || '0', 10);

    if (response.status === 429) {
      const retryAfter = parseInt(response.headers.get('Retry-After') || '60', 10);
      await new Promise(resolve => setTimeout(resolve, retryAfter * 1000));
      return fn();
    }

    return response;
  }
}
import time
import requests as http

class RateLimitedClient:
    def __init__(self):
        self.remaining = float('inf')
        self.reset_at = 0

    def request(self, method, url, **kwargs):
        # Wait if we know we are out of requests
        if self.remaining <= 0:
            wait_seconds = max(0, self.reset_at - time.time())
            if wait_seconds > 0:
                print(f"Rate limit reached. Waiting {int(wait_seconds)}s...")
                time.sleep(wait_seconds)

        response = http.request(method, url, **kwargs)

        # Update rate limit state from headers
        self.remaining = int(response.headers.get('X-RateLimit-Remaining', float('inf')))
        self.reset_at = int(response.headers.get('X-RateLimit-Reset', 0))

        if response.status_code == 429:
            retry_after = int(response.headers.get('Retry-After', 60))
            print(f"Rate limited. Retrying after {retry_after} seconds...")
            time.sleep(retry_after)
            response = http.request(method, url, **kwargs)

        return response

# Usage
client = RateLimitedClient()
response = client.request(
    'GET',
    'https://api.clickairtime.com/enterprise/wallets',
    headers={
        'Authorization': f'Bearer {access_token}',
        'X-Company-ID': str(company_id),
    }
)

Best Practices

  1. Monitor rate limit headers -- Track X-RateLimit-Remaining proactively to avoid hitting the limit.

  2. Implement queuing -- For high-volume applications, use a job queue to throttle API calls to stay within limits.

  3. Cache where possible -- Cache catalog lookups and balance checks to reduce unnecessary API calls.

  4. Spread requests evenly -- Instead of bursting 60 requests at once, spread them across the minute window (1 request per second).

  5. Handle 429 gracefully -- Always respect the Retry-After header and do not immediately retry rate-limited requests.

  6. Use separate rate limit budgets -- Track rate limits independently for each endpoint category to maximize throughput.