Rate limits
Rate limits bound how many requests your organization can make in a time window. Use the response headers to pace traffic and the retry delay to recover from a rejected request.
How limits are keyed
Your organization shares one regional limit for each policy, across its API keys and workspaces. Creating another key does not add capacity. Different organizations have separate limits.
Each request consumes one customer policy. Product policies have independent capacity: retrieving message status does not consume the general resource-retrieval limit, and sending an email does not consume the resource-creation limit.
Login, password reset, and other security-sensitive operations have additional abuse protections. Supplier checks and connection limits can also reject requests independently of your plans' rate limits.
Groups
Ordinary API operations use these policies:
| Policy | Operations |
|---|---|
api_get | Retrieve one resource |
api_list | List or search a collection |
api_create | Create a resource |
api_update | Update or upsert a resource |
api_delete | Delete a resource |
Product operations use a named policy in place of the ordinary API policy. Examples include email_send, email_batch, sms_send, whatsapp_send, lookup, and message_status_read. Batch policies count submission requests; the batch's recipient count does not consume extra policy units. See email batch sending and SMS batch sending for batch-size limits.
REST email and SMTP submission share email_send capacity. An SMTP DATA submission consumes one unit; SMTP authentication does not. If the policy denies a submission, the server returns temporary 452 4.3.1 with a retry delay and does not accept the message. Keep the message queued and retry after that delay.
Creating a broadcast uses api_create; starting an existing broadcast uses api_update. Background delivery to its recipients does not consume email_send. Sending allowances and delivery pacing remain separate controls.
The voice_call policy limits inbound and outbound call admission. An exhausted policy refuses the call and records calls_per_second_exceeded; no HTTP response is involved. See Rejected calls.
How your limit is resolved
Each product has its own plan, and each plan sets values for that product's policies: an SMS plan sets sms_send and sms_batch, a Voice plan sets voice_call, and so on. Higher tiers set higher values. Shared policies, such as the api_* group, verify_send, verify_check, and message_status_read, can be set by any product's plan.
Your effective rate for a policy resolves in this order:
- An active organization override, when one exists.
- The highest value among your organization's active product plans that set the policy.
- The policy default, which is the pay-as-you-go baseline.
An override can raise or lower the rate. The policy's time window stays fixed whichever value applies.
Read your effective quota from the RateLimit-Policy response header, which names the policy key alongside the rate and window that applied to that call. To compare tiers, read each plan's rate_limits from the public plan catalog (bird billing plans list --commercial-product sms) or the comparison table on the pricing page.
Raising a limit
If no organization override applies to the policy, upgrade the plan for the product that owns it, on that product's plans page under Billing and payments > Overview. Check the target plan's rate_limits first: the new rate applies only when it is higher than the value your other plans already give. For a shared policy, such as api_get or verify_send, any product's plan that sets the policy can raise it. The higher rate applies once the upgrade's payment succeeds; Plan changes & renewals covers the timing. Plans also raise quotas that are not rate limits, such as Voice concurrent calls.
An active organization override stays in effect whatever plan you choose, and can be lower than the plan's value. If the RateLimit-Policy header shows a rate below your plan's published value, contact support with the policy key to review the override.
If you need more than the highest published plan provides, contact sales with the policy key and your expected traffic.
Response headers
Rate-limit evaluations supply two headers in the IETF Structured Fields format (RFC 9651):
RateLimit-Policy: "email_send";q=1000;w=60
RateLimit: "email_send";r=842;t=35| Header | Meaning |
|---|---|
RateLimit-Policy | The policy that applies: q is the quota (maximum units) and w is the window in seconds. |
RateLimit | Your current state: r is the number of units remaining and t is the seconds until the window resets. |
The quoted string names the policy. In this example, the organization has an effective email_send limit of 1000 submissions per 60 seconds, with 842 remaining and 35 seconds until reset.
Use r and t to slow requests before receiving a 429. The t value is a relative delay in seconds, rather than a Unix timestamp.
When you hit a limit
Your integration must handle 429 responses as part of normal operation. At minimum, honor Retry-After and retry with backoff. A client that also paces itself against the live RateLimit headers (see Response headers) avoids reaching the limit.
An exhausted customer policy returns 429 Too Many Requests with Retry-After in seconds and rate-limit headers showing r=0. An independent abuse or supplier protection can return 429 even when your customer policy has remaining capacity. Follow Retry-After to decide when to retry; it can differ from the policy's t value.
The body uses the standard error envelope:
{
"error": {
"type": "rate_limit_error",
"code": "E01003",
"name": "RateLimited",
"message": "Too many requests. Please retry after the period indicated in the Retry-After header.",
"doc_url": "https://bird.com/docs/api/errors/E01003",
"request_id": "req_01ky7qavkff7qr88vadv6bv948"
}
}Branch on type: rate_limit_error. The human-readable message can change. Read the policy key and retry timing from the headers:
async function sendWithBackoff(url, headers, payload, maxAttempts = 5) {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const response = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify(payload),
});
if (response.status !== 429) return response;
const retryAfter = Number(response.headers.get("Retry-After") ?? 2 ** attempt);
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
}
throw new Error("rate limited after max retries");
}import time
import requests
def send_with_backoff(url, headers, payload, max_attempts=5):
for attempt in range(max_attempts):
response = requests.post(url, headers=headers, json=payload)
if response.status_code != 429:
return response
retry_after = int(response.headers.get("Retry-After", 2 ** attempt))
time.sleep(retry_after)
raise RuntimeError("rate limited after max retries")func sendWithBackoff(req *http.Request, maxAttempts int) (*http.Response, error) {
for attempt := range maxAttempts {
if attempt > 0 && req.Body != nil {
if req.GetBody == nil {
return nil, errors.New("request body cannot be replayed")
}
body, err := req.GetBody()
if err != nil {
return nil, err
}
req.Body = body
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusTooManyRequests {
return resp, nil
}
resp.Body.Close()
wait := 1 << attempt
if s, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil {
wait = s
}
time.Sleep(time.Duration(wait) * time.Second)
}
return nil, errors.New("rate limited after max retries")
}function sendWithBackoff(ClientInterface $http, RequestInterface $request, int $maxAttempts = 5): ResponseInterface
{
for ($attempt = 0; $attempt < $maxAttempts; $attempt++) {
$response = $http->sendRequest($request);
if ($response->getStatusCode() !== 429) {
return $response;
}
$retryAfter = (int) ($response->getHeaderLine('Retry-After') ?: 2 ** $attempt);
sleep($retryAfter);
}
throw new RuntimeException('rate limited after max retries');
}For retries of the same operation, reuse its idempotency key. Keep the request body unchanged.
See SDK concepts for automatic retry and backoff behavior.
Failure mode
The rate limiter fails open: if Bird cannot evaluate a limit, the request proceeds rather than receiving a spurious refusal. Rate limiting protects service capacity. Authentication and authorization remain the security boundaries. A Bird-side limiter outage does not cause a 429.
Next steps
- Errors: the error envelope and how to branch on error types
- Idempotency: safe retries for mutating requests
- SDK concepts: automatic retry and backoff behavior
Related resources
Continue with the documentation, guides and examples for this topic.