code/message/retryable:
integer
The HTTP status code, repeated in the body for convenience. Note that the HTTP status is not derivable from the
code range — e.g. 1011 is a 1xxx code returned with HTTP 409.integer
A stable integer — build your error-handling logic against it. Never changes for a given failure class.
string
Human-readable detail. May change between releases — do not branch on it. Internal delivery names, env var names, raw database errors, and crypto-step detail are deliberately not included; the full detail is logged server-side under
trace_id.boolean
Always present and authoritative — branch on this, not on the code or HTTP status.
true for 3xxx server/infra errors, most 4xxx delivery errors, and 1007 rate-limiting; false for everything else (including 4098 UNKNOWN_EFFECT and 4099 NOT_SUPPORTED). A few routes override the default — notably 3003 PROVIDER_NOT_CONFIGURED is sent with retryable: false because a missing server-side key won’t fix itself. Codes can shift between retryable and not at the route level; always trust the field, never re-derive it.integer
May be present on rate-limited / capacity responses (
1007, 4002) — not guaranteed. Seconds to wait before retrying; when present, mirrored in the Retry-After header. When absent, fall back to your own exponential backoff; branch on retryable, which is always present.string
Correlation id for this response, also returned in the
X-Trace-ID header (on both success and error responses). Quote it when contacting support.code field is a stable integer — build your error-handling logic against it. The message is human-readable and may change between releases.
Every error path across all four customer-facing API surfaces —
/api/v1/*, /api/salesforce/*, /api/hubspot/*, /api/slack/* — carries a numeric code, a human message, a retryable boolean, and a trace_id. Auth and signature failures return distinct codes (2012 AUTH_MISSING, 2004 AUTH_INVALID, 2013 AUTH_TIMESTAMP_SKEW) rather than one opaque “unauthorized”.Some endpoints keep a flat shape rather than the
{success,error,trace_id} envelope. POST /api/v1/send failures return status: "failed" with top-level code and retryable, plus a human-readable reason on 4xx (or a generic message on 5xx) and the targeted lead_id/chat_id/convo_id on 4xx delivery failures — there is no trace_id on /send responses. /api/salesforce/* keeps an { ok: false, ... } shape (the Apex client branches on ok) with code / message / retryable / trace_id added alongside. /api/hubspot/* webhook and workflow routes return HTTP 200 for logical failures (HubSpot’s Events API contract) with the failure detail in {ok:false,...} / outputFields:{ok:false,...}. In every case the numeric code scheme is the one documented here, so error-handling logic keyed on code works across all shapes. See Sending → Delivery failures.The numeric
code is the stable contract. The HTTP status is chosen per-route and is not derivable from the code range. The Retryable column below is the default error.retryable value for that code; a route may override it (see 3003).
1xxx — request errors
Fix the request before retrying.2xxx — auth / resource errors
Fix credentials or the target before retrying.3xxx — server / infrastructure errors
Retryable with exponential backoff — except3003.
4xxx — delivery errors
Retryable except4099.
5xxx — attachment errors
Fix the request before retrying.Error envelopes — examples
401 auth_missing
502 delivery_service_error
409 attachment_ref_invalid
400 unknown_effect
Retry guidance
error.retryable is the authoritative signal — it is always present on every error response, and clients should branch on it directly rather than re-deriving retryability from the code range or HTTP status. The table below explains the strategy per class.
Use
idempotency_key on POST /api/v1/send and POST /api/v1/chats to make retries safe.

