Pro Plan5 minutesintermediate

Rate Limits & Best Practices

Understand API rate limits - quotas, headers, and strategies for efficient API usage.

apirate-limitsperformancebest-practices
Last updated: January 15, 2025
Pro Plan

Understand Zenovay API rate limits and optimize your integration for reliability and performance.

Rate Limit Overview

External API Limits by Plan

These limits apply to the External API (endpoints under /api/external/v1/):

PlanRequests/MinuteRequests/Month
Free101,000
Pro3010,000
Scale60100,000
Enterprise1201,000,000

What Counts as a Request

Each API call counts as one request:

  • GET requests
  • POST requests
  • PUT/PATCH requests
  • DELETE requests

What Doesn't Count

  • Failed authentication (401)
  • Malformed requests (400)
  • Server errors (500)

Rate Limit Headers

Response Headers

Every External API response includes:

X-RateLimit-Limit: 30
X-RateLimit-Remaining: 27
X-Usage-Monthly: 4521
X-Usage-Limit: 10000
X-Usage-Reset: 2025-02-01T00:00:00Z
HeaderDescription
X-RateLimit-LimitYour per-minute request limit
X-RateLimit-RemainingRequests left this minute
X-Usage-MonthlyRequests used this month
X-Usage-LimitMonthly request limit
X-Usage-ResetWhen monthly usage resets

Reading Headers

const response = await fetch(url, { headers });

const limit = response.headers.get('X-RateLimit-Limit');
const remaining = response.headers.get('X-RateLimit-Remaining');
const reset = response.headers.get('X-RateLimit-Reset');

console.log(`${remaining}/${limit} requests remaining`);
console.log(`Resets at ${new Date(reset * 1000)}`);

Rate Limit Exceeded

Response

When limit exceeded:

HTTP/1.1 429 Too Many Requests
Retry-After: 3600

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded. Please retry after 3600 seconds.",
    "retry_after": 3600
  }
}

Handling 429 Errors

async function apiRequest(url, options, retries = 3) {
  const response = await fetch(url, options);

  if (response.status === 429 && retries > 0) {
    const retryAfter = response.headers.get('Retry-After') || 60;
    console.log(`Rate limited. Waiting ${retryAfter}s...`);
    await sleep(retryAfter * 1000);
    return apiRequest(url, options, retries - 1);
  }

  return response;
}

Best Practices

Caching

Cache responses when possible:

const cache = new Map();
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes

async function getWithCache(endpoint) {
  const cached = cache.get(endpoint);
  if (cached && Date.now() - cached.time < CACHE_TTL) {
    return cached.data;
  }

  const response = await api.get(endpoint);
  cache.set(endpoint, { data: response, time: Date.now() });
  return response;
}

Request Only What You Need

Use time range filters to limit the data returned:

# Filter to a specific time range:
GET /api/external/v1/analytics/WEBSITE_ID?timeRange=7d

Use Date Filters

Limit data returned to specific periods:

# Fetch only the last 30 days:
GET /api/external/v1/analytics/WEBSITE_ID?timeRange=30d

Pagination

Default Pagination

GET /api/external/v1/analytics/WEBSITE_ID/visitors?page=1&per_page=50

Response

{
  "data": [...],
  "meta": {
    "total": 1250,
    "page": 1,
    "per_page": 50,
    "total_pages": 25
  }
}

Efficient Pagination

async function getAllVisitors() {
  let allVisitors = [];
  let page = 1;
  let hasMore = true;

  while (hasMore) {
    const response = await api.get(`/visitors?page=${page}&per_page=100`);
    allVisitors = allVisitors.concat(response.data);

    hasMore = page < response.meta.total_pages;
    page++;

    // Respect rate limits
    await sleep(100);
  }

  return allVisitors;
}

Exponential Backoff

Implementation

async function requestWithBackoff(fn, maxRetries = 5) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      if (error.status === 429 && attempt < maxRetries - 1) {
        const delay = Math.pow(2, attempt) * 1000 + Math.random() * 1000;
        console.log(`Retry ${attempt + 1} after ${delay}ms`);
        await sleep(delay);
      } else {
        throw error;
      }
    }
  }
}

Backoff Schedule

AttemptWait Time
11-2 seconds
22-4 seconds
34-8 seconds
48-16 seconds
516-32 seconds

Monitoring Usage

Check Current Usage

curl https://api.zenovay.com/api/external/v1/usage \
  -H "X-API-Key: zv_YOUR_API_KEY"

This returns your current API usage and remaining quota for the month.

Usage Dashboard

View in API Keys from the sidebar:

  • Hourly usage graph
  • Daily totals
  • Top endpoints
  • Error rates

Usage Alerts

Set up alerts:

  • 80% of hourly limit
  • 90% of daily limit
  • Rate limit exceeded

Optimizing High-Volume Use

Use Aggregated Endpoints

Use the analytics overview endpoint instead of fetching multiple separate data points:

# Single request for analytics overview:
GET /api/external/v1/analytics/WEBSITE_ID?timeRange=30d

This returns visitors, page views, bounce rate, top countries, and daily stats in one response.

Enterprise Rate Limits

Enterprise Plan

Custom Limits

Enterprise customers can request:

  • Higher hourly limits
  • Higher burst limits
  • Dedicated capacity

Priority Queue

Enterprise requests get:

  • Priority processing
  • Dedicated resources
  • Better SLA

Requesting Increase

Contact your CSM or email support@zenovay.com with:

  • Current usage patterns
  • Expected growth
  • Use case details

Troubleshooting

Frequently Rate Limited

If hitting limits often:

  1. Review usage patterns
  2. Implement caching
  3. Use aggregated endpoints
  4. Upgrade plan

Burst Limit Issues

If hitting burst limits:

  1. Add delays between requests
  2. Implement request queue
  3. Use exponential backoff

Unexpected Low Limits

If limits seem wrong:

  1. Verify plan level
  2. Check all API keys
  3. Contact support

Next Steps

Was this article helpful?