Rate limits
The API uses token-bucket rate
limiting. Each applicable limit allows a short burst followed by a sustained
request rate. Requests beyond an applicable limit receive
429 Too Many Requests.
How Token Buckets Work
Each limit is a bucket with two numbers:
- Capacity — the maximum burst. The bucket starts full; each admitted operation consumes one token from each applicable operation bucket.
- Refill / sec — the sustained rate at which tokens are replenished.
If at least one token is available, the API accepts the request and consumes a
token. If the bucket is empty, the API rejects the request with a 429. The
Retry-After response header tells you how many seconds to wait. An idle bucket
refills back to full, so you regain your full burst allowance after a quiet
period.
Example: a bucket with capacity 8 and refill 4/s lets you send 8 requests back-to-back, then accepts roughly 4 more per second sustained. If you drain it, waiting one second restores 4 tokens.
Limit Levels
Every admitted operation is checked against each applicable operation bucket. Authentication failures do not consume a token. An idempotency replay returns the stored response without re-executing or consuming a token.
Buckets are normally per command type. At the per-command and per-room levels, command types normally have their own bucket. The exception is the shared seek group: playback seek and Recording mask/unmask controls draw from one 20 / 20 bucket. A burst of one of those commands can drain the budget available to the others; other command types use independent buckets.
The 429 Response
When you exceed a request rate limit, the API returns 429 Too Many Requests. Read the
integer delay from the Retry-After response header. The JSON body keeps the
uniform code and message error shape:
Check the error code to decide when to retry. For rate_limited, wait at least
the number of seconds in Retry-After, then retry. The human-readable message
may change. Do not look for retry timing in the JSON body.
Include the traceparent response header when contacting support
about a request.
Per-Command Limits
The table below lists each command’s per-call bucket as
capacity / refill-per-second. The three commands in the shared seek group
draw from the same bucket rather than receiving a bucket apiece.
Room-participant volume handlers do not use per-command-in-call buckets. Room-participant add and delete endpoints use only the per-room limits below.
Per-Room Limits
Room commands are additionally checked against a per-room bucket that aggregates across all participants in the room.
Global Limits
Creating a room and listing rooms or sessions use global limits. Each limit is shown as capacity / refill-per-second.
Resource Limits (Not a Rate Limit)
This is a count cap, not a rate cap. Everything above bounds how fast you may call; this bounds how many rooms exist at once. The two are independent: staying under the creation rate limit does not exempt you from the room-count cap, and vice versa.
The number of concurrently live rooms is capped. The cap includes rooms that are still being torn down; a room frees its slot only after deletion and teardown finish.
The default room cap is 100 concurrently live rooms. Contact support for
higher limit.
When creating a room would exceed the cap, POST /api/v1/rooms is rejected with
403 Forbidden — not a 429. Because this is not a rate limit, the
response does not include a Retry-After header. The condition clears when you
delete a room and free a slot, not after a fixed delay.
Room participant cap
A second count cap bounds how many participants may be in a single room at once. It is independent of the room-count cap above and of every rate limit. The default is 250 participants per room. Contact support for a higher limit.
When a join (POST /api/v1/rooms/{room_id}/participants) would exceed the cap, it is
rejected with 409 Conflict and code: room_full — not a 429 and not
the room-count 403. The participant is not added. As with the room-count cap, this
is not a rate limit, so the response does not include a Retry-After header.
The condition clears when a participant leaves and frees a slot, not after a fixed
delay.
The cap applies to participants added through the join endpoint.
Monthly SMS quota
Each application has a default quota of 3,000 outbound SMS units
per calendar month in Asia/Jerusalem. The quota resets at midnight on the
first day of the next month. Your application configuration includes the
effective limits.monthly_messages; 0 means unlimited. Contact support to
change the quota.
Each 70 Unicode code points, or remaining fraction, consumes one unit. A 70-character message consumes one unit, 71 characters consume two, and 140 characters consume two. This rule applies to all text, regardless of encoding. The maximum 1,600-character message consumes 23 units.
A validated, authorized request reserves all of its units when admitted for
submission, before contacting the provider. If the remaining quota cannot cover
the entire message, the API rejects it without consuming any units or submitting
it. The text is sent as one request. Later submission failures and failed
deliveries still count. Invalid requests, unauthorized senders, and requests
rejected by the monthly quota do not count. Replaying a stored outcome or
retrying an in-progress request with its original Idempotency-Key does not consume more
units. Retrying after a definitive provider refusal, or reusing a key after its
seven-day expiry, creates a new counted attempt, using its text length.
When the remaining quota cannot cover a message, creating it returns HTTP 429
with code resource_limit_exceeded and makes no submission. Retry after the
quota resets or your limit increases. You can still retrieve messages and use
call operations.
Best Practices
- Honor
Retry-After. For429responses withcode: rate_limited, wait at least the number of seconds in theRetry-Afterresponse header before retrying. The JSON body does not contain retry timing. After the burst capacity is exhausted, requests above the refill rate receive429responses. - Wait for webhooks. For asynchronous operations, use the lifecycle webhook to detect completion instead of polling.
- Batch playback files. Send multiple URLs in one
playback/playcommand withtype: files. - Back off on repeated rate limits. If a retry returns
rate_limitedagain, wait for the newRetry-Afterdelay and increase the wait between subsequent retries.