> ## Documentation Index
> Fetch the complete documentation index at: https://docs.drin.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Rate limits

> How Drin rate-limits requests, the 429 response, the Retry-After header, and how the SDK backs off automatically.

To keep the platform fast and fair, Drin limits how quickly a project can call the API. When you exceed a limit you get a 429 with a Retry-After header telling you exactly how long to wait.

Limits are enforced per project and scale with your plan. Sending also has a separate *volume* ceiling (daily quota and a per-tenant trust tier) that's independent of request rate — exceeding that surfaces as a `permission_error`, not a `rate_limited` one. This page is about request rate.

## The 429 response

Over the limit, you get a `429` with a `rate_limited` error envelope and a `Retry-After` header (seconds). Wait that long, then retry.

```http 429 Too Many Requests theme={null}
HTTP/1.1 429 Too Many Requests
Retry-After: 2
Content-Type: application/json

{
  "error": {
    "type": "rate_limited",
    "message": "Too many requests. Retry after 2 seconds."
  }
}
```

## Backing off

Read `Retry-After` and sleep for that many seconds before retrying. The SDK does this for you — it reads the header and retries with exponential backoff and full jitter (default: 2 retries).

<CodeGroup>
  ```typescript Node.js theme={null}
  import { DrinClient, DrinRateLimitError } from "@drin00/sdk";

  const drin = new DrinClient({ apiKey: process.env.DRIN_API_KEY });

  try {
    await drin.emails.send(message);
  } catch (err) {
    if (err instanceof DrinRateLimitError) {
      // err.retryAfter is the Retry-After value (seconds)
      await new Promise((r) => setTimeout(r, (err.retryAfter ?? 1) * 1000));
      await drin.emails.send(message);
    } else {
      throw err;
    }
  }
  ```

  ```bash cURL theme={null}
  # A naive shell back-off: honor Retry-After, then retry once
  resp=$(curl -s -w "\n%{http_code}" https://api.drin.run/v1/emails \
    -H "Authorization: Bearer $DRIN_API_KEY" \
    -H "Content-Type: application/json" \
    -D headers.txt \
    -d @message.json)

  if tail -n1 <<<"$resp" | grep -q 429; then
    sleep "$(grep -i '^retry-after:' headers.txt | tr -dc '0-9')"
    # …retry the request…
  fi
  ```
</CodeGroup>

<Warning>
  **Don't hammer a 429.** Retrying immediately, without honoring `Retry-After`, only extends the limit window. Always wait the full interval; add jitter so concurrent workers don't retry in lockstep.
</Warning>

## Staying under the limit

* Collapse many sends into one request with [`POST /v1/emails/batch`](/api-reference/emails/batch) — up to 100 messages count as a single call.
* Use a worker pool with bounded concurrency rather than firing every request at once.
* Schedule non-urgent sends with `scheduledAt` instead of blasting them in real time.
* Treat `429` as backpressure: slow down, don't spin.

<Note>
  **See also.** The full error catalogue, including `rate_limited`, lives on [Errors](/api-reference/errors).
</Note>
