Documentation menu

Operate

Rate limits & idempotency

Two different protections, easy to conflate: traffic caps stop you from placing calls faster than your account allows, and idempotency keys stop a retried request from placing a second call by accident.

Traffic caps

Every account has two independent limits on outbound call placement:

CapDefaultRefusal
Concurrency — simultaneous live calls50429, reason LIMIT.0429
CPS — new calls started per second10429, reason LIMIT.0430

Both are per-account, checked before a call is placed — a refused call is never dialled and never billed. If your traffic legitimately needs a higher ceiling, contact support; these are account settings, not hardcoded platform limits.

A cap refusal on POST /calls/originate returns the standard error body:

// HTTP 429
{ "detail": "Concurrency cap reached (50 simultaneous calls)." }

Distinguish "slow down / raise my cap" (LIMIT.*) from "top up your wallet" (BILL.*, 402) — they're never the same code, because the fix is different. See reason codes for the full table.

Handling a 429

  • Back off before retrying — an immediate retry against a CPS cap will just hit it again.
  • For a concurrency cap, wait for a call to end (watch the event stream for call.completed/call.failed) rather than polling.
  • If you're consistently hitting a cap under normal traffic, that's a sign to ask us to raise it, not to build a bigger retry loop around it.

Idempotency

Any endpoint that spends money or places a call accepts an Idempotency-Key header. Send the same key on a retry — after a timeout, a dropped connection, anything — and you get back the original response instead of a second call or a second charge.

curl https://sauti-pbx.services.co.ke/api/calls/originate \
  -H "Authorization: Bearer sk_live_..." \
  -H "Idempotency-Key: order-4021" \
  -d '{ "to": "+254711111111", "from": "+254709080010", "url": "https://yourapp.com/handle-call" }'

Rules

  • Scope. Keys are per-account — two accounts can safely reuse the same key.
  • Window. A key is remembered for 24 hours from its first use, then it's free to reuse for a new operation.
  • In flight. If a request with that key is still being processed, a concurrent duplicate gets 409, not a second call — retry shortly after.
  • Completed. Once the original request finished, a retry with the same key replays that exact response verbatim (same status, same body) — it does not re-run the operation.
  • Failed attempts don't burn the key. If the operation was refused (402, 429, validation error), the key is freed immediately — a legitimate retry after fixing the problem is not blocked.
  • Pick a key that identifies the business action you're taking (an order id, a job id) — not a random value per HTTP attempt, or you lose the protection.

No key, no protection: without Idempotency-Key, a retried POST /calls/originate after a timeout can place a second call. Always send one on money-moving requests.