> ## 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.

# Errors

> The error envelope, status codes, request IDs, rate limits, and typed SDK error handling.

Drin uses conventional HTTP status codes and returns a consistent JSON envelope on any non-2xx response, so you can branch on a stable machine-readable type.

```json Error envelope theme={null}
{
  "error": {
    "type": "validation_error",
    "message": "`to` must contain at least one recipient.",
    "param": "to"
  }
}
```

| Field     | Description                                                      |
| --------- | ---------------------------------------------------------------- |
| `type`    | Machine-readable category — switch on this (see table below).    |
| `message` | Human-readable explanation. Safe to log; don't parse it.         |
| `code`    | Optional finer-grained code, when present.                       |
| `param`   | Optional offending request field, when the error is about input. |

## Error types

| `type`                 | Status        | Meaning                                                                                                                     |
| ---------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `validation_error`     | `400` / `422` | The request was malformed or failed validation (`param` points at the field).                                               |
| `authentication_error` | `401`         | Missing, malformed, or revoked API key.                                                                                     |
| `permission_error`     | `403`         | The key is valid but not allowed to do this — wrong project, sandbox restriction, or plan limit.                            |
| `not_found`            | `404`         | No such resource for this account.                                                                                          |
| `conflict`             | `409`         | Conflicts with current state — e.g. an idempotency replay with a different body, or deleting a domain with sending history. |
| `suppressed`           | `409`         | Every recipient is on this project's suppression list, so nothing was sent.                                                 |
| `rate_limited`         | `429`         | Too many requests — back off and retry (see below).                                                                         |
| `internal_error`       | `5xx`         | Something went wrong on our side. Safe to retry idempotently.                                                               |

## Request IDs

Every response echoes a request id in the `X-Request-Id` header. Include it when you contact support — it lets us find the exact request in our logs.

## Rate limits

When you exceed a limit you get a `429` with a `Retry-After` header (seconds). Wait that long, then retry. The SDK reads `Retry-After` and backs off automatically.

## Handling errors in the SDK

Each `type` maps to a typed error subclass, so you can branch with `instanceof` or on `err.type`:

```typescript Node.js theme={null}
import {
  DrinError,
  DrinValidationError,
  DrinRateLimitError,
  DrinSuppressedError,
} from "@drin00/sdk";

try {
  await drin.emails.send({ /* … */ });
} catch (err) {
  if (err instanceof DrinSuppressedError) {
    // every recipient is suppressed — nothing was sent
  } else if (err instanceof DrinRateLimitError) {
    await sleep((err.retryAfter ?? 1) * 1000);
  } else if (err instanceof DrinValidationError) {
    console.error("Bad field:", err.param, err.message);
  } else if (err instanceof DrinError) {
    console.error(err.type, err.status, err.requestId);
  }
}
```

<Note>
  **Automatic retries.** The SDK auto-retries `429` and `5xx` responses with exponential backoff and full jitter (default: 2 retries). A `POST` is only retried when you pass an [Idempotency-Key](/authentication), so sends are never duplicated.
</Note>
