Platform

What is API rate limiting, and how do I handle a 429?

Rate limiting is a cap on how many requests you can make in a window, and the interesting part is not the number. It is that on Bird the number is not a constant, so a client that hardcodes one is wrong in both directions: it either throttles itself below what it is entitled to, or it plans around a figure that has been raised.

How does Bird decide my limit?

Three layers, resolved in order. A base rate applies to every organization, your plan raises it for specific groups, and an override arranged with Bird replaces both when your volume outgrows the plan. So two organizations calling the same endpoint can have different limits, and yours can change without you doing anything.

Limits attach to a group rather than to an endpoint. Endpoints of similar cost share one, with dedicated groups for the expensive operations. That matters more than it sounds: exhausting your send quota does not stop you reading delivery status or managing webhooks, because only the group you exhausted is empty.

How a group is bucketed depends on what it protects:

  • Send groups are one organization-wide quota per product. It does not matter which key sends, because these are account quotas.
  • Management groups, the read, list and write ones, bucket per acting credential inside the organization, so one runaway script cannot drain everyone else's budget.
  • Unauthenticated endpoints like login and password reset are keyed by client IP, with fixed thresholds that are not adjustable.

One more property is worth knowing before you design a sender: the send buckets count requests, not recipients. So a batch endpoint multiplies your throughput without spending more of the bucket, which is often a better answer to a rate limit than asking for a higher one.

It only pays if you have recipients to group, though, because a batch group's own rate is lower than its single-send equivalent. Batching wins from about three recipients a call upwards. A workload that can only ever carry one, a transactional message triggered by one person's action, is faster on the send group and would halve its rate by moving.

How do I know my limit without asking?

Read it off the response. Every rate-limited endpoint returns two headers in the structured-field format from RFC 9651:

RateLimit-Policy: "email_send";q=1000;w=60
RateLimit: "email_send";r=842;t=35

The quoted string names the group that matched your request. On RateLimit-Policy, q is the quota and w is the window in seconds. On RateLimit, r is how many requests you have left and t is the seconds until the window resets.

Those particular numbers are an illustration from our own documentation, not your quota. The point of the headers is that you never have to look a number up: yours arrive on every call.

Two details make them easy to use correctly. t is always seconds from now rather than a Unix timestamp, so there is no ambiguity to get wrong. And a single response can carry more than one policy, when an endpoint is subject to more than one limit.

A client that watches r and slows down as it approaches zero does not get rate limited at all, which is the outcome worth designing for. Handling 429 is the floor, not the goal.

What does a 429 actually return?

An exhausted limit returns 429 Too Many Requests with Retry-After in seconds, both rate-limit headers with r=0, and the name of the group you exhausted, so you know which bucket to slow down rather than throttling everything.

The body is the standard error envelope, and one field is the one to branch on:

{
  "error": {
    "type": "rate_limit_error",
    "code": "E01003",
    "name": "RateLimited"
  }
}

Branch on type. The message is written for a human and can be reworded; the type and code are the contract.

How should my client handle it?

At minimum, honour Retry-After and retry with backoff. Every production integration needs that, because a 429 is a normal operating condition rather than an error state.

Better, pace yourself against the live headers so you approach the limit and stop rather than hitting it. The information to do that is already on every response you have received.

If you are using a Bird SDK, this is already done: they detect a 429, honour Retry-After and retry with backoff, so most SDK users never write the loop.

And if the limit is genuinely too low for your traffic, ask rather than engineering around it. Overrides exist for exactly that, and they are routine for higher-volume organizations.

Is it safe to retry?

Yes, and more safely than most failures.

A rate-limited request is rejected before it does any work, so it never consumes an idempotency key. Retrying with the same key is always safe, which is not something you can assume of a 5xx.

One more property that affects how you reason about outages: the rate limiter fails open. If Bird cannot evaluate a limit, the request proceeds rather than being refused, so a limiter problem on our side does not show up as a 429 on yours.

That behaviour splits along a line worth knowing, because it is deliberate rather than incidental. The two mechanisms that protect capacity fail open: the rate limiter, and the idempotency store, which processes your request without deduplication rather than refusing it. The two that protect access fail closed: authentication and authorization refuse when they cannot decide. So an infrastructure problem at Bird can cost you a duplicate, and it cannot let anyone in.

Rate limits has every group, its base rate and the endpoints it covers, plus retry-and-backoff examples in four languages.

Buduj na tej samej sieci.

Testowy klucz API otrzymasz od razu. Dostęp produkcyjny odblokujesz po dodaniu metody płatności i zweryfikowaniu nadawcy.

Zacznij od jednego kanału.
Dodaj kolejne, gdy będziesz gotowy.

Testowy klucz API otrzymasz od razu. Dostęp produkcyjny odblokujesz po dodaniu metody płatności i weryfikacji nadawcy.

Używasz Claude Code, Cursor lub Codex? Skopiuj prompt konfiguracyjny, a Twój agent zainstaluje za Ciebie Bird CLI i umiejętności. Wybierz swój:

Cursor