An error catalog is a fixed set of identified failures, each with a stable identifier and a page explaining it, rather than a collection of prose messages you are left to pattern-match. The practical difference is that your handler can branch on something that will not change under it.
Every failed Bird request returns the same envelope, nested under a top-level error. The HTTP status tells you the category, and the envelope explains what happened.
Which field should I branch on?
Three of them are for machines and one is not, and mixing that up is the mistake this section exists to prevent.
type is for coarse handling. A closed enum that grows rarely: validation_error, auth_error, permission_error, not_found_error, conflict_error, rate_limit_error, billing_error, internal_error and a few more. This is what a switch statement should key on: retry a rate-limit error, surface a validation error to the user, page someone on an internal one.
code is for specific handling. An opaque, stable identifier like E01001. It is the canonical reference: unique, never renamed and never reused, and when an error is retired its code is permanently reserved. That guarantee is what makes it safe to match a handful of codes you treat specially.
message is for people. The wording changes without notice. Display it, log it, and never parse it. A handler that matches on message text breaks on a copy edit.
name is a readable slug that always accompanies the code rather than replacing it, and doc_url is a stable link to that code's page.
How do I map codes to retries?
There is one rule and it is shorter than people expect: retry 429 and 5xx, and nothing else by default.
Honour Retry-After on a rate limit, use exponential backoff on a server error, and send an idempotency key so a retried mutation cannot apply twice. Everything else is deterministic: a 422 will fail identically however many times you send it, and retrying a 401 just means being refused more often.
That is why branching on type rather than status alone is worth it. rate_limit_error and internal_error are the two retryable categories, and they are the two the type names for you.
Why is every validation failure the same code?
Because a code per field-and-failure combination would be a catalog nobody could hold, and the information you need is one level down.
Every field-level validation failure is E01001 ValidationError with a details array, each entry a param and a message. So you branch once on the code and then read details to map problems onto form fields, using param as the key. The message strings inside details change like any other message, so display them rather than matching them.
Do errors tell me how to fix them?
Some do, in two forms, and the second is the unusual one.
remediation is a human-readable next step, present when a recovery is known. A permission error that needs a scope your credential lacks comes back with a sentence saying to re-authenticate with a credential that has it. Show that as-is.
next lists the API operations that resolve the error, in the order to try them, and it is present for errors with a well-defined recovery such as an unmet precondition. Verify a domain, then retry the send. An agent or a CLI can execute that list directly rather than parsing advice out of prose, which is the part worth knowing if you are building something that recovers on its own.
There is also vendor_code, present only when Bird is surfacing a downstream system's own code, an SMTP response or a payment decline, that you may want to act on separately.
What should my handler do about codes it has never seen?
Have a default branch, because new codes ship regularly as products grow and the type enum occasionally gains a value.
An exhaustive match over either field is a handler that breaks on a release you did not read about. Treat an unrecognised type as its nearest category or as an unknown failure, and log the code so you find out what it was.
One logging habit is worth the line it costs: log code, name and request_id together. The code is what you will search for in the docs and in your own history; the request ID is what support needs to trace the exact call.
Errors has the full envelope with real captured responses, and the error reference is the catalog itself, one page per code.