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:
Codebeispiel
{
"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.
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:
Codebeispiel
{
"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), use exponential backoff on 5xx, and send an Idempotency-Key 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: the full error catalog, one page per code
- Idempotency: safe retries for mutating requests
- Rate limits: limit headers and backoff guidance