Documentation
Sign inGet started

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

FieldAlways presentDescription
typeYesBroad category for coarse branching: a closed enum (auth_error, validation_error, rate_limit_error, ...).
codeYesOpaque, stable identifier matching E\d{5}. Unique, never renamed, never reused; the canonical thing to match on.
nameYesHuman-readable slug (ValidationError) for log readability. Always paired with code, never a replacement for it.
messageYesHuman-readable description. Not stable; display or log it, never parse it.
doc_urlYesStable link to the documentation page for this code.
request_idYesCorrelation ID, also returned as the X-Request-Id response header. Quote it in support requests.
paramNoThe offending field, when a single field is at fault.
detailsNoPer-field validation failures as {param, message} objects, where param is a dotted path like to[0].email. Present only on validation_error responses.
vendor_codeNoVerbatim 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.
StatustypeMeaning
400bad_request_errorThe request was malformed: an unparseable body or an invalid header (for example, a bad Idempotency-Key).
401auth_errorThe request carried missing, invalid, or revoked credentials. See Authentication.
402billing_errorThe request requires a payment method, balance, or plan the organization does not have. See Billing and usage.
403permission_errorThe credentials are valid, but not allowed to perform this request. See Authentication.
404not_found_errorNo route matches this path, or the resource does not exist in this workspace. See Regions.
409conflict_errorThe request conflicts with the current state of the resource, including idempotency conflicts (E01004, E01005). See Idempotency.
410gone_errorThe resource existed but has been permanently removed.
412precondition_errorA precondition for this request was not met.
413payload_too_large_errorThe request body exceeds the maximum allowed size.
421misdirected_errorThe request reached a region that cannot serve it. See Regions.
422delivery_errorThe message was accepted as a request but cannot be delivered as addressed.
422validation_errorThe request body parsed, but one or more values are invalid.
425too_early_errorThe request arrived before Bird is willing to process it.
429rate_limit_errorA rate-limit group is exhausted for this workspace. See Rate limits.
500internal_errorSomething failed on Bird's side while processing the request. See Idempotency.
501not_implemented_errorThe endpoint is declared in the API but not implemented yet.
503service_unavailable_errorA 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.
RangeAreaCodes
E01xxxInfrastructure24 codes
E02xxxAuth & identity6 codes
E04xxxEmail sending & delivery26 codes, 1 retired
E05xxxDomains & DNS19 codes
E06xxxWebhooks6 codes
E07xxxWallet2 codes
E10xxxQuotas4 codes, 2 retired
E11xxxIP pools & dedicated IPs3 codes
E12xxxSMS sending & delivery22 codes, 1 retired
E13xxxVerify6 codes
E15xxxWhatsApp sending & delivery4 codes
E17xxxAgent mailboxes12 codes, 1 retired