API error codes reference
Every HTTP status and error.code the wemail API returns (400–503), what each means, how to retry it, and the JSON error body with request_id for support.
Every failed request answers with a conventional HTTP status and a JSON body you can act on: error.code is a stable machine-readable reason, error.message explains it in plain words (often pointing at the exact field), and request_id lets support trace the request end to end. Success responses use 200/201/202; anything else carries this body:
{
"error": {
"code": "invalid_json",
"message": "Unexpected token at position 42 — every string must be double-quoted"
},
"request_id": "req_9c9b3d644cbf45e9"
}The general rule: a 4xx means the request itself must change — fix it and resend (retrying the same call returns the same error). 409, 429 and every 5xx are transient — back off (exponential, starting around one second) and retry the identical request. Retries are always safe on the send endpoints when you pass an Idempotency-Key: a replay returns the original result instead of sending twice — see Idempotency.
Status codes and what to do
| Status | Description |
|---|---|
400 | 400 — fix the request body. Invalid parameters or missing required fields; error.code names the problem: invalid_json (malformed body — the message pinpoints the offending snippet), validation_error (a field failed its rules; the message names the field), template_not_found (the template_id you referenced does not exist — the message tells you how to list templates), attachment_too_large, recipient_limit_exceeded. Correct the payload and resend. |
401 | 401 — check the API key. Missing, malformed or revoked key (error.code: unauthorized). Send it as Authorization: Bearer <key>; keys are listed and rotated in Console → API keys. |
402 | 402 — buy verification credits. Not enough credits for a live POST /v3/validate or POST /v3/validate/bulk (insufficient_credits). Buy a credit pack and retry; test keys are exempt. |
403 | 403 — not allowed for this account or key. A suspended or deleted account, a plan limit that was reached (domains, API keys, webhook endpoints, dedicated-IP pools), an owner-only action on a domain shared from another workspace, a sandbox send to a recipient outside the sandbox, or a test key on a live-only operation (test_key_forbidden, e.g. GET /v3/domains). The message says which; fix the cause rather than retrying. |
404 | 404 — check the resource id. The id does not exist or belongs to another account (not_found). Ids are exact and account-scoped; a stale id after a delete also lands here. |
409 | 409 — wait and retry. A request with the same Idempotency-Key is still being processed (idempotency_in_progress). Wait for the original to finish, then retry — you receive the cached response. |
429 | 429 — back off and retry. Your per-account request rate, a route-level limit, or a sending / verification quota was exceeded (rate_limited). Respect Retry-After when present; see Rate limits for the per-plan windows. |
500 | 500 — retry with exponential backoff. Something failed on our side (internal_error). Retry the identical request; with an Idempotency-Key a retry can never double-send. Quote the request_id to support if it persists. |
503 | 503 — back off; the message says which of two causes. sending_paused: live sending was paused by our team (deliverability or billing hold) — check the Console banner or contact support; retries succeed once lifted. query_timeout (GET /v3/events, GET /v3/metrics*): the query exceeded its time budget — narrow since/until or the filters, lower the limit or set include_trend=false, honour Retry-After, or use POST /v3/events/export for a full extract. |
Handling errors in code
Branch on error.code, not on the message text — codes are stable, messages may be reworded. Log the request_id with every failure. For transient statuses (409, 429, 5xx) retry the same request with the same Idempotency-Key after an exponential delay with jitter; give up after a handful of attempts and surface the failure. For 4xx other than those, treat the response as a bug in the request and do not retry blindly. All seven SDKs raise a typed exception carrying statusCode, code, message and requestId, and retry the transient statuses for you.