Errors
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.
Codevoorbeeld
{
"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": "attachments", "message": "this field is reserved and not yet supported" },
{ "param": "scheduled_at", "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 — unparseable body, invalid header (for example, a bad Idempotency-Key). |
| 401 | auth_error | Missing, invalid, or revoked credentials. See Authentication. |
| 402 | billing_error | The request requires a payment method, balance, or plan the organization does not have. |
| 403 | permission_error | Authenticated, but not allowed — insufficient API key scope or workspace role. |
| 404 | not_found_error | No such route, or no such resource in this workspace. |
| 409 | conflict_error | The request conflicts with current state — including idempotency conflicts E01004 and E01005 (Idempotency). |
| 412 | precondition_error | A precondition for the request was not met. |
| 413 | payload_too_large_error | The request body exceeds the maximum allowed size. |
| 421 | misdirected_error | The request was routed to the wrong region — use the regional host that matches your key's bk_{region}_ prefix. |
| 422 | validation_error | The body parsed but the values are invalid. details lists each failing field; reserved-but-unshipped fields such as attachments and scheduled_at on email sends also land here (E04007). |
| 429 | rate_limit_error | A rate-limit group is exhausted. Retry-After gives the wait in seconds, and the RateLimit/RateLimit-Policy headers name the group and quota. |
| 500 | internal_error | Bird-side failure. Safe to retry with backoff — and with the same idempotency key. |
| 501 | not_implemented_error | The endpoint is declared but not yet implemented. |
| 503 | service_unavailable_error | A dependency is temporarily unavailable. Retry with backoff. |
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.
Codevoorbeeld
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;
}Codevoorbeeld
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
}
}Codevoorbeeld
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)Related
- Errors concepts — branching strategy, validation details, and vendor_code semantics
- Authentication — credentials behind 401 and 403
- Idempotency — the 409 conflict errors and safe retries
- Rate limits — groups, headers, and handling 429