Rate Limits¶
The TopTickets API uses rate limiting to ensure fair usage and system stability. This guide explains how rate limits work and how to handle them.
Rate Limit Tiers¶
Rate limits vary based on your API key type:
| Key Type | Prefix | Limit |
|---|---|---|
| Admin | tt_admin_ |
2,000 requests/minute |
| Read-Only | tt_ro_ |
200 requests/minute |
| Agent | tt_agent_ |
200 requests/minute |
| Live/Default | tt_live_ |
200 requests/minute |
Rate Limit Response¶
When you exceed the rate limit, you receive a 429 Too Many Requests response:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 30
{
"detail": "Rate limit exceeded. Try again in 30 seconds."
}
Response Headers¶
| Header | Description |
|---|---|
Retry-After |
Seconds until you can retry |
Handling Rate Limits¶
Basic Retry Logic¶
import time
import requests
def make_request_with_retry(method, url, max_retries=3, **kwargs):
"""Make request with automatic retry on rate limit."""
for attempt in range(max_retries):
response = requests.request(method, url, **kwargs)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
print(f"Rate limited. Waiting {retry_after}s...")
time.sleep(retry_after)
continue
return response
raise Exception("Max retries exceeded")
Exponential Backoff¶
For more robust handling, use exponential backoff:
import time
import random
import requests
def request_with_backoff(method, url, max_retries=5, **kwargs):
"""Make request with exponential backoff."""
for attempt in range(max_retries):
response = requests.request(method, url, **kwargs)
if response.status_code == 429:
# Exponential backoff with 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"Rate limited. Waiting {delay:.1f}s (attempt {attempt + 1})")
time.sleep(delay)
continue
return response
raise Exception(f"Request failed after {max_retries} retries")
Rate Limiting Class¶
For production applications, implement a rate limiter:
import time
import threading
from collections import deque
class RateLimiter:
"""Simple rate limiter using sliding window."""
def __init__(self, requests_per_minute):
self.requests_per_minute = requests_per_minute
self.window = deque()
self.lock = threading.Lock()
def wait_if_needed(self):
"""Block until we can make another request."""
with self.lock:
now = time.time()
window_start = now - 60
# Remove requests outside the window
while self.window and self.window[0] < window_start:
self.window.popleft()
# Check if we're at the limit
if len(self.window) >= self.requests_per_minute:
# Wait until oldest request expires
sleep_time = self.window[0] - window_start
if sleep_time > 0:
time.sleep(sleep_time)
self.window.popleft()
# Record this request
self.window.append(now)
# Usage
rate_limiter = RateLimiter(requests_per_minute=200)
def make_api_call():
rate_limiter.wait_if_needed()
return requests.get(
"https://api.toptickets.app/v1/tickets",
headers=HEADERS
)
Best Practices¶
1. Cache Responses¶
Reduce API calls by caching frequently accessed data:
from functools import lru_cache
import time
@lru_cache(maxsize=100)
def get_ticket_cached(ticket_id):
"""Get ticket with 5-minute cache."""
return requests.get(
f"{BASE_URL}/tickets/{ticket_id}",
headers=HEADERS
).json()
# Clear cache periodically
def clear_ticket_cache():
get_ticket_cached.cache_clear()
2. Batch Operations¶
Instead of making many individual requests, batch operations where possible:
# Bad: Many individual requests
for ticket_id in ticket_ids:
response = requests.get(f"{BASE_URL}/tickets/{ticket_id}")
# Better: Use list endpoint with filtering
response = requests.get(
f"{BASE_URL}/tickets",
params={"limit": 500}
)
3. Use Webhooks¶
For real-time updates, consider using webhooks instead of polling:
# Bad: Polling every minute
while True:
tickets = get_updated_tickets()
process(tickets)
time.sleep(60)
# Better: Receive webhook notifications
# (Configure webhook in dashboard)
4. Implement Circuit Breakers¶
Prevent cascading failures when rate limited:
class CircuitBreaker:
def __init__(self, failure_threshold=5, reset_timeout=60):
self.failure_count = 0
self.failure_threshold = failure_threshold
self.reset_timeout = reset_timeout
self.last_failure_time = None
self.state = "closed" # closed, open, half-open
def call(self, func, *args, **kwargs):
if self.state == "open":
if time.time() - self.last_failure_time > self.reset_timeout:
self.state = "half-open"
else:
raise Exception("Circuit breaker is open")
try:
result = func(*args, **kwargs)
if self.state == "half-open":
self.state = "closed"
self.failure_count = 0
return result
except Exception as e:
self.failure_count += 1
self.last_failure_time = time.time()
if self.failure_count >= self.failure_threshold:
self.state = "open"
raise e
5. Monitor Your Usage¶
Track your API usage to stay within limits:
import logging
class APIClient:
def __init__(self):
self.request_count = 0
self.window_start = time.time()
def make_request(self, method, endpoint, **kwargs):
# Reset counter every minute
if time.time() - self.window_start > 60:
logging.info(f"Made {self.request_count} requests in last minute")
self.request_count = 0
self.window_start = time.time()
self.request_count += 1
# Warn if approaching limit
if self.request_count > 150: # 75% of 200 limit
logging.warning(f"Approaching rate limit: {self.request_count} requests")
return requests.request(method, f"{BASE_URL}{endpoint}", **kwargs)
Rate Limit FAQs¶
Q: Do rate limits apply per API key or per user?¶
Rate limits apply per API key. Each key has its own quota.
Q: Are there different limits for different endpoints?¶
No, all endpoints share the same rate limit based on your key type.
Q: What happens if I consistently exceed rate limits?¶
Requests will be rejected with 429 errors. Consider: - Optimizing your code to make fewer requests - Caching responses - Upgrading to an admin API key
Q: Do failed requests count against the limit?¶
Yes, all requests count regardless of outcome.
Q: Is there a burst allowance?¶
The limit is enforced as a sliding window. Brief bursts may be allowed, but sustained high volume will trigger rate limiting.