# Rate Limiting Guide **Version**: 1.0.0 **Last Updated**: 2025-11-16 Complete guide for understanding and handling rate limits in the IGNY8 API v1.0. --- ## Overview Rate limiting protects the API from abuse and ensures fair resource usage. Different operation types have different rate limits based on their resource intensity. --- ## Rate Limit Headers Every API response includes rate limit information in headers: - `X-Throttle-Limit`: Maximum requests allowed in the time window - `X-Throttle-Remaining`: Remaining requests in current window - `X-Throttle-Reset`: Unix timestamp when the limit resets ### Example Response Headers ```http HTTP/1.1 200 OK X-Throttle-Limit: 60 X-Throttle-Remaining: 45 X-Throttle-Reset: 1700123456 Content-Type: application/json ``` --- ## Rate Limit Scopes Rate limits are scoped by operation type: ### AI Functions (Expensive Operations) | Scope | Limit | Endpoints | |-------|-------|-----------| | `ai_function` | 10/min | Auto-cluster, content generation | | `image_gen` | 15/min | Image generation (DALL-E, Runware) | | `planner_ai` | 10/min | AI-powered planner operations | | `writer_ai` | 10/min | AI-powered writer operations | ### Content Operations | Scope | Limit | Endpoints | |-------|-------|-----------| | `content_write` | 30/min | Content creation, updates | | `content_read` | 100/min | Content listing, retrieval | ### Authentication | Scope | Limit | Endpoints | |-------|-------|-----------| | `auth` | 20/min | Login, register, password reset | | `auth_strict` | 5/min | Sensitive auth operations | ### Planner Operations | Scope | Limit | Endpoints | |-------|-------|-----------| | `planner` | 60/min | Keywords, clusters, ideas CRUD | ### Writer Operations | Scope | Limit | Endpoints | |-------|-------|-----------| | `writer` | 60/min | Tasks, content, images CRUD | ### System Operations | Scope | Limit | Endpoints | |-------|-------|-----------| | `system` | 100/min | Settings, prompts, profiles | | `system_admin` | 30/min | Admin-only system operations | ### Billing Operations | Scope | Limit | Endpoints | |-------|-------|-----------| | `billing` | 30/min | Credit queries, usage logs | | `billing_admin` | 10/min | Credit management (admin) | ### Default | Scope | Limit | Endpoints | |-------|-------|-----------| | `default` | 100/min | Endpoints without explicit scope | --- ## Rate Limit Exceeded (429) When rate limit is exceeded, you receive: **Status Code**: `429 Too Many Requests` **Response**: ```json { "success": false, "error": "Rate limit exceeded", "request_id": "550e8400-e29b-41d4-a716-446655440000" } ``` **Headers**: ```http X-Throttle-Limit: 60 X-Throttle-Remaining: 0 X-Throttle-Reset: 1700123456 ``` ### Handling Rate Limits **1. Check Headers Before Request** ```python def make_request(url, headers): response = requests.get(url, headers=headers) # Check remaining requests remaining = int(response.headers.get('X-Throttle-Remaining', 0)) if remaining < 5: # Approaching limit, slow down time.sleep(1) return response.json() ``` **2. Handle 429 Response** ```python def make_request_with_backoff(url, headers, max_retries=3): for attempt in range(max_retries): response = requests.get(url, headers=headers) if response.status_code == 429: # Get reset time reset_time = int(response.headers.get('X-Throttle-Reset', 0)) current_time = int(time.time()) wait_seconds = max(1, reset_time - current_time) print(f"Rate limited. Waiting {wait_seconds} seconds...") time.sleep(wait_seconds) continue return response.json() raise Exception("Max retries exceeded") ``` **3. Implement Exponential Backoff** ```python import time import random def make_request_with_exponential_backoff(url, headers): max_wait = 60 # Maximum wait time in seconds base_wait = 1 # Base wait time in seconds for attempt in range(5): response = requests.get(url, headers=headers) if response.status_code != 429: return response.json() # Exponential backoff with jitter wait_time = min( base_wait * (2 ** attempt) + random.uniform(0, 1), max_wait ) print(f"Rate limited. Waiting {wait_time:.2f} seconds...") time.sleep(wait_time) raise Exception("Rate limit exceeded after retries") ``` --- ## Best Practices ### 1. Monitor Rate Limit Headers Always check `X-Throttle-Remaining` to avoid hitting limits: ```python def check_rate_limit(response): remaining = int(response.headers.get('X-Throttle-Remaining', 0)) if remaining < 10: print(f"Warning: Only {remaining} requests remaining") return remaining ``` ### 2. Implement Request Queuing For bulk operations, queue requests to stay within limits: ```python import queue import threading class RateLimitedAPI: def __init__(self, requests_per_minute=60): self.queue = queue.Queue() self.requests_per_minute = requests_per_minute self.min_interval = 60 / requests_per_minute self.last_request_time = 0 def make_request(self, url, headers): # Ensure minimum interval between requests elapsed = time.time() - self.last_request_time if elapsed < self.min_interval: time.sleep(self.min_interval - elapsed) response = requests.get(url, headers=headers) self.last_request_time = time.time() return response.json() ``` ### 3. Cache Responses Cache frequently accessed data to reduce API calls: ```python from functools import lru_cache import time class CachedAPI: def __init__(self, cache_ttl=300): # 5 minutes self.cache = {} self.cache_ttl = cache_ttl def get_cached(self, url, headers, cache_key): # Check cache if cache_key in self.cache: data, timestamp = self.cache[cache_key] if time.time() - timestamp < self.cache_ttl: return data # Fetch from API response = requests.get(url, headers=headers) data = response.json() # Store in cache self.cache[cache_key] = (data, time.time()) return data ``` ### 4. Batch Requests When Possible Use bulk endpoints instead of multiple individual requests: ```python # ❌ Don't: Multiple individual requests for keyword_id in keyword_ids: response = requests.get(f"/api/v1/planner/keywords/{keyword_id}/", headers=headers) # ✅ Do: Use bulk endpoint if available response = requests.post( "/api/v1/planner/keywords/bulk/", json={"ids": keyword_ids}, headers=headers ) ``` --- ## Rate Limit Bypass ### Development/Debug Mode Rate limiting is automatically bypassed when: - `DEBUG=True` in Django settings - `IGNY8_DEBUG_THROTTLE=True` environment variable - User belongs to `aws-admin` account - User has `admin` or `developer` role **Note**: Headers are still set for debugging, but requests are not blocked. --- ## Monitoring Rate Limits ### Track Usage ```python class RateLimitMonitor: def __init__(self): self.usage_by_scope = {} def track_request(self, response, scope): if scope not in self.usage_by_scope: self.usage_by_scope[scope] = { 'total': 0, 'limited': 0 } self.usage_by_scope[scope]['total'] += 1 if response.status_code == 429: self.usage_by_scope[scope]['limited'] += 1 remaining = int(response.headers.get('X-Throttle-Remaining', 0)) limit = int(response.headers.get('X-Throttle-Limit', 0)) usage_percent = ((limit - remaining) / limit) * 100 if usage_percent > 80: print(f"Warning: {scope} at {usage_percent:.1f}% capacity") def get_report(self): return self.usage_by_scope ``` --- ## Troubleshooting ### Issue: Frequent 429 Errors **Causes**: - Too many requests in short time - Not checking rate limit headers - No request throttling implemented **Solutions**: 1. Implement request throttling 2. Monitor `X-Throttle-Remaining` header 3. Add delays between requests 4. Use bulk endpoints when available ### Issue: Rate Limits Too Restrictive **Solutions**: 1. Contact support for higher limits (if justified) 2. Optimize requests (cache, batch, reduce frequency) 3. Use development account for testing (bypass enabled) --- ## Code Examples ### Python - Complete Rate Limit Handler ```python import requests import time from datetime import datetime class RateLimitedClient: def __init__(self, base_url, token): self.base_url = base_url self.headers = { 'Authorization': f'Bearer {token}', 'Content-Type': 'application/json' } self.rate_limits = {} def _wait_for_rate_limit(self, scope='default'): """Wait if approaching rate limit""" if scope in self.rate_limits: limit_info = self.rate_limits[scope] remaining = limit_info.get('remaining', 0) reset_time = limit_info.get('reset_time', 0) if remaining < 5: wait_time = max(0, reset_time - time.time()) if wait_time > 0: print(f"Rate limit low. Waiting {wait_time:.1f}s...") time.sleep(wait_time) def _update_rate_limit_info(self, response, scope='default'): """Update rate limit information from response headers""" limit = response.headers.get('X-Throttle-Limit') remaining = response.headers.get('X-Throttle-Remaining') reset = response.headers.get('X-Throttle-Reset') if limit and remaining and reset: self.rate_limits[scope] = { 'limit': int(limit), 'remaining': int(remaining), 'reset_time': int(reset) } def request(self, method, endpoint, scope='default', **kwargs): """Make rate-limited request""" # Wait if approaching limit self._wait_for_rate_limit(scope) # Make request url = f"{self.base_url}{endpoint}" response = requests.request(method, url, headers=self.headers, **kwargs) # Update rate limit info self._update_rate_limit_info(response, scope) # Handle rate limit error if response.status_code == 429: reset_time = int(response.headers.get('X-Throttle-Reset', 0)) wait_time = max(1, reset_time - time.time()) print(f"Rate limited. Waiting {wait_time:.1f}s...") time.sleep(wait_time) # Retry once response = requests.request(method, url, headers=self.headers, **kwargs) self._update_rate_limit_info(response, scope) return response.json() def get(self, endpoint, scope='default'): return self.request('GET', endpoint, scope) def post(self, endpoint, data, scope='default'): return self.request('POST', endpoint, scope, json=data) # Usage client = RateLimitedClient("https://api.igny8.com/api/v1", "your_token") # Make requests with automatic rate limit handling keywords = client.get("/planner/keywords/", scope="planner") ``` --- **Last Updated**: 2025-11-16 **API Version**: 1.0.0