code/message/retryable:
| Field | When |
|---|---|
retry_after | Rate-limit and capacity errors (1007, 4002). Seconds to wait; also mirrored in the Retry-After header. |
attachment_index | 1011/1002 / 5xxx raised by a specific entry in an attachments or images array. 0-based index of the offending entry. |
allowed_effects | 4098 UNKNOWN_EFFECT. The current list of valid effect values. |
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.A stable integer — build your error-handling logic against it. Never changes for a given failure class.
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.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.Present on rate-limited / capacity responses. Seconds to wait before retrying; mirrored in the
Retry-After header.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, 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.| Code | HTTP | Retryable | Meaning | Action |
|---|---|---|---|---|
| 1001 | 400 | No | Missing required field | Check the request body for the named field. |
| 1002 | 400 | No | Invalid field value — also returned for malformed attachments arrays | Correct the field to a valid value. |
| 1003 | 400 | No | Invalid phone number | Use E.164 format, e.g. +15555550100. |
| 1004 | 400 | No | Invalid JSON | Check content-type: application/json and serialization. |
| 1005 | 400 | No | First message in a new chat cannot contain URLs in text parts or legacy link parts | Use a rich_link part for previewed URLs, or send a URL-free first text and add links in a follow-up. |
| 1006 | 400 | No | Too many parts in a single message | Reduce to 10 parts or fewer per message. |
| 1007 | 429 | Yes | Rate limited | Back off and retry; respect retry_after / Retry-After. |
| 1008 | 400 | No | Consecutive text parts not permitted | Merge adjacent text parts into one. |
| 1009 | 400 | No | Text too long | Shorten the text or split into multiple parts. |
| 1010 | 400 | No | Unsupported messaging service | Only imessage is supported in v1. |
| 1011 | 409 | No | Resource kind mismatch — wrong resource type for the operation, or an attachment ref mixes url and attachment_id, or is missing attachment_id | Address 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. |
| 1012 | 409 | No | Resource state invalid — the resource exists but is not in a usable state | Wait for / move the resource into a valid state before retrying. |
| 1013 | 409 | No | Contact has no usable phone number | The addressed contact/record carries no phone; enrich it or pick another. |
2xxx — auth / resource errors
Fix credentials or the target before retrying.| Code | HTTP | Retryable | Meaning | Action |
|---|---|---|---|---|
| 2001 | 404 | No | Tenant not found | Verify x-chert-tenant matches your registration response. |
| 2002 | 404 | No | Chat not found | Verify the chat id belongs to your tenant. |
| 2003 | 404 | No | Message not found | Verify the message id belongs to your tenant. |
| 2004 | 401 | No | Invalid auth — credentials supplied but wrong (bad signature / bad token) | Re-sign with the current timestamp. See Authentication. |
| 2005 | 403 | No | Forbidden | Tenant suspended or you lack permissions for this resource. Contact your Chert administrator. |
| 2006 | 400 | No | Phone number not assigned to your tenant | Use a number from GET /api/v1/phone-numbers. |
| 2007 | 403 | No | Email not verified | Click the link in the verification email sent at registration. |
| 2008 | 402 | No | Account limit reached | Contact your Chert administrator before retrying. |
| 2010 | 403 | No | Integration not connected — a required OAuth integration is not linked | Connect the integration (Salesforce / HubSpot / Slack), then retry. |
| 2011 | 404 | No | Resource not found — a generically addressed resource is missing (icp / list / sheet / sequence / lead / message) | Verify the id belongs to your tenant. |
| 2012 | 401 | No | Auth missing — no credentials were supplied at all | Add the x-chert-signature header (or bearer token). See Authentication. |
| 2013 | 401 | No | Auth timestamp skew — the signature timestamp is outside the allowed clock skew | Fix your system clock and re-sign with a fresh timestamp. |
| 2014 | 400 | No | OAuth state invalid — the OAuth callback state is missing, expired, or mismatched | Restart the OAuth flow from the beginning. |
3xxx — server / infrastructure errors
Retryable with exponential backoff — except3003.
| Code | HTTP | Retryable | Meaning | Action |
|---|---|---|---|---|
| 3001 | 500 | Yes | Internal server error | Retry with exponential backoff. Contact support if persistent. |
| 3002 | 503 | Yes | Delivery unavailable | Chert delivery infrastructure is unreachable. Retry after a short delay. |
| 3003 | 500 / 503 | No | Delivery service not configured | Not retryable despite the 3xxx range — the response carries retryable: false. Contact support. |
| 3004 | 502 | Yes | Delivery service error | Retry after a short delay; the dependency may be transiently failing. |
4xxx — delivery errors
Retryable except4099.
| Code | HTTP | Retryable | Meaning | Action |
|---|---|---|---|---|
| 4001 | 502 | Yes | Delivery failed | The message was dispatched but the downstream rejected it. Check message (or top-level reason on the /api/v1/send flat response). |
| 4002 | 503 | Yes | All available phone lines are saturated for this recipient | Retry later. The response includes diagnostic detail; healthy lines free up at the top of the next hour. |
| 4098 | 400 | No | Unknown effect — the effect value is not in the whitelist | Response carries allowed_effects: [...] with the current valid set. Pick one of those values, or omit effect. |
| 4099 | 422 / 501 | No | Feature not supported in this context | Returned 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.| Code | HTTP | Retryable | Meaning | Action |
|---|---|---|---|---|
| 5001 | 404 | No | Attachment not found | Verify the attachment_id belongs to your tenant. |
| 5002 | 400 | No | Attachment too large | Reduce file size below the limit. |
| 5003 | 400 | No | Unsupported MIME type | Check the supported types list for your tier. |
| 5004 | 400 | No | Attachment not yet uploaded | Complete the PUT upload step before referencing the attachment in a message. |
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.
| Class | retryable | Strategy |
|---|---|---|
1xxx except 1007 (400/409) | false | Fix the request and resubmit. |
1007 (429) | true | Respect retry_after / Retry-After. Exponential backoff otherwise. |
2xxx (401/402/403/404) | false | Fix credentials, account state, integration connection, or target id — then resubmit. |
3001 (500), 3002 (503) | true | Exponential backoff: 1s, 2s, 4s. |
3003 (500/503) | false | Server-side config gap — retrying won’t help. Contact support. |
3004 (502) | true | Delivery service failed transiently. Short delay, then retry. |
4001 (502), 4002 (503) | true | Re-attempt after a short delay; check message for the reason. |
4098 (400) | false | Pick a value from allowed_effects in the response, or omit the field. |
4099 (422/501) | false | Feature not live in this context; do not retry. |
5xxx (400/404) | false | Fix the attachment reference and resubmit. |
idempotency_key on POST /api/v1/send and POST /api/v1/chats to make retries safe.

