Rate Limiting

The Ping API uses rate limiting to ensure fair usage and protect infrastructure from abuse. Understanding rate limits helps you build robust integrations that handle temporary restrictions gracefully.


Rate limit tiers

Rate limits vary by endpoint type and authentication method. All limits are per account (for API keys) or per user (for JWT tokens).

Authentication endpoints

  • Name
    POST /v1/auth/login
    Description

    5 requests per minute - Prevents brute force attacks

  • Name
    POST /v1/auth/refresh
    Description

    5 requests per minute - Prevents token abuse

  • Name
    POST /v1/auth/accept-invite
    Description

    10 requests per hour - Prevents invite abuse

Notification endpoints

  • Name
    POST /v1/notification/api/sms/send
    Description

    No hard limit - Throttled by provider capacity (~80-100/sec for bulk)

  • Name
    POST /v1/notification/api/whatsapp/send
    Description

    No hard limit - Subject to WhatsApp Business API limits

  • Name
    POST /v1/notification/api/email/send
    Description

    No hard limit - Throttled by email provider

Other endpoints

Most read endpoints (GET requests) have generous limits or no hard limits. Write endpoints (POST, PUT, DELETE) may have endpoint-specific limits to prevent abuse.


Handling rate limits

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

429 Response format

{
  "result": "failed",
  "message": "Rate limit exceeded. Please retry after 60 seconds.",
  "code": "RATE_LIMIT_EXCEEDED"
}

Response headers

Rate-limited responses include helpful headers:

  • Retry-After - Seconds to wait before retrying (e.g., 60)
  • X-RateLimit-Limit - Maximum requests allowed in the window
  • X-RateLimit-Remaining - Requests remaining in current window
  • X-RateLimit-Reset - Unix timestamp when the limit resets

Exponential backoff

Implement exponential backoff to handle rate limits gracefully:

  1. First retry: Wait time from Retry-After header (or 1 second)
  2. Second retry: Wait 2× the previous delay
  3. Third retry: Wait 4× the original delay
  4. Max retries: Stop after 3-5 attempts

Example: Python with backoff

Python

import requests
import time

def send_with_backoff(url, headers, data, max_retries=5):
    """Send request with exponential backoff on 429."""
    delay = 1

    for attempt in range(max_retries):
        response = requests.post(url, headers=headers, json=data)

        if response.status_code == 200:
            return response.json()

        if response.status_code == 429:
            # Get retry delay from header or use exponential backoff
            retry_after = int(response.headers.get('Retry-After', delay))
            print(f"Rate limited. Waiting {retry_after}s...")
            time.sleep(retry_after)
            delay *= 2  # Exponential backoff
            continue

        # Other error
        response.raise_for_status()

    raise Exception(f"Failed after {max_retries} retries")

# Usage
result = send_with_backoff(
    'https://api.ping.co.zw/v1/notification/api/sms/send',
    headers={'X-Ping-Api-Key': api_key},
    data={'to_phone': '+263771234567', 'message': 'Hello'}
)

Example: Node.js with backoff

Node.js

async function sendWithBackoff(url, options, maxRetries = 5) {
  let delay = 1000 // Start with 1 second

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, options)

    if (response.ok) {
      return await response.json()
    }

    if (response.status === 429) {
      const retryAfter = response.headers.get('Retry-After')
      const waitTime = retryAfter ? parseInt(retryAfter) * 1000 : delay

      console.log(`Rate limited. Waiting ${waitTime/1000}s...`)
      await new Promise(resolve => setTimeout(resolve, waitTime))
      delay *= 2 // Exponential backoff
      continue
    }

    throw new Error(`HTTP ${response.status}: ${response.statusText}`)
  }

  throw new Error(`Failed after ${maxRetries} retries`)
}

// Usage
const result = await sendWithBackoff(
  'https://api.ping.co.zw/v1/notification/api/sms/send',
  {
    method: 'POST',
    headers: {
      'X-Ping-Api-Key': apiKey,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      to_phone: '+263771234567',
      message: 'Hello'
    }),
  }
)

Best practices

1. Respect the Retry-After header

Always check the Retry-After header in 429 responses. This tells you exactly when it's safe to retry.

2. Implement exponential backoff

Don't retry immediately. Use exponential backoff to avoid overwhelming the API during recovery.

3. Cache authentication tokens

Don't call /v1/auth/login on every request. Cache JWT tokens and only refresh when they expire (after 30 days).

4. Batch operations when possible

Instead of making 100 separate SMS requests, use bulk operations:

# Bad - 100 separate requests (slow, may hit rate limits)
for phone in phones:
    send_sms(phone, message)

# Good - 1 bulk request
send_sms_bulk(phones, message)

5. Use bulk endpoints for large sends

For sending to 10+ recipients, use bulk endpoints:

  • SMS: Send array or recipient group to /v1/notification/api/sms/send
  • Email: Send array of addresses to /v1/notification/api/email/send

6. Monitor rate limit headers

Check X-RateLimit-Remaining on every response. If it's low, slow down your requests proactively.

7. Distribute load over time

For scheduled campaigns, spread sends over hours or days rather than sending everything at once.

8. Handle errors gracefully

Don't fail silently on rate limits. Log errors and implement proper retry logic so messages eventually get sent.

Rate limit monitoring

Monitor rate limits

import requests

response = requests.post(url, headers=headers, json=data)

# Check rate limit status
limit = response.headers.get('X-RateLimit-Limit')
remaining = response.headers.get('X-RateLimit-Remaining')
reset = response.headers.get('X-RateLimit-Reset')

if remaining:
    remaining_count = int(remaining)
    if remaining_count < 10:
        print(f"⚠️  Only {remaining_count} requests remaining!")
        # Slow down or pause

Bulk vs individual requests

When to use bulk:

  • Sending to 10+ recipients
  • Same message to multiple people
  • Scheduled campaigns

When to use individual:

  • Personalized messages
  • Real-time notifications
  • Fewer than 10 recipients

Common scenarios

Scenario 1: Authentication rate limit

Problem: Login endpoint returns 429 during development testing.

Solution:

  • Cache tokens instead of logging in repeatedly
  • Use long-lived API keys for automated testing
  • Implement proper token refresh flow

Scenario 2: Bulk campaign rate limited

Problem: Large email campaign fails with 429 midway through.

Solution:

  • Use bulk endpoints (/v1/notification/api/email/send with array)
  • Bulk jobs run in background without hitting rate limits
  • Track progress with bulk job status endpoint

Scenario 3: High-frequency notifications

Problem: Real-time notifications (OTPs, alerts) hit rate limits.

Solution:

  • Notification endpoints generally don't have hard rate limits
  • If hitting provider limits, contact support for increased capacity
  • Consider message queuing on your side to smooth traffic

Was this page helpful?