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:
{
"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. Client errors use 400 for malformed requests, 401 or 403 for access failures, 402 for billing, and 404 for missing resources. They use 409 for conflicts, 412 for unmet preconditions, 422 for validation and business-rule failures, and 429 for rate limits. Bird-side failures use 5xx. The status identifies the category; the envelope explains 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, each as {param, message}. Present only on validation_error responses, and only on those that list fields. |
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. |
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.
Codes such as E04012 are intentionally opaque. To turn one into plain English, read the error's message, then open its doc_url: each code has a page that says what went wrong and what to do. 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. Most field problems are E01001 ValidationError. The top-level message says what is wrong and param names the field. A details array lists each field-level problem as {param, message}, the way the captured send failure lists its missing properties. A contact property is named by its key under data, such as data.plan. Some validation errors name a single field in param and carry no details, so read details when it is present. The message strings can change and should only be displayed. Use param to map problems to form fields.
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:
{
"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
429and5xx, nothing else by default. HonorRetry-Afteron rate limits (see Rate limits), use exponential backoff on5xx, and send anIdempotency-Keyso retries of mutating requests are safe. - Log
code,name, andrequest_idtogether. 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
typeenum 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
Related resources
Continue with the documentation, guides and examples for this topic.