# Rate limits

Every Bird API endpoint is rate limited. Limits exist to keep the platform stable for everyone; they are generous enough that well-behaved integrations rarely hit them, and every response tells you exactly where you stand so you never have to guess.

This page is the one home for rate limits: the model, which is the same across all channels, and the numbers, with every group's base rate in the tables below.

## How limits are keyed

Rate limits are resolved per **organization**: every API key and workspace in the organization draws against the same resolved allowance. How that allowance is bucketed depends on the group:

- **Send groups** (`email_send`, `sms_send`, and the other per-product send and batch groups) share one organization-wide bucket. They are account quotas: it doesn't matter which key or workspace sends.
- **Management groups** (`read`, `list`, `write`) bucket per acting credential within the organization, so one runaway script can't drain the whole organization's read budget.
- **Unauthenticated endpoints** (login, signup, password reset) are keyed by client IP, with fixed abuse-prevention thresholds that are not adjustable.

## Groups

Endpoints are grouped into **rate limit groups**, and the group sets the limit. A group covers endpoints with a similar cost profile: single-resource reads share the `read` group, collection queries `list`, management writes `write`, and expensive operations get groups of their own (`email_send`, `email_batch`, `sms_send`, `whatsapp_send`, `verify_send`, and so on). When you hit a limit, only that group is exhausted; running out of send quota does not block you from reading delivery status or managing webhooks.

The three management groups span every product and start at the same base rate for all organizations:

| Group   | Base rate | Covers                                                                        |
| ------- | --------- | ----------------------------------------------------------------------------- |
| `list`  | 150/min   | Collection queries: list messages, events, recipients, domains, suppressions. |
| `read`  | 500/min   | Single-resource lookups: a message by ID, a domain, a webhook, a suppression. |
| `write` | 60/min    | Creates, updates, and deletes: domains, webhooks, suppressions.               |

The management buckets count per acting credential within your organization, so one busy worker's polling can't starve another key's reads. Send buckets are org-wide account quotas, one per product, and every organization starts from the same one-minute-window base rates:

| Group           | Base rate | Covers                                                                                                                           |
| --------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `email_send`    | 10/min    | [`POST /v1/email/messages`](/docs/guides/email/sending-email), plus mailbox sends (replies and forwards).                        |
| `email_batch`   | 5/min     | [`POST /v1/email/batches`](/docs/guides/email/sending-bulk) (one request queues up to 100 messages), plus contact batch imports. |
| `sms_send`      | 10/min    | [`POST /v1/sms/messages`](/docs/guides/sms/sending-sms).                                                                         |
| `sms_batch`     | 5/min     | [`POST /v1/sms/batches`](/docs/guides/sms/sending-sms#batch-sending), up to 100 messages per request.                            |
| `whatsapp_send` | 10/min    | [`POST /v1/whatsapp/messages`](/docs/guides/whatsapp/sending-whatsapp).                                                          |
| `verify_send`   | 10/min    | [`POST /v1/verify/verifications`](/docs/guides/verify/sending-verifications) (sending or resending a code).                      |
| `verify_check`  | 60/min    | [`POST /v1/verify/verifications/check`](/docs/guides/verify/sending-verifications#check-the-code).                               |

Three things to know about the send buckets. They count **requests, not recipients**, which makes batching the volume lever: at base rates, single email sends top out at 10 messages a minute while 5 batch calls queue 500 in the same window, the bulk-sending path for [email](/docs/guides/email/sending-bulk) and [SMS](/docs/guides/sms/sending-sms#batch-sending). `whatsapp_send` is an abuse guard on request volume, not a spend control: WhatsApp is billed per message, so your wallet governs spend. And the verify groups bound your account's request volume only; the per-recipient guardrails that throttle code requests and guesses are separate, documented under [abuse guardrails](/docs/guides/verify/sending-verifications#abuse-guardrails).

The group that matched your request is named in the rate-limit response headers, so you always know which bucket you exhausted.

Treat every number on this page as a starting point, not a constant to build against. Limits change: your plan raises them, a per-organization override replaces them (see [How your limit is resolved](#how-your-limit-is-resolved)), and base rates may be tuned over time. Your effective quota is always advertised on the response itself, so a well-built client discovers its limits from live traffic rather than hardcoding the values above.

## How your limit is resolved

Your effective limit for a group is resolved from three layers:

1. **Base**: the default that applies to every organization.
2. **Plan**: your subscription plan raises the limit for specific groups.
3. **Override**: a per-organization override, arranged with Bird, for customers whose volume outgrows their plan's ceiling. An active override replaces the base and plan values.

In practice the base is where every organization starts, your plan raises it, and an override is the escape hatch when you need more. If your traffic is approaching your plan's ceiling, contact support; overrides are a normal part of scaling on Bird, not an exception process. Your current effective quota for a group is advertised on every response in the `RateLimit-Policy` header, so you can read it from live traffic.

## Response headers

Every response from a rate-limited endpoint carries two headers in the IETF Structured Fields format ([RFC 9651](https://www.rfc-editor.org/rfc/rfc9651)):

```text
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 (max requests) and `w` is the window in seconds.                  |
| `RateLimit`        | Your current state: `r` is the number of requests remaining and `t` is the seconds until the window resets. |

The quoted string names the group that matched the request. In this example, the org has an effective `email_send` limit of 1000 requests per 60 seconds, with 842 remaining and 35 seconds until reset.

Pace yourself against `r` and `t` proactively rather than only reacting to `429`s: a client that slows down as `r` approaches zero never gets throttled at all. Bird uses this format instead of the older `X-RateLimit-*` convention because it is unambiguous (`t` is always seconds from now, never a Unix timestamp) and because a single response can express multiple policies if an endpoint is ever subject to more than one.

## When you hit a limit

Rate-limit handling belongs in every production integration: a `429` is a normal, expected response, not an exceptional failure. At minimum, your client should honor `Retry-After` and retry with backoff; a client that also paces itself against the live `RateLimit` headers (see [Response headers](#response-headers)) avoids being throttled at all.

An exhausted limit returns `429 Too Many Requests` with a `Retry-After` header (in seconds, matching the `t` value) and both `RateLimit` headers, with `r=0` and the name of the group you exhausted. The body is the standard [error envelope](/docs/guides/errors); this one is a real response:

```json
{
  "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`, not the message text. The group name and retry timing are in the headers, not the body:

```python
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")
```

A rate-limited request is rejected before it does any work: it does not consume an [idempotency key](/docs/guides/idempotency), so retrying with the same key is always safe.

The [Bird SDKs](/docs/sdks/concepts) handle all of this automatically: they detect `429`s, honor `Retry-After`, and retry with backoff, so most SDK users never write this code.

## Failure mode

The rate limiter **fails open**: if its backing store is unavailable, requests are allowed rather than spuriously refused. Rate limiting is a protective measure, not a security boundary; authentication and authorization are always enforced regardless of the limiter's health. You will never receive a `429` caused by a Bird-side outage.

## Next steps

- [Errors](/docs/guides/errors): the error envelope and how to branch on error types
- [Idempotency](/docs/guides/idempotency): safe retries for mutating requests
- [SDK concepts](/docs/sdks/concepts): automatic retry and backoff behavior