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
429with aRetry-After: 60header.
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: 60Body:
{
"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.createdandticket.message.createdrather than re-listing every few seconds. - Apply exponential backoff with jitter. On
429, wait at least the value ofRetry-Afterbefore 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")