Skip to main content

Rate Limits

Usage limits, response headers, and best practices for staying within quota.

Back to Docs

Limits by Tier

Rate limits are applied per API key. If you exceed the limit, the API returns a 429 Too Many Requests response.

TierPer MinutePer DayPer Week
Free10 req100 req50 conversions
Pro60 req10,000 req200 conversions
Team300 req100,000 req500 conversions

Response Headers

Every API response includes rate-limit headers so you can proactively manage your usage:

HeaderDescription
X-RateLimit-LimitThe maximum number of requests allowed in the current time window.
X-RateLimit-RemainingThe number of requests remaining in the current time window.
X-RateLimit-ResetUnix timestamp (seconds) when the current window resets.

Example response headers:

HTTP/1.1 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1705312800

Handling 429 Responses

When you receive a 429 response, the body includes a retry_after field indicating how many seconds to wait before retrying:

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Please retry after 12 seconds.",
    "status": 429,
    "retry_after": 12
  }
}
Tip: Monitor the X-RateLimit-Remaining header proactively to throttle your requests before hitting the limit.

Exponential Backoff

For production systems, implement exponential backoff with jitter to gracefully handle rate limits and transient errors:

async function requestWithBackoff(url, options, maxRetries = 5) {
  let attempt = 0;

  while (attempt < maxRetries) {
    const res = await fetch(url, options);

    if (res.ok) {
      return res.json();
    }

    // Only retry on rate-limit or server errors
    if (res.status !== 429 && res.status < 500) {
      const { error } = await res.json();
      throw new Error(error.message);
    }

    const { error } = await res.json();

    // Use server-provided retry_after, or calculate backoff
    const retryAfter = error.retry_after
      ? error.retry_after * 1000
      : Math.min(1000 * Math.pow(2, attempt), 30000);

    // Add jitter (0-25% of wait time)
    const jitter = retryAfter * Math.random() * 0.25;
    const waitMs = retryAfter + jitter;

    console.log(
      `Rate limited. Retrying in ${Math.round(waitMs / 1000)}s `
      + `(attempt ${attempt + 1}/${maxRetries})`
    );

    await new Promise(resolve => setTimeout(resolve, waitMs));
    attempt++;
  }

  throw new Error('Max retries exceeded');
}

Python example

import time
import random
import requests

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

        if response.ok:
            return response.json()

        if response.status_code not in (429, 500, 502, 503):
            error = response.json().get("error", {})
            raise Exception(error.get("message", "Request failed"))

        error = response.json().get("error", {})
        retry_after = error.get("retry_after", min(2 ** attempt, 30))

        # Add jitter
        jitter = retry_after * random.uniform(0, 0.25)
        wait = retry_after + jitter

        print(f"Rate limited. Retrying in {wait:.1f}s "
              f"(attempt {attempt + 1}/{max_retries})")

        time.sleep(wait)

    raise Exception("Max retries exceeded")
Best practices summary:
  • Always respect the retry_after value when provided.
  • Add random jitter to avoid thundering herd effects.
  • Set a maximum retry count to prevent infinite loops.
  • Log rate-limit events to identify usage patterns.
  • Consider queuing requests client-side to stay under the per-minute limit.