AI API Rate Limiting and Error Handling

Production Patterns for Reliable AI Integrations (2026)

Production Rate Limiting Error Handling

AI APIs fail. Networks timeout, providers rate-limit, models return errors, and traffic spikes overwhelm endpoints. Production applications need robust handling for every failure mode. This guide covers battle-tested patterns for rate limiting, retry logic, circuit breakers, and error recovery when using Chinese AI models through TokenEase.

Common AI API Failure Modes

StatusCauseRetry?
429 Too Many RequestsRate limit exceededYes, with backoff
500 Internal Server ErrorProvider errorYes, with backoff
502 Bad GatewayUpstream provider downYes, with longer backoff
503 Service UnavailableProvider overloadedYes, with backoff
504 Gateway TimeoutRequest took too longYes, reduce max_tokens
401 UnauthorizedInvalid API keyNo, fix credentials
400 Bad RequestMalformed requestNo, fix request

Exponential Backoff with Jitter

The gold standard for retry logic. Wait longer between each retry, plus random jitter to prevent thundering herd:

import random
import time
import requests

def call_with_retry(url, headers, payload, max_retries=3):
    for attempt in range(max_retries + 1):
        try:
            response = requests.post(url, headers=headers, json=payload, timeout=60)
            
            if response.status_code == 200:
                return response.json()
            
            # Don't retry client errors (4xx except 429)
            if 400 <= response.status_code < 500 and response.status_code != 429:
                raise Exception(f"Client error {response.status_code}: {response.text}")
            
            # Server errors and rate limits - retry with backoff
            if attempt < max_retries:
                # Exponential backoff: 1s, 2s, 4s + random jitter
                base_delay = 2 ** attempt
                jitter = random.uniform(0, 1)
                delay = base_delay + jitter
                
                # Respect Retry-After header if present
                retry_after = response.headers.get('Retry-After')
                if retry_after:
                    delay = max(delay, int(retry_after))
                
                print(f"Attempt {attempt + 1} failed ({response.status_code}). Retrying in {delay:.1f}s...")
                time.sleep(delay)
            else:
                raise Exception(f"Max retries exceeded. Last status: {response.status_code}")
                
        except requests.exceptions.Timeout:
            if attempt < max_retries:
                delay = 2 ** attempt + random.uniform(0, 1)
                print(f"Timeout. Retrying in {delay:.1f}s...")
                time.sleep(delay)
            else:
                raise Exception("Request timed out after all retries")
        
        except requests.exceptions.ConnectionError:
            if attempt < max_retries:
                delay = 2 ** attempt + random.uniform(0, 1)
                print(f"Connection error. Retrying in {delay:.1f}s...")
                time.sleep(delay)
            else:
                raise Exception("Connection failed after all retries")
    
    raise Exception("Unexpected end of retry loop")

Circuit Breaker Pattern

When a provider is consistently failing, stop calling it temporarily to prevent cascading failures:

import time
from enum import Enum

class CircuitState(Enum):
    CLOSED = "closed"      # Normal operation
    OPEN = "open"          # Failing, reject requests
    HALF_OPEN = "half_open"  # Testing if recovered

class CircuitBreaker:
    def __init__(self, failure_threshold=5, recovery_timeout=30):
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.state = CircuitState.CLOSED
        self.failure_count = 0
        self.last_failure_time = None
    
    def call(self, func, *args, **kwargs):
        if self.state == CircuitState.OPEN:
            if time.time() - self.last_failure_time > self.recovery_timeout:
                self.state = CircuitState.HALF_OPEN
                print("Circuit breaker: testing recovery...")
            else:
                raise Exception("Circuit breaker is OPEN. Provider temporarily disabled.")
        
        try:
            result = func(*args, **kwargs)
            self._on_success()
            return result
        except Exception as e:
            self._on_failure()
            raise e
    
    def _on_success(self):
        self.failure_count = 0
        if self.state == CircuitState.HALF_OPEN:
            self.state = CircuitState.CLOSED
            print("Circuit breaker: CLOSED (recovered)")
    
    def _on_failure(self):
        self.failure_count += 1
        self.last_failure_time = time.time()
        
        if self.failure_count >= self.failure_threshold:
            self.state = CircuitState.OPEN
            print(f"Circuit breaker: OPEN ({self.failure_count} failures)")

# Usage
cb = CircuitBreaker(failure_threshold=3, recovery_timeout=60)

def make_api_call():
    response = requests.post(API_URL, headers=headers, json=payload)
    response.raise_for_status()
    return response.json()

try:
    result = cb.call(make_api_call)
except Exception as e:
    # Fallback: use different model or cached response
    result = fallback_to_cached_or_alternative_model()

Model Fallback Strategy

When one model provider fails, automatically switch to another. This is where TokenEase's unified API shines:

MODEL_PRIORITY = ["deepseek", "qwen", "zhipu", "kimi", "doubao"]

def call_with_fallback(messages, preferred_model="deepseek"):
    # Try preferred model first
    models_to_try = [preferred_model] + [m for m in MODEL_PRIORITY if m != preferred_model]
    
    last_error = None
    for model in models_to_try:
        try:
            response = requests.post(
                f"{BASE_URL}/chat/completions",
                headers={"Authorization": f"Bearer {API_KEY}"},
                json={
                    "model": model,
                    "messages": messages,
                    "max_tokens": 500
                },
                timeout=30
            )
            
            if response.status_code == 200:
                print(f"Success with model: {model}")
                return response.json()
            
            # If rate limited or server error, try next model
            if response.status_code in [429, 500, 502, 503, 504]:
                print(f"{model} returned {response.status_code}, trying fallback...")
                continue
            else:
                # Client error - don't retry with other models
                response.raise_for_status()
                
        except requests.exceptions.Timeout:
            print(f"{model} timed out, trying fallback...")
            continue
        except Exception as e:
            last_error = e
            print(f"{model} failed: {e}")
            continue
    
    raise Exception(f"All models failed. Last error: {last_error}")

# Usage - seamless fallback across 5 providers
result = call_with_fallback(messages, preferred_model="deepseek")
Why this matters: Individual model providers have outages. On January 28, 2026, DeepSeek experienced a major outage due to a cyberattack. Users with fallback logic switched to Qwen or GLM instantly. Users without fallback were completely down.

Token Bucket Rate Limiter (Client-Side)

Prevent yourself from hitting provider rate limits by throttling your own requests:

import time
import threading

class TokenBucket:
    def __init__(self, rate_per_minute=60):
        self.rate = rate_per_minute / 60.0  # tokens per second
        self.tokens = rate_per_minute
        self.last_update = time.time()
        self.lock = threading.Lock()
    
    def acquire(self, tokens=1):
        with self.lock:
            now = time.time()
            elapsed = now - self.last_update
            self.tokens = min(self.rate * 60, self.tokens + elapsed * self.rate)
            self.last_update = now
            
            if self.tokens >= tokens:
                self.tokens -= tokens
                return True
            else:
                wait_time = (tokens - self.tokens) / self.rate
                return wait_time
    
    def wait_and_acquire(self, tokens=1):
        result = self.acquire(tokens)
        if result is not True:
            time.sleep(result)
            self.acquire(tokens)

# Usage: limit to 30 requests per minute
bucket = TokenBucket(rate_per_minute=30)

for query in batch_queries:
    bucket.wait_and_acquire(1)
    response = call_api(query)

Handling Streaming Errors

Streaming connections have unique failure modes:

def safe_stream(model, messages, on_token, on_error):
    try:
        response = requests.post(
            f"{BASE_URL}/chat/completions",
            headers={"Authorization": f"Bearer {API_KEY}"},
            json={"model": model, "messages": messages, "stream": True},
            stream=True,
            timeout=(5, 60)  # (connect_timeout, read_timeout)
        )
        
        if response.status_code != 200:
            on_error(f"HTTP {response.status_code}: {response.text}")
            return
        
        for line in response.iter_lines():
            if line:
                try:
                    data = line.decode('utf-8')[6:]  # Remove "data: "
                    if data == '[DONE]':
                        break
                    chunk = json.loads(data)
                    if chunk["choices"][0]["delta"].get("content"):
                        on_token(chunk["choices"][0]["delta"]["content"])
                except Exception as e:
                    # Skip malformed chunks, continue stream
                    continue
                    
    except requests.exceptions.ChunkedEncodingError:
        on_error("Stream interrupted. Connection lost.")
    except requests.exceptions.ReadTimeout:
        on_error("Stream timed out. Model generation took too long.")
    except Exception as e:
        on_error(f"Stream error: {str(e)}")

Monitoring and Alerting

Track API health to detect issues before users complain:

import datetime

class APIMonitor:
    def __init__(self):
        self.metrics = {
            "total_requests": 0,
            "successful": 0,
            "failed": 0,
            "rate_limited": 0,
            "avg_latency": 0,
            "errors_by_model": {}
        }
    
    def record(self, model, status_code, latency, success):
        self.metrics["total_requests"] += 1
        
        if success:
            self.metrics["successful"] += 1
        else:
            self.metrics["failed"] += 1
            if status_code == 429:
                self.metrics["rate_limited"] += 1
        
        if model not in self.metrics["errors_by_model"]:
            self.metrics["errors_by_model"][model] = {"success": 0, "fail": 0}
        
        key = "success" if success else "fail"
        self.metrics["errors_by_model"][model][key] += 1
        
        # Update rolling average latency
        n = self.metrics["total_requests"]
        self.metrics["avg_latency"] = (self.metrics["avg_latency"] * (n-1) + latency) / n
    
    def health_check(self):
        failure_rate = self.metrics["failed"] / max(self.metrics["total_requests"], 1)
        
        if failure_rate > 0.1:  # >10% failure rate
            print(f"ALERT: High failure rate {failure_rate:.1%}")
            return "unhealthy"
        elif failure_rate > 0.05:
            return "degraded"
        return "healthy"

# Usage
monitor = APIMonitor()

start = time.time()
try:
    result = call_api(model="deepseek", messages=messages)
    monitor.record("deepseek", 200, time.time() - start, True)
except Exception as e:
    monitor.record("deepseek", 500, time.time() - start, False)

Production Checklist

TokenEase Advantage: With 6 model providers behind a single endpoint, model fallback is built-in. If DeepSeek returns 503, TokenEase can route to Qwen automatically. One API key, one endpoint, six fallback providers.

Conclusion

Reliable AI integrations require more than just calling an API. They need retry logic, circuit breakers, rate limiting, and graceful degradation. The patterns in this guide have been proven in production systems handling millions of requests per day.

Start with exponential backoff for retries, add circuit breakers for resilience, and implement model fallback for high availability. Monitor everything, and your AI integration will be as reliable as any other production service.

Build Resilient AI Applications

Get $1 free API credit to test production patterns with all 6 Chinese AI models.

Start Building