Skip to navigation

Rate limits

Understand request limits and handle traffic safely.
View as Markdown

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.

LevelApplies toEnforced?
Per-command-in-callEach command type on a call, normally with its own bucket; playback seek and Recording mask/unmask controls share the seek bucket.Yes — 429
Per-roomRoom commands, aggregated across all participants in a room, per command type.Yes — 429
GlobalCommands not tied to a session, including room creation and listings.Yes — 429

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:

HTTP/1.1 429 Too Many Requests
Retry-After: 1
{
"code": "rate_limited",
"message": "Command rate limit exceeded. See Retry-After header."
}

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.

CommandCapacity / Refill (per sec)Notes
playback/seek20 / 20Shares the seek bucket with Recording mask and unmask controls.
playback/stop20 / 20
playback/pause20 / 20
playback/resume20 / 20
playback/restart20 / 20
recordings/{recording_uuid}/stop20 / 20
recordings/{recording_uuid}/mask20 / 20Shares the seek bucket with playback seek and Recording unmask.
recordings/{recording_uuid}/unmask20 / 20Shares the seek bucket with playback seek and Recording mask.
playback/play8 / 4Plays files, a live stream, or silence.
dtmf/collect (POST + DELETE)4 / 0.8The prompt-and-collect POST and cancel DELETE each have an independent bucket with the same capacity and refill rate.
POST .../recordings4 / 1Creates a Session Recording.
room/mute (conference.mute)15 / 10
room/unmute (conference.unmute)15 / 10

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.

Room commandCapacity / Refill (per sec)Scope
POST /rooms/{room_id}/participants100 / 30per-room
DELETE /rooms/{room_id}/participants/{uuid}100 / 30per-room
POST /rooms/{room_id}/playback/pause15 / 10per-room
POST /rooms/{room_id}/playback/seek15 / 10per-room
POST /rooms/{room_id}/playback/volume15 / 10per-room
POST /rooms/{room_id}/playback/stop15 / 10per-room
POST /rooms/{room_id}/playback/play8 / 3per-room
POST /rooms/{room_id}/playback/record8 / 3per-room; start and stop share this bucket
POST /rooms/{room_id}/playback/record/stop8 / 3per-room; start and stop share this bucket

Global Limits

Creating a room and listing rooms or sessions use global limits. Each limit is shown as capacity / refill-per-second.

CommandHard limitNotes
POST /rooms (create)10 / 2Room creation
GET /sessions (list)40 / 20
GET /rooms (list)40 / 20

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.

HTTP/1.1 403 Forbidden
{
"code": "forbidden",
"message": "Maximum number of rooms reached"
}

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.

HTTP/1.1 409 Conflict
{
"code": "room_full",
"message": "Room is full"
}

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. For 429 responses with code: rate_limited, wait at least the number of seconds in the Retry-After response header before retrying. The JSON body does not contain retry timing. After the burst capacity is exhausted, requests above the refill rate receive 429 responses.
  • 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/play command with type: files.
  • Back off on repeated rate limits. If a retry returns rate_limited again, wait for the new Retry-After delay and increase the wait between subsequent retries.