Error reference

Error reference

Every API error uses one canonical envelope: an error object with a stable machine-readable code, a human message, and a fields object (empty unless a specific field failed validation).

Response shape

Error body
{
  "error": {
    "code": "not_found",
    "message": "Resource not found.",
    "fields": {}
  }
}

Match on error.code, never on the message text — codes are stable; messages may change.

Codes

CodeHTTPMeaningHow to handle
authentication_required 401 Missing or invalid API key. Send a valid Authorization: Bearer ms_live_... header.
account_forbidden 403 The account is not permitted to use the API (unverified, suspended, or inactive). Verify your email or contact support.
not_found 404 The resource does not exist or is not owned by this key. Check the id; foreign and deleted ids are indistinguishable by design.
method_not_allowed 405 The HTTP method is not supported for this path. Use the documented method for the endpoint.
not_acceptable 406 The requested response format is not acceptable. Request application/json.
conflict 409 The request conflicts with the current state (e.g. allocation retries exhausted). Retry, or inspect the other fields.
plan_limit_reached 409 The plan limit for this action has been reached. Upgrade your plan, or free capacity first.
slug_taken 409 The requested inbox routing_key is already claimed by another account. Choose a different routing_key, or omit it for a random one.
unsupported_media_type 415 The request media type is not supported. Send a JSON request body.
validation_error 422 The request body or a parameter failed validation. Read the fields object for per-field messages.
rate_limited 429 The per-key request rate limit was exceeded. Honour Retry-After and slow down.
too_many_wait_requests 429 The per-account concurrent-wait cap was reached (2 free / 3 paid). Wait for a wait to return or time out, then retry.
wait_capacity 429 The global concurrent-wait cap across all accounts was reached. Retry shortly; capacity frees automatically.
plan_upgrade_required 402 This feature (batch message operations) requires a paid plan. Upgrade your plan to use POST /messages/batch.
webhook_entitlement_required 403 Webhooks require a paid plan. Upgrade your plan to configure or test a webhook.
webhook_not_configured 404 No webhook is configured for this inbox. Create a webhook for the inbox before testing it.
webhook_ssrf_rejected 400 The webhook URL was rejected by SSRF validation. Use a public HTTPS URL; localhost/private/link-local addresses are blocked.
webhook_unreachable 502 The webhook receiver could not be reached (timeout, TLS, or network error). Check the receiver is up and reachable over HTTPS.
server_error 500 An internal error occurred. Retry; if it persists, report it.
byod_disabled 403 Custom domains are not enabled for this account. Custom domains are gated behind a feature flag; contact support.
domain_entitlement_required 403 Custom domains require a Business plan. Upgrade to the Business plan to add a custom domain.
domain_limit_reached 403 The custom-domain limit for this plan has been reached. Remove an existing domain, or upgrade your plan.
domain_taken 409 The requested domain name is already registered (by any account). Choose a different domain, or contact support if you believe this is an error.
domain_has_inboxes 409 The domain still has inboxes bound to it. Remove the bound inboxes before deleting the domain.
domain_verify_rate_limited 429 Too many manual domain-verify attempts. Honour Retry-After and slow down.
domain_not_found_or_unverified 422 The domain id was not found, is not owned by this account, or is not verified. Create/verify the domain first, or omit `domain` to create a shared-domain inbox.
public_not_allowed_on_custom_domain 422 A custom-domain (BYOD) inbox cannot be made public — `public` is shared-domain only. Omit `public`, or omit `domain` to create a shared-domain inbox.
metadata_must_be_object 422 The `metadata` field must be a JSON object (not a list, string, or number). Send `metadata` as a JSON object, e.g. {"run": "ci-123"}.
metadata_too_large 422 `metadata` exceeds 4096 bytes (compact UTF-8 JSON) or 50 top-level keys. Shrink the payload — metadata is for correlation ids, not blobs.
catch_all_entitlement_required 403 Catch-all requires a Business plan. Upgrade to the Business plan to enable catch-all.
domain_not_verified 409 The domain must be `verified` before catch-all can be enabled. Verify the domain first (`POST /domains/{id}/verify`).
catch_all_target 409 This inbox is the active catch-all target for its domain and cannot be deleted or disabled. Disable catch-all on the domain first (`PATCH /domains/{id}/catch_all` with `catch_all: false`).
catch_all_target_invalid 422 The designated `inbox_id` is not a live inbox bound to this domain. Pass an enabled, non-deleted inbox_id already bound to this domain, or omit it to auto-create a sentinel.
inbox_limit_reached 403 The plan's inbox limit was reached while auto-creating the catch-all sentinel. Remove an existing inbox, or upgrade your plan.
catch_all_rate_limited 429 Too many catch-all toggle attempts for this domain. Honour Retry-After and slow down.
outbound_disabled 403 Outbound email is not enabled for this account. Contact support; outbound send is gated behind a feature flag.
outbound_entitlement_required 403 Outbound email requires a Business plan (or the account is a demo/agent account, which can never send). Upgrade to the Business plan.
sending_domain_not_eligible 403 The from_address's domain is not owned, verified, and DKIM-verified by this account (all four causes collapse to this one code — no oracle). Verify the domain and complete DKIM setup (`POST /domains/{id}/sending_setup`), then re-check its DNS.
recipient_rejected 422 One or more recipients are on the suppression list (hard-bounced or explicitly suppressed). Remove the suppressed recipient(s) and resend.
outbound_quota_exceeded 403 The Business plan's per-day or per-hour outbound recipient cap has been reached. Wait for the window to roll over, or contact support.
outbound_rate_limited 429 Too many outbound send requests, or the per-tenant queue-depth guard was hit. Honour Retry-After and slow down.
recipient_not_approved 422 One or more recipients are not on the caller's approved-recipients allowlist. Approve the recipient (`POST /outbound_allowlist`) and resend.
outbound_allowlist_entitlement_required 403 The approved-recipients allowlist requires a Business plan. Upgrade to the Business plan.
approved_recipient_exists 409 That email address is already on the caller's approved-recipients allowlist. No action needed; the address is already approved.
outbound_allowlist_limit_reached 422 The approved-recipients allowlist entry limit has been reached. Remove an existing entry first.
outbound_allowlist_rate_limited 429 Too many allowlist changes. Honour Retry-After and slow down.
smtp_verify_disabled 403 Full SMTP verification (`mode: "full"`) is not enabled for this account. Full SMTP verify is gated behind a feature flag; contact support.
verify_full_entitlement_required 403 Full SMTP verification requires a Business plan. Upgrade to the Business plan, or use `mode: "safe"` (the default).
reply_target_invalid 422 No usable reply address (Reply-To / From / envelope From) could be parsed from the original message. Use compose instead, or address the recipient directly.
batch_too_large 422 The batch exceeds the maximum number of unique rows for this mode (100 safe / 25 full). Split the batch into smaller requests.
file_too_large 422 The uploaded file exceeds the 64KB maximum (checked via Content-Length and re-checked against the actual parsed size). Split the file, or paste the addresses as `text`/`emails` instead.
unsupported_file_type 422 The uploaded file must be a `.csv` or `.txt` file. Rename/re-export the file with a `.csv` or `.txt` extension.
malformed_file 422 The uploaded file could not be decoded as UTF-8 text, or contains a NUL byte (binary content). Re-save the file as plain UTF-8 text.

The two 429s

The wait endpoint can return 429 for two distinct reasons, distinguished only by error.code:

CodeCapRelease
too_many_wait_requestsPer-account concurrent waits: 2 free / 3 paid.When one of your waits returns or times out.
wait_capacityGlobal cap across all accounts (16).Automatically when any wait frees a slot; retry shortly.