Rate limits

Rate Limits

Why the limits exist, what the current per-key budget is, and how clients should react when they hit a 429.

Why we rate-limit

Rate limits protect Tixallo from runaway integrations and keep response times consistent for every workspace. They are per API key, so different keys (e.g. one per environment) have independent budgets.

Current limit (live)

Today the public v1 API enforces:

  • 60 requests per 60-second window per API key.
  • On exceedance, Tixallo returns HTTP 429 with a Retry-After: 60 header.

Per-IP, per-workspace, and burst limits are planned; they are not enforced today.

Example 429 response

Headers:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60

Body:

{
  "error": "Rate limit exceeded",
  "limit": 60,
  "window_seconds": 60
}

Recommended client behaviour

  • Cache where possible. Don't re-fetch the same ticket on every page render.
  • Avoid aggressive polling. Polling more than once a minute per resource is almost always the wrong tool.
  • Use webhooks instead of polling. Subscribe to ticket.created and ticket.message.created rather than re-listing every few seconds.
  • Apply exponential backoff with jitter. On 429, wait at least the value of Retry-After before retrying.
  • Treat 429 as a soft failure. Don't hard-fail your worker — schedule a retry and move on.

Backoff example — Node.js

async function callTixallo(path, init = {}, attempt = 0) {
  const apiKey = process.env.TIXALLO_API_KEY;
  const res = await fetch(`https://tixallo.com${path}`, {
    ...init,
    headers: {
      ...(init.headers ?? {}),
      Authorization: `Bearer ${apiKey}`,
      "Content-Type": "application/json",
    },
  });

  if (res.status === 429 && attempt < 5) {
    const retryAfter = Number(res.headers.get("retry-after") ?? "60");
    const jitter = Math.random() * 1000;
    await new Promise((r) => setTimeout(r, retryAfter * 1000 + jitter));
    return callTixallo(path, init, attempt + 1);
  }

  if (!res.ok) throw new Error(`Tixallo ${res.status}`);
  return res.json();
}

Backoff example — Python

import os
import random
import time
import requests

API_KEY = os.environ["TIXALLO_API_KEY"]
BASE = "https://tixallo.com"

def call_tixallo(path, *, method="GET", json=None, max_attempts=5, timeout=10):
    for attempt in range(max_attempts):
        resp = requests.request(
            method,
            f"{BASE}{path}",
            headers={
                "Authorization": f"Bearer {API_KEY}",
                "Content-Type": "application/json",
            },
            json=json,
            timeout=timeout,
        )
        if resp.status_code == 429:
            retry_after = int(resp.headers.get("Retry-After", "60"))
            time.sleep(retry_after + random.random())
            continue
        resp.raise_for_status()
        return resp.json()
    raise RuntimeError("Rate limit not cleared after retries")