Documentation
Sign inGet started

Errors

Every failed Bird API request returns the same JSON envelope, nested under a top-level error key:
Ejemplo de código
{
  "error": {
    "type": "validation_error",
    "code": "E01001",
    "name": "ValidationError",
    "message": "Request validation failed.",
    "doc_url": "https://docs.bird.com/errors/E01001",
    "request_id": "req_01krdgeqcxet5s7t44vh8rt9mg",
    "details": [
      { "param": "to", "message": "must contain at least one recipient" },
      { "param": "subject", "message": "exceeds maximum length of 998 characters" }
    ]
  }
}
The HTTP status code is set correctly at the transport layer — 400 for bad input, 401/403 for auth, 404 for missing resources, 409 for conflicts, 429 for rate limits, 5xx for Bird-side failures — and the body adds specificity. Status alone tells you the category; the envelope tells you exactly what happened.

The envelope fields

FieldRole
typeBroad 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.
codeOpaque, stable identifier (E01001). The canonical reference — unique, never renamed, never reused. When an error is retired, its code is permanently reserved.
nameHuman-readable slug (ValidationError) for log readability. Always paired with code, never a replacement for it.
messageHuman-readable description. Not stable — wording changes without notice. Display it, log it, never parse it.
paramFor input-related errors, the offending field. Omitted when not applicable.
doc_urlPermanent link to the docs page for this code. Valid forever, even for retired codes.
request_idAlways present, and also returned as the X-Request-Id response header. Quote it in support requests — it lets Bird trace the exact request.
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.
Ejemplo de código
curl -s https://us1.platform.bird.com/v1/email/messages \
  -X POST \
  -H "Authorization: Bearer bk_us1_invalid" \
  -H "Content-Type: application/json" \
  -d '{}' | jq '.error | {type, code, request_id}'
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.

Two structured exceptions

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} — see the envelope example above. The details array is present only on validation_error responses, and the message strings inside it are human-readable, not machine-stable: use param to map problems to form fields, use the message for display.

External-system failures: vendor_code

When a failure originates in a system outside Bird — an upstream email provider, an SMS carrier, a card network, a recipient's mail server — the envelope carries one stable Bird code plus a vendor_code field with the external system's own code, verbatim:
Ejemplo de código
{
  "error": {
    "type": "internal_error",
    "code": "E05003",
    "name": "SparkPostError",
    "message": "An error occurred communicating with the mail infrastructure provider.",
    "doc_url": "https://docs.bird.com/errors/E05003",
    "request_id": "req_01krdgeqcxet5s7t44vh8rt9mg",
    "vendor_code": "1902"
  }
}
This keeps Bird's catalog small and stable while still giving you the full upstream taxonomy when you need it — the docs page for each such code explains what its vendor_code values mean in context.

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.