Skip to content

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.