Skip to main content
All errors return a structured JSON envelope:
{
  "success": false,
  "error": {
    "status": 400,
    "code": 1001,
    "message": "Missing required field: phone",
    "retryable": false
  },
  "trace_id": "a1b2c3d4e5f6"
}
Some codes attach extra context fields next to code/message/retryable:
FieldWhen
retry_afterRate-limit and capacity errors (1007, 4002). Seconds to wait; also mirrored in the Retry-After header.
attachment_index1011/1002 / 5xxx raised by a specific entry in an attachments or images array. 0-based index of the offending entry.
allowed_effects4098 UNKNOWN_EFFECT. The current list of valid effect values.
error.status
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.
error.code
integer
A stable integer — build your error-handling logic against it. Never changes for a given failure class.
error.message
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.
error.retryable
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.
error.retry_after
integer
Present on rate-limited / capacity responses. Seconds to wait before retrying; mirrored in the Retry-After header.
trace_id
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, but carry the same fields. POST /api/v1/send returns status: "failed" (or a flat object) with top-level code, message, retryable, and trace_id. /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.
CodeHTTPRetryableMeaningAction
1001400NoMissing required fieldCheck the request body for the named field.
1002400NoInvalid field value — also returned for malformed attachments arraysCorrect the field to a valid value.
1003400NoInvalid phone numberUse E.164 format, e.g. +15555550100.
1004400NoInvalid JSONCheck content-type: application/json and serialization.
1005400NoFirst message in a new chat cannot contain URLs in text parts or legacy link partsUse a rich_link part for previewed URLs, or send a URL-free first text and add links in a follow-up.
1006400NoToo many parts in a single messageReduce to 10 parts or fewer per message.
1007429YesRate limitedBack off and retry; respect retry_after / Retry-After.
1008400NoConsecutive text parts not permittedMerge adjacent text parts into one.
1009400NoText too longShorten the text or split into multiple parts.
1010400NoUnsupported messaging serviceOnly imessage is supported in v1.
1011409NoResource kind mismatch — wrong resource type for the operation, or an attachment ref mixes url and attachment_id, or is missing attachment_idAddress the operation at a resource of the correct type. For attachment errors the response includes attachment_index; the message hints “register via POST /api/v1/attachments first” when the id is missing.
1012409NoResource state invalid — the resource exists but is not in a usable stateWait for / move the resource into a valid state before retrying.
1013409NoContact has no usable phone numberThe addressed contact/record carries no phone; enrich it or pick another.

2xxx — auth / resource errors

Fix credentials or the target before retrying.
CodeHTTPRetryableMeaningAction
2001404NoTenant not foundVerify x-chert-tenant matches your registration response.
2002404NoChat not foundVerify the chat id belongs to your tenant.
2003404NoMessage not foundVerify the message id belongs to your tenant.
2004401NoInvalid auth — credentials supplied but wrong (bad signature / bad token)Re-sign with the current timestamp. See Authentication.
2005403NoForbiddenTenant suspended or you lack permissions for this resource. Contact your Chert administrator.
2006400NoPhone number not assigned to your tenantUse a number from GET /api/v1/phone-numbers.
2007403NoEmail not verifiedClick the link in the verification email sent at registration.
2008402NoAccount limit reachedContact your Chert administrator before retrying.
2010403NoIntegration not connected — a required OAuth integration is not linkedConnect the integration (Salesforce / HubSpot / Slack), then retry.
2011404NoResource not found — a generically addressed resource is missing (icp / list / sheet / sequence / lead / message)Verify the id belongs to your tenant.
2012401NoAuth missing — no credentials were supplied at allAdd the x-chert-signature header (or bearer token). See Authentication.
2013401NoAuth timestamp skew — the signature timestamp is outside the allowed clock skewFix your system clock and re-sign with a fresh timestamp.
2014400NoOAuth state invalid — the OAuth callback state is missing, expired, or mismatchedRestart the OAuth flow from the beginning.

3xxx — server / infrastructure errors

Retryable with exponential backoff — except 3003.
CodeHTTPRetryableMeaningAction
3001500YesInternal server errorRetry with exponential backoff. Contact support if persistent.
3002503YesDelivery unavailableChert delivery infrastructure is unreachable. Retry after a short delay.
3003500 / 503NoDelivery service not configuredNot retryable despite the 3xxx range — the response carries retryable: false. Contact support.
3004502YesDelivery service errorRetry after a short delay; the dependency may be transiently failing.

4xxx — delivery errors

Retryable except 4099.
CodeHTTPRetryableMeaningAction
4001502YesDelivery failedThe message was dispatched but the downstream rejected it. Check message (or top-level reason on the /api/v1/send flat response).
4002503YesAll available phone lines are saturated for this recipientRetry later. The response includes diagnostic detail; healthy lines free up at the top of the next hour.
4098400NoUnknown effect — the effect value is not in the whitelistResponse carries allowed_effects: [...] with the current valid set. Pick one of those values, or omit effect.
4099422 / 501NoFeature not supported in this contextReturned when a route, line, or request shape does not support the requested feature, including group-chat endpoints missing chat key / pinned line and /messages/{id}/edit past the 15-min window or on lines without edit support. Status varies by route. Not retryable.

5xxx — attachment errors

Fix the request before retrying.
CodeHTTPRetryableMeaningAction
5001404NoAttachment not foundVerify the attachment_id belongs to your tenant.
5002400NoAttachment too largeReduce file size below the limit.
5003400NoUnsupported MIME typeCheck the supported types list for your tier.
5004400NoAttachment not yet uploadedComplete the PUT upload step before referencing the attachment in a message.

Error envelopes — examples

401 auth_missing
{
  "success": false,
  "error": {
    "status": 401,
    "code": 2012,
    "message": "No credentials supplied",
    "retryable": false
  },
  "trace_id": "9f8e7d6c"
}
502 delivery_service_error
{
  "success": false,
  "error": {
    "status": 502,
    "code": 3004,
    "message": "A delivery service returned an error",
    "retryable": true
  },
  "trace_id": "b2c3d4e5"
}
409 attachment_ref_invalid
{
  "success": false,
  "error": {
    "status": 409,
    "code": 1011,
    "message": "Attachment ref missing attachment_id — register via POST /api/v1/attachments first",
    "retryable": false,
    "attachment_index": 2
  },
  "trace_id": "c3d4e5f6"
}
400 unknown_effect
{
  "success": false,
  "error": {
    "status": 400,
    "code": 4098,
    "message": "Unknown effect: 'fireworks'",
    "retryable": false,
    "allowed_effects": ["celebration", "balloons", "confetti", "fireball", "heart", "laser", "lasers", "shootingstar", "sparkles", "spotlight"]
  },
  "trace_id": "d4e5f6a7"
}

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.
ClassretryableStrategy
1xxx except 1007 (400/409)falseFix the request and resubmit.
1007 (429)trueRespect retry_after / Retry-After. Exponential backoff otherwise.
2xxx (401/402/403/404)falseFix credentials, account state, integration connection, or target id — then resubmit.
3001 (500), 3002 (503)trueExponential backoff: 1s, 2s, 4s.
3003 (500/503)falseServer-side config gap — retrying won’t help. Contact support.
3004 (502)trueDelivery service failed transiently. Short delay, then retry.
4001 (502), 4002 (503)trueRe-attempt after a short delay; check message for the reason.
4098 (400)falsePick a value from allowed_effects in the response, or omit the field.
4099 (422/501)falseFeature not live in this context; do not retry.
5xxx (400/404)falseFix the attachment reference and resubmit.
Use idempotency_key on POST /api/v1/send and POST /api/v1/chats to make retries safe.

See also