# Error responses

Every failed request returns the same JSON envelope under a top-level `error` key, with an HTTP status that tells you the coarse category. This page is the wire contract; for guidance on branching, retries, and the code catalog philosophy, see [Errors](/docs/guides/errors).

```json
{
  "error": {
    "type": "validation_error",
    "code": "E01001",
    "name": "ValidationError",
    "message": "Request validation failed.",
    "doc_url": "https://bird.com/docs/api/errors/E01001",
    "request_id": "req_01krdgeqcxet5s7t44vh8rt9mg",
    "details": [
      { "param": "contact_id", "message": "this field is reserved and not yet supported" },
      { "param": "topic_id", "message": "this field is reserved and not yet supported" }
    ]
  }
}
```

## Envelope fields

| Field         | Always present | Description                                                                                                                                                   |
| ------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type`        | Yes            | Broad category for coarse branching: a closed enum (`auth_error`, `validation_error`, `rate_limit_error`, ...).                                               |
| `code`        | Yes            | Opaque, stable identifier matching `E\d{5}`. Unique, never renamed, never reused; the canonical thing to match on.                                            |
| `name`        | Yes            | Human-readable slug (`ValidationError`) for log readability. Always paired with `code`, never a replacement for it.                                           |
| `message`     | Yes            | Human-readable description. Not stable; display or log it, never parse it.                                                                                    |
| `doc_url`     | Yes            | Stable link to the documentation page for this `code`.                                                                                                        |
| `request_id`  | Yes            | Correlation ID, also returned as the `X-Request-Id` response header. Quote it in support requests.                                                            |
| `param`       | No             | The offending field, when a single field is at fault.                                                                                                         |
| `details`     | No             | Per-field validation failures as `{param, message}` objects, where `param` is a dotted path like `to[0].email`. Present only on `validation_error` responses. |
| `vendor_code` | No             | Verbatim code from a downstream system (an SMTP reply code, a payment decline code) when one is worth acting on.                                              |

## HTTP status mapping

Each `type` maps to exactly one HTTP status, so the status and the envelope never disagree.

<!-- bird:error-types -->

| Status | `type`                      | Meaning                                                                                                                                                       |
| ------ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `bad_request_error`         | The request was malformed: an unparseable body or an invalid header (for example, a bad `Idempotency-Key`).                                                   |
| `401`  | `auth_error`                | The request carried missing, invalid, or revoked credentials. See [Authentication](/docs/api/authentication).                                                 |
| `402`  | `billing_error`             | The request requires a payment method, balance, or plan the organization does not have. See [Billing and usage](/docs/guides/billing-and-usage).              |
| `403`  | `permission_error`          | The credentials are valid, but not allowed to perform this request. See [Authentication](/docs/api/authentication).                                           |
| `404`  | `not_found_error`           | No route matches this path, or the resource does not exist in this workspace. See [Regions](/docs/api/regions).                                               |
| `409`  | `conflict_error`            | The request conflicts with the current state of the resource, including idempotency conflicts (`E01004`, `E01005`). See [Idempotency](/docs/api/idempotency). |
| `410`  | `gone_error`                | The resource existed but has been permanently removed.                                                                                                        |
| `412`  | `precondition_error`        | A precondition for this request was not met.                                                                                                                  |
| `413`  | `payload_too_large_error`   | The request body exceeds the maximum allowed size.                                                                                                            |
| `421`  | `misdirected_error`         | The request reached a region that cannot serve it. See [Regions](/docs/api/regions).                                                                          |
| `422`  | `delivery_error`            | The message was accepted as a request but cannot be delivered as addressed.                                                                                   |
| `422`  | `validation_error`          | The request body parsed, but one or more values are invalid.                                                                                                  |
| `425`  | `too_early_error`           | The request arrived before Bird is willing to process it.                                                                                                     |
| `429`  | `rate_limit_error`          | A rate-limit group is exhausted for this workspace. See [Rate limits](/docs/guides/rate-limits).                                                              |
| `500`  | `internal_error`            | Something failed on Bird's side while processing the request. See [Idempotency](/docs/api/idempotency).                                                       |
| `501`  | `not_implemented_error`     | The endpoint is declared in the API but not implemented yet.                                                                                                  |
| `503`  | `service_unavailable_error` | A dependency Bird needs for this request is temporarily unavailable.                                                                                          |

<!-- /bird:error-types -->

## Handling errors in the SDKs

Each [SDK](/docs/sdks/concepts) maps the envelope onto its language's native error model and carries every envelope field (`type`, `code`, `message`, `doc_url`, `request_id`, ...) on the error value.

<!-- bird:snippet email.errors -->

```typescript
import { BirdRateLimitError, BirdValidationError, BirdAPIError } from "@messagebird/sdk";

try {
  await bird.email.send({
    from: { email: "onboarding@messagebird.dev", name: "Bird" },
    to: ["delivered@messagebird.dev"],
    subject: "Hello from Bird",
    html: "<p>My first Bird email.</p>",
  });
} catch (err) {
  if (err instanceof BirdRateLimitError) console.log(`rate limited — retry in ${err.retryAfter}s`);
  else if (err instanceof BirdValidationError) console.error(err.details);
  else if (err instanceof BirdAPIError) console.error(err.code, err.requestId);
  else throw err;
}
```

<!-- bird:snippet email.errors -->

```go
if err != nil {
	var rle *bird.RateLimitError
	var ve *bird.ValidationError
	var ae *bird.APIError
	switch {
	case errors.As(err, &rle):
		fmt.Println("rate limited; retry after", rle.RetryAfter)
	case errors.As(err, &ve):
		for _, d := range ve.Details {
			fmt.Printf("%s: %s\n", d.Param, d.Message)
		}
	case errors.As(err, &ae):
		fmt.Printf("API error %s (status %d, request %s)\n", ae.Code, ae.StatusCode, ae.RequestID)
	default:
		log.Print(err) // transport: *bird.ConnectionError or *bird.TimeoutError
	}
}
```

<!-- bird:snippet email.errors -->

```python
from bird import APIStatusError, RateLimitError, ValidationError

try:
    client.email.send(
        from_={"email": "onboarding@messagebird.dev", "name": "Bird"},
        to=["delivered@messagebird.dev"],
        subject="Hello from Bird",
        text="My first Bird email.",
    )
except RateLimitError as err:
    print("rate limited; retry after", err.retry_after)
except ValidationError as err:
    print(err.status_code, err.details)
except APIStatusError as err:
    print(err.status_code, err.code, err.request_id)
```

## Error catalog

<!-- bird:error-catalog -->

Every error code the public Bird API returns, split into one page per code range. Each range page lists its codes, and each code has its own page with the cause and what to do. The `doc_url` on any error response links directly to that code's page.

| Range                               | Area                        | Codes               |
| ----------------------------------- | --------------------------- | ------------------- |
| [`E01xxx`](/docs/api/errors/E01xxx) | Infrastructure              | 24 codes            |
| [`E02xxx`](/docs/api/errors/E02xxx) | Auth & identity             | 6 codes             |
| [`E04xxx`](/docs/api/errors/E04xxx) | Email sending & delivery    | 26 codes, 1 retired |
| [`E05xxx`](/docs/api/errors/E05xxx) | Domains & DNS               | 19 codes            |
| [`E06xxx`](/docs/api/errors/E06xxx) | Webhooks                    | 6 codes             |
| [`E07xxx`](/docs/api/errors/E07xxx) | Wallet                      | 2 codes             |
| [`E10xxx`](/docs/api/errors/E10xxx) | Quotas                      | 4 codes, 2 retired  |
| [`E11xxx`](/docs/api/errors/E11xxx) | IP pools & dedicated IPs    | 3 codes             |
| [`E12xxx`](/docs/api/errors/E12xxx) | SMS sending & delivery      | 22 codes, 1 retired |
| [`E13xxx`](/docs/api/errors/E13xxx) | Verify                      | 6 codes             |
| [`E15xxx`](/docs/api/errors/E15xxx) | WhatsApp sending & delivery | 4 codes             |
| [`E17xxx`](/docs/api/errors/E17xxx) | Agent mailboxes             | 12 codes, 1 retired |

<!-- /bird:error-catalog -->

## Related

- [Errors concepts](/docs/guides/errors): branching strategy, validation details, and `vendor_code` semantics
- [Authentication](/docs/api/authentication): credentials behind `401` and `403`
- [Idempotency-Key header](/docs/api/idempotency): the `409` conflict errors and safe retries
- [Rate limits](/docs/guides/rate-limits): groups, headers, and handling `429`