ioZen Docs

Rate Limits

Understand API rate limiting, response headers, and retry strategies.

The ioZen API enforces rate limits per API key to ensure fair usage and platform stability. Limits use a sliding window algorithm — there's no fixed reset point, and your available quota replenishes gradually.

Limits by Plan

PlanRequests per MinuteMax API Keys
Free0 (no API access)
Pro1003
Business50010

Rate limits apply per API key, not per workspace. If your workspace has multiple keys, each gets its own quota.

Rate Limit Headers

Every API response includes rate limit information in the headers:

HeaderTypeDescription
X-RateLimit-LimitintegerMaximum requests allowed per window
X-RateLimit-RemainingintegerRequests remaining in the current window
X-RateLimit-ResetintegerUnix timestamp (milliseconds) when the window resets

These headers are present on all responses, including error responses.

When Rate Limited

If you exceed your limit, the API returns a 429 Too Many Requests response:

{
  "success": false,
  "error": {
    "code": "rate_limit_exceeded",
    "message": "Rate limit exceeded",
    "details": {
      "retry_after": 12
    }
  }
}

The retry_after value is in seconds — wait at least this long before retrying.

Retry Strategies

JavaScript — Exponential Backoff with Jitter

async function fetchWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status !== 429) return response;

    const body = await response.json();
    const retryAfter = body.error?.details?.retry_after ?? 1;
    const jitter = Math.random() * 1000;
    const delay = retryAfter * 1000 + jitter;

    await new Promise((resolve) => setTimeout(resolve, delay));
  }

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

Python — Exponential Backoff with Jitter

import time
import random
import requests

def fetch_with_retry(url, headers, max_retries=3):
    for attempt in range(max_retries + 1):
        response = requests.get(url, headers=headers)

        if response.status_code != 429:
            return response

        body = response.json()
        retry_after = body.get("error", {}).get("details", {}).get("retry_after", 1)
        jitter = random.uniform(0, 1)
        time.sleep(retry_after + jitter)

    raise Exception("Max retries exceeded")

Best Practices

  • Check headers proactively. Monitor X-RateLimit-Remaining and slow down before hitting the limit — don't wait for a 429.
  • Use webhooks instead of polling. Instead of polling GET /submissions every few seconds, register a webhook for submission.completed events.
  • Cache responses where possible to reduce API calls. Intake bot configurations change infrequently.
  • Batch work during off-peak hours. If you have bulk operations, spread them across time to stay within limits.
  • Use a single API key per integration. This gives each integration its own rate limit quota and makes monitoring easier.

Timeout Behavior

If the rate limit check itself takes longer than 3 seconds (e.g., due to Redis latency), the API allows the request through rather than blocking it. This fail-open behavior ensures that temporary infrastructure issues don't prevent legitimate API usage.

On this page