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.
Code example
{
"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.
| 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. |
| 402 | billing_error | The request requires a payment method, balance, or plan the organization does not have. See Billing and usage. |
| 403 | permission_error | The credentials are valid, but not allowed to perform this request. See Authentication. |
| 404 | not_found_error | No route matches this path, or the resource does not exist in this workspace. See Regions. |
| 409 | conflict_error | The request conflicts with the current state of the resource, including idempotency conflicts (E01004, E01005). See 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. |
| 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. |
| 500 | internal_error | Something failed on Bird's side while processing the request. See 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. |
Handling errors in the SDKs
Each SDK 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.
Code example
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;
}Code example
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
}
}Code example
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
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 | Infrastructure | 24 codes |
| E02xxx | Auth & identity | 6 codes |
| E04xxx | Email sending & delivery | 26 codes, 1 retired |
| E05xxx | Domains & DNS | 19 codes |
| E06xxx | Webhooks | 6 codes |
| E07xxx | Wallet | 2 codes |
| E10xxx | Quotas | 4 codes, 2 retired |
| E11xxx | IP pools & dedicated IPs | 3 codes |
| E12xxx | SMS sending & delivery | 22 codes, 1 retired |
| E13xxx | Verify | 6 codes |
| E15xxx | WhatsApp sending & delivery | 4 codes |
| E17xxx | Agent mailboxes | 12 codes, 1 retired |
Related
- Errors concepts: branching strategy, validation details, and vendor_code semantics
- Authentication: credentials behind 401 and 403
- Idempotency-Key header: the 409 conflict errors and safe retries
- Rate limits: groups, headers, and handling 429