Skip to main content
All errors return a structured JSON envelope:
Some codes attach extra context fields next to 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.
The 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 — except 3003.

4xxx — delivery errors

Retryable except 4099.

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.

See also