Rate limit + Idempotency
Hai cơ chế Zorio dùng để bảo vệ API + giúp CRM xử lý retry an toàn.
Rate limit
Mục đích
- Bảo vệ hệ thống Zorio khỏi flood request (vô tình hoặc DDoS).
- Đảm bảo công bằng giữa các tài khoản — 1 tài khoản không thể chiếm hết tài nguyên.
- Khuyến khích CRM caching + batch xử lý thay vì spam endpoint.
Mức giới hạn mặc định
Tính theo token (mỗi token riêng):
| Endpoint group | Limit | Window |
|---|---|---|
/api/pbx/calls/click-to-call | 10 req/phút | 60 giây trượt |
/api/pbx/* (mọi endpoint khác) | 60 req/phút | 60 giây trượt |
/api/telesales/* | 120 req/phút | 60 giây trượt |
/api/autocall/* | 120 req/phút | 60 giây trượt |
/api/auth/login | 5 req/phút | 60 giây trượt (chống brute-force) |
/api/auth/* (các endpoint khác) | 30 req/phút | 60 giây trượt |
Cần limit cao hơn?
Khách hàng enterprise có thể request limit cao hơn qua hợp đồng SLA. Liên hệ team Zorio.
Header trả về
Mọi response có header sau:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1782706011| Header | Ý nghĩa |
|---|---|
X-RateLimit-Limit | Tổng request cho phép trong window |
X-RateLimit-Remaining | Còn lại bao nhiêu request trong window |
X-RateLimit-Reset | Epoch giây UTC khi quota reset về Limit |
Khi vượt giới hạn
HTTP 429 với body:
{ "message": "Too many requests. Retry after 25 seconds." }Header:
Retry-After: 25
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1782706036Best practice CRM
1. Tôn trọng Retry-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();
}2. Exponential backoff + jitter
Tránh thundering herd khi nhiều client cùng retry:
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);
}
}3. Batch endpoint khi có
Nhiều endpoint hỗ trợ batch:
POST /api/telesales/campaigns/{id}/leads/bulk-import— import N lead 1 lầnPATCH /api/telesales/leads/bulk— update N lead 1 lầnDELETE /api/telesales/leads/bulk— xoá N lead 1 lần
Dùng batch tiết kiệm rate limit + nhanh hơn so với loop call từng cái.
4. Cache response
Resource ít đổi (vd caller-ids, queues, users) — cache 1-5 phút ở CRM. Không poll mỗi giây.
5. Monitor X-RateLimit-Remaining
Khi Remaining < 5 — slow down chủ động, đừng đợi đến 0 rồi retry.
Idempotency
Vấn đề
Khi client retry 1 POST/PATCH/DELETE do timeout, làm sao đảm bảo:
- Không tạo trùng resource (vd 2 lead cùng số).
- Không double charge (vd 2 cuộc gọi cho 1 click).
- Không double action (vd disposition 2 lần cho 1 cuộc gọi).
Giải pháp Zorio: Idempotency-Key
Client gửi header Idempotency-Key: <UUID v4> với mỗi request retry-able.
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"}'Cách Zorio xử lý
- Lần đầu thấy
Idempotency-Key→ xử lý request bình thường, lưu response vào cache với key này (TTL 24h). - Lần sau thấy
Idempotency-Keyđã thấy → trả ngay response cached, KHÔNG xử lý lại.
Endpoint hỗ trợ Idempotency-Key
| Method + endpoint | Lý do cần |
|---|---|
POST /api/pbx/calls/click-to-call | Tránh gọi 2 lần khi mạng glitch |
POST /api/telesales/campaigns | Tránh tạo 2 campaign cùng tên |
POST /api/telesales/campaigns/{id}/leads | Tránh tạo 2 lead trùng |
POST /api/autocall/campaigns | Tránh tạo 2 campaign cùng tên |
POST /api/integrations/sms | Tránh gửi SMS 2 lần (kép phí) |
Yêu cầu với Idempotency-Key
- Format: UUID v4 (
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx). - Unique per request: KHÔNG dùng lại key cho request khác — bạn sẽ nhận response cũ.
- TTL 24 giờ: sau 24h key tự expire, retry với cùng key có thể tạo resource mới.
Sample tích hợp Node.js
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));
}
}
}→ Dù retry 3 lần, chỉ 1 cuộc gọi được tạo.
Lưu ý
- Body request phải giống hệt giữa các lần retry (cùng
Idempotency-Key). Khác body → HTTP 422 với"message": "Idempotency-Key đã dùng với body khác.". - Endpoint không hỗ trợ Idempotency-Key sẽ ignore header này — không gây lỗi.
Concurrency control
Optimistic locking với If-Match
Một số endpoint update hỗ trợ If-Match để chống race:
PUT /api/pbx/extensions/1001
If-Match: "etag-abc123"Nếu resource đã đổi giữa lúc client GET → ETag mismatch → HTTP 412 { "message": "Resource đã thay đổi, vui lòng GET lại." }. CRM cần re-fetch + re-apply changes.
Connection limit
- Tối đa 20 connection đồng thời per token.
- Vượt → HTTP 429 với body
{ "message": "Too many concurrent connections." }.
Tài liệu liên quan
- Bearer (phiên user)
- API Token
- HTTP status codes — chi tiết 429
- Webhook retry policy — idempotency phía receiver
