# Errors

Every failed Bird API request returns the same JSON envelope, nested under a top-level `error` key. This is a real response to a send request with an empty body:

```json
{
  "error": {
    "type": "validation_error",
    "code": "E01001",
    "name": "ValidationError",
    "message": "Request has 1 validation error.",
    "doc_url": "https://bird.com/docs/api/errors/E01001",
    "request_id": "req_01ky7q3hckecgv6d7jpq865532",
    "details": [{ "param": "body", "message": "missing properties 'from', 'to'" }]
  }
}
```

The HTTP status follows from the `type`: `400` for malformed requests, `401`/`403` for auth and permissions, `402` for billing, `404` for missing resources, `409` for conflicts, `412` for unmet preconditions, `422` for validation and business-rule failures, `429` for rate limits, `5xx` for Bird-side failures. Status alone tells you the category; the envelope tells you exactly what happened.

## The envelope fields

| Field         | Role                                                                                                                                                                                                                                             |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type`        | Broad category for coarse branching: `validation_error`, `auth_error`, `permission_error`, `not_found_error`, `conflict_error`, `rate_limit_error`, `billing_error`, `internal_error`, and a handful of others. A closed enum that grows rarely. |
| `code`        | Opaque, stable identifier (`E01001`). **The canonical reference**: unique, never renamed, never reused. When an error is retired, its code is permanently reserved.                                                                              |
| `name`        | Human-readable slug (`ValidationError`) for log readability. Always paired with `code`, never a replacement for it.                                                                                                                              |
| `message`     | Human-readable description. **Not stable**: wording changes without notice. Display it, log it, never parse it.                                                                                                                                  |
| `param`       | For input-related errors, the offending field. Omitted when not applicable.                                                                                                                                                                      |
| `doc_url`     | Stable link to the docs page for this code.                                                                                                                                                                                                      |
| `request_id`  | Always present, and also returned as the `X-Request-Id` response header. Quote it in support requests; it lets Bird trace the exact request.                                                                                                     |
| `details`     | Per-field validation problems. Present only on `validation_error` responses.                                                                                                                                                                     |
| `remediation` | A human-readable next step to resolve the error. Present when a recovery is known.                                                                                                                                                               |
| `next`        | Operations that resolve the error, in the order to try them. Present for errors with a well-defined recovery, such as unmet preconditions.                                                                                                       |
| `vendor_code` | Verbatim code from a downstream system (an SMTP response code, a payment decline code). Present only when Bird is surfacing an external system's code you may want to act on.                                                                    |
| `unmet_gates` | The verification requirements blocking the action, each with the flow that resolves it. Present only when an action is blocked pending verification.                                                                                             |

**Branch on `type` for coarse handling and `code` for specific handling, never on `message`.** A typical client switches on `type` (retry on `rate_limit_error`, surface `validation_error` to the user, page someone on `internal_error`) and matches individual `code` values only for the few errors it handles specially.

The opacity of codes is deliberate: a code like `E04012` doesn't pretend to be self-documenting, so every code gets a real docs page (its `doc_url`) describing the exact cause and what to do about it. The full catalog lives in the [error reference](/docs/api/errors).

## Validation failures: one code, many details

Field-level validation does not get a separate code per field-and-failure combination. Every validation failure is `E01001 ValidationError` with a `details` array listing each field-level problem as `{param, message}`, the way the captured send failure lists its missing properties. The `message` strings inside `details` are human-readable, not machine-stable: use `param` to map problems to form fields, use the message for display.

## Recovery guidance: remediation and next

Errors with a known fix carry it in the envelope. This is a real response to a webhooks call made with an API key that lacks the required scope:

```json
{
  "error": {
    "type": "permission_error",
    "code": "E02035",
    "name": "InsufficientScope",
    "message": "This request requires the \"webhooks:read\" scope, which your credential has not been granted.",
    "param": "webhooks:read",
    "doc_url": "https://bird.com/docs/api/errors/E02035",
    "request_id": "req_01ky7q4665emc9tw1pxkptaqwq",
    "remediation": "Re-authenticate with a credential that has been granted the required scope, then retry."
  }
}
```

`remediation` is a sentence for a human or an agent log; `next`, when present, lists the API operations that resolve the error in the order to try them (verify a domain, then retry the send). Agents and CLIs can execute `next` directly; interactive clients can show `remediation` as-is.

## Handling errors well

- **Retry `429` and `5xx`, nothing else by default.** Honor `Retry-After` on rate limits (see [Rate limits](/docs/guides/rate-limits)), use exponential backoff on `5xx`, and send an [`Idempotency-Key`](/docs/guides/idempotency) so retries of mutating requests are safe.
- **Log `code`, `name`, and `request_id` together.** The code is what you'll search the docs and your own logs for; the request ID is what support needs.
- **Tolerate new codes and types.** New error codes ship regularly as products grow, and the `type` enum occasionally gains a value. Write your handler with a sane default branch rather than an exhaustive match.

## Next steps

- [Error reference](/docs/api/errors): the full error catalog, one page per code
- [Idempotency](/docs/guides/idempotency): safe retries for mutating requests
- [Rate limits](/docs/guides/rate-limits): limit headers and backoff guidance