Documentation
Sign inGet started

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 describes the model, which is the same across all channels. The actual numbers live on per-channel pages — see Email rate limits.

How limits are keyed

Your quota is shared across everything you send: every authenticated request counts against one account-wide quota, regardless of which API key or workspace made it. (Quotas are organization-level, so multiple keys or workspaces draw from the same allowance.) Unauthenticated endpoints (login, signup, password reset) are keyed by client IP instead, 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 typically covers endpoints with a similar cost profile: single-resource reads share one group, collection queries another, management writes a third, and expensive operations like sending get groups of their own. 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 group that matched your request is named in the rate-limit response headers, so you always know which bucket you exhausted.

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. Tier — your subscription tier can raise the limit for specific groups. A tier never lowers a limit below base.
  3. Override — a per-organization override, arranged with Bird, for customers whose volume outgrows their tier's ceiling. An active override takes precedence over the base and tier values.
In practice: the base is the floor, your tier raises it, and an override is the escape hatch when you need more. If your traffic is approaching your tier's ceiling, contact support — overrides are a normal part of scaling on Bird, not an exception process.
You can list your organization's current effective limits with GET /v1/organization/rate-limits.

Response headers

Every response from a rate-limited endpoint carries two headers in the IETF Structured Fields format (RFC 9651):
Ejemplo de código
RateLimit-Policy: "email_send";q=1000;w=60
RateLimit: "email_send";r=842;t=35
HeaderMeaning
RateLimit-PolicyThe policy that applies: q is the quota (max requests) and w is the window in seconds.
RateLimitYour 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 the example above, the org has an effective email_send limit of 1000 requests per 60 seconds, with 842 remaining and 35 seconds until reset.
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

An exhausted limit returns 429 Too Many Requests with a Retry-After header (in seconds, matching the t value) and the standard error envelope:
Ejemplo de código
HTTP/1.1 429 Too Many Requests
Retry-After: 35
RateLimit-Policy: "email_send";q=1000;w=60
RateLimit: "email_send";r=0;t=35
Content-Type: application/json
Ejemplo de código
{
  "type": "rate_limit_error",
  "code": "E01003",
  "message": "Too many requests. Please retry after the period indicated in the Retry-After header.",
  "request_id": "req_abc123"
}
Branch on type: rate_limit_error, not the message text. The group name and retry timing are in the headers, not the body:
Ejemplo de código
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, so retrying with the same key is always safe.
The Bird SDKs handle all of this automatically: they detect 429s, 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.

Per-channel limits

  • Email — send and batch groups, management traffic, and tier ceilings.