Skip to main content

Rate Limits

Rate limits protect the API from abuse and ensure fair usage across all clients.

Rate Limit Headers

Every API response includes these headers:

HeaderDescriptionExample
X-RateLimit-LimitMax requests per minute for your plan120
X-RateLimit-RemainingRequests remaining in current window95
X-RateLimit-ResetUnix timestamp when window resets1704067200

Example response headers:

HTTP/1.1 200 OK
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1704067200
Content-Type: application/json

Rate Limit Exceeded (429)

When you exceed the limit, you receive:

HTTP/1.1 429 Too Many Requests
Retry-After: 15
X-RateLimit-Limit: 120
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1704067200
Content-Type: application/json
{
"error": {
"code": "rate_limit_exceeded",
"message": "Too many requests. Please retry after 15 seconds."
},
"request_id": "req_abc123"
}

The Retry-After header indicates how many seconds to wait.

Soft Limiting Behavior

Before rejecting requests, the API applies soft limiting:

UsageBehavior
0-80%Normal response
80-100%Normal response + X-RateLimit-Warning: approaching_limit
>100%Requests delayed (throttled)
Sustained >100%429 Too Many Requests

This gives you time to reduce request rate before hitting hard limits.

Per-Endpoint Adjustments

Some endpoints have lower limits due to query complexity:

Endpoint PatternLimit Adjustment
/sites/{id}/stats50% of plan limit
/sites/{id}/funnel50% of plan limit
All other endpoints100% of plan limit

Endpoints Without Rate Limits

These endpoints are excluded from rate limiting:

  • /health - Health check
  • /livez - Liveness probe
  • /readyz - Readiness probe
  • /docs - Swagger documentation
  • /redoc - ReDoc documentation
  • /openapi.json - OpenAPI specification

Best Practices

1. Monitor Rate Limit Headers

def make_request(url, headers):
response = requests.get(url, headers=headers)

remaining = int(response.headers.get('X-RateLimit-Remaining', 0))
if remaining < 10:
print(f"Warning: Only {remaining} requests remaining")

return response

2. Implement Exponential Backoff

import time
import random

def request_with_backoff(url, headers, max_retries=5):
for attempt in range(max_retries):
response = requests.get(url, headers=headers)

if response.status_code != 429:
return response

retry_after = int(response.headers.get('Retry-After', 1))
# Add jitter to prevent thundering herd
sleep_time = retry_after + random.uniform(0, 1)
time.sleep(sleep_time)

raise Exception("Max retries exceeded")

3. Cache Responses

from functools import lru_cache
import time

# Cache stats for 5 minutes
@lru_cache(maxsize=100)
def get_stats_cached(site_id, period, cache_key):
return get_stats(site_id, period)

def get_stats(site_id, period):
# Create cache key that expires every 5 minutes
cache_key = int(time.time() / 300)
return get_stats_cached(site_id, period, cache_key)

4. Batch Requests When Possible

Instead of:

# Bad: 10 separate requests
for site_id in site_ids:
stats = get_stats(site_id)

Consider:

# Better: Fewer requests with more data per request
stats = get_stats_batch(site_ids) # If batch endpoint available

5. Use Webhooks for Real-Time Data

For real-time updates, use webhooks instead of polling:

# Bad: Polling every second (60 req/min just for one metric)
while True:
stats = get_stats(site_id)
time.sleep(1)

# Better: Configure webhook for real-time updates
# (No API calls needed - data pushed to you)

Enterprise Custom Limits

Enterprise plans can negotiate custom limits:

  • Per-endpoint limits
  • Higher burst allowances
  • Dedicated API instances
  • SLA guarantees

Contact sales@sealmetrics.com for Enterprise pricing.