English
English
Appearance
English
English
Appearance
Two mechanisms Zorio uses to protect the API and to help your CRM retry safely.
Counted per token (each token has its own bucket):
| Endpoint group | Limit | Window |
|---|---|---|
/api/pbx/calls/click-to-call | 10 req/min | 60-second sliding window |
/api/pbx/* (every other endpoint) | 60 req/min | 60-second sliding window |
/api/telesales/* | 120 req/min | 60-second sliding window |
/api/autocall/* | 120 req/min | 60-second sliding window |
/api/auth/login | 5 req/min | 60-second sliding window (brute-force protection) |
/api/auth/* (other endpoints) | 30 req/min | 60-second sliding window |
Need higher limits?
Enterprise customers can request higher limits through their SLA. Reach out to the Zorio team.
Every response includes:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1782706011| Header | Meaning |
|---|---|
X-RateLimit-Limit | Total allowed requests in the window |
X-RateLimit-Remaining | Requests still available in the window |
X-RateLimit-Reset | UTC epoch seconds when the quota resets to Limit |
HTTP 429 with body:
{ "message": "Too many requests. Retry after 25 seconds." }Headers:
Retry-After: 25
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1782706036Retry-After const response = await fetch(url, options);
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After'), 10) || 60;
await sleep(retryAfter * 1000);
return retry();
}Avoid the thundering herd when many clients retry at once:
async function callWithBackoff(fn, attempt = 0) {
try {
return await fn();
} catch (err) {
if (err.status !== 429 && err.status < 500) throw err;
if (attempt >= 5) throw err;
const baseDelay = Math.min(1000 * Math.pow(2, attempt), 30_000);
const jitter = Math.random() * 1000;
await sleep(baseDelay + jitter);
return callWithBackoff(fn, attempt + 1);
}
}Several endpoints support batching:
POST /api/telesales/campaigns/{id}/leads/bulk-import — import N leads at oncePATCH /api/telesales/leads/bulk — update N leads at onceDELETE /api/telesales/leads/bulk — delete N leads at onceBatching saves rate-limit budget and runs faster than looping single calls.
Slow-changing resources (e.g. caller-ids, queues, users) — cache for 1-5 minutes on the CRM side. Don't poll every second.
X-RateLimit-Remaining When Remaining < 5 — slow down proactively, don't wait until you hit 0.
When a client retries a POST/PATCH/DELETE due to a timeout, how do you guarantee:
Clients send an Idempotency-Key: <UUID v4> header on each retry-able request.
curl -X POST 'https://app.zorio.vn/api/pbx/calls/click-to-call' \
-H 'Authorization: Bearer <TOKEN>' \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890' \
-d '{"from_extension":"1001","to_phone":"0987654321"}'Idempotency-Key → processes the request normally and caches the response under that key (TTL 24h).| Method + endpoint | Why you need it |
|---|---|
POST /api/pbx/calls/click-to-call | Avoid firing two calls on a network glitch |
POST /api/telesales/campaigns | Avoid creating two campaigns with the same name |
POST /api/telesales/campaigns/{id}/leads | Avoid duplicate leads |
POST /api/autocall/campaigns | Avoid creating two campaigns with the same name |
POST /api/integrations/sms | Avoid sending the same SMS twice (double charge) |
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx).const { v4: uuidv4 } = require('uuid');
async function clickToCall(fromExt, toPhone) {
const key = uuidv4();
for (let attempt = 0; attempt < 3; attempt++) {
try {
const res = await api.post('/pbx/calls/click-to-call', {
from_extension: fromExt,
to_phone: toPhone,
}, {
headers: { 'Idempotency-Key': key },
});
return res.data;
} catch (err) {
if (attempt === 2) throw err;
await sleep(1000 * (attempt + 1));
}
}
}→ Even with 3 retries, only one call is created.
Idempotency-Key). A different body → HTTP 422 with "message": "Idempotency-Key was used with a different body.".If-Match Some update endpoints support If-Match to prevent races:
PUT /api/pbx/extensions/1001
If-Match: "etag-abc123"If the resource has changed between the client's GET and PUT → ETag mismatch → HTTP 412 { "message": "Resource has changed, please GET again." }. Your CRM must re-fetch and re-apply changes.
{ "message": "Too many concurrent connections." }.