Skip to main content
Xquik enforces rate limits to ensure fair usage and platform stability. Limits are applied per API key using a token bucket algorithm.

Rate Limit Tiers

ScopeSustained RateBurst Allowance
API endpoints (/api/v1/*)10 req/s20 requests
General requests20 req/s40 requests

How Limits Work

Xquik uses a token bucket algorithm:
  • Each API key has a bucket that fills at the sustained rate (e.g. 10 tokens/second for API endpoints)
  • The bucket holds a maximum of burst tokens (e.g. 20 for API endpoints)
  • Each request consumes 1 token
  • When the bucket is empty, requests return 429 Too Many Requests
This means you can send short bursts above the sustained rate as long as you stay within the burst allowance. For example, with the API tier:
  • You can send 20 requests instantly (burst)
  • After that, you can send 10 requests per second (sustained)
  • If you stop sending requests, the bucket refills at 10 tokens per second
Time 0s:  [20 tokens] → Send 20 requests → [0 tokens]
Time 1s:  [10 tokens] → Send 10 requests → [0 tokens]
Time 2s:  [10 tokens] → Send 5 requests  → [5 tokens]
Time 3s:  [15 tokens] → Idle             → [20 tokens] (capped at burst)

Response Headers

When you exceed the rate limit, the response includes:
HeaderDescription
Retry-AfterSeconds to wait before retrying
Example 429 response:
HTTP/1.1 429 Too Many Requests
Retry-After: 2
Content-Type: text/plain

Rate limit exceeded
Always respect the Retry-After header. Sending requests before the window resets may extend your cooldown.

Client-Side Rate Limiter

Prevent hitting server-side limits by implementing a client-side token bucket. This is more efficient than relying on 429 responses and backoff.
class RateLimiter {
  constructor(ratePerSecond, burst) {
    this.ratePerSecond = ratePerSecond;
    this.burst = burst;
    this.tokens = burst;
    this.lastRefill = Date.now();
  }

  refill() {
    const now = Date.now();
    const elapsed = (now - this.lastRefill) / 1000;
    this.tokens = Math.min(this.burst, this.tokens + elapsed * this.ratePerSecond);
    this.lastRefill = now;
  }

  async acquire() {
    this.refill();

    if (this.tokens < 1) {
      const waitMs = ((1 - this.tokens) / this.ratePerSecond) * 1000;
      await new Promise((resolve) => setTimeout(resolve, waitMs));
      this.refill();
    }

    this.tokens -= 1;
  }
}

// Usage: 10 req/s sustained, 20 burst
const limiter = new RateLimiter(10, 20);

async function apiRequest(url) {
  await limiter.acquire();
  return fetch(url, {
    headers: { "x-api-key": "xq_YOUR_KEY_HERE" },
  });
}

Best Practices

Fetch events in larger pages (limit=100) instead of many small requests. 1 request for 100 events is better than 10 requests for 10 events each.
Webhooks deliver events in real time with zero polling overhead. You only receive traffic when something happens. See the webhooks overview.
Monitor and webhook configurations change infrequently. Cache GET responses for list endpoints and invalidate only after mutations (create, update, delete).
When you receive a 429, wait for the Retry-After duration. If the retry also fails, double the wait time. See the error handling guide for complete retry implementations.
Avoid sending all requests in a burst at the start of each interval. Spread them evenly across the window to maintain a steady token refill rate.