Reference
Errors
The error envelope, status codes, and every stable error code.
Every error is JSON with a stable, machine-readable error code and the
matching HTTP status. No stack traces, no surprises.
{ "error": "validation_failed", "detail": "at least one recipient is required" }error is always present. detail is an optional human-readable
elaboration. Some codes carry extra context fields (field, max, limit,
status, alias, …) documented on the operation that returns them.
Status codes
| Status | Meaning |
|---|---|
400 | Malformed JSON body (invalid_json) |
401 | Missing, unknown, or revoked key (unauthorized) |
402 | Plan limit reached — contacts, webhooks (contact_limit_reached, webhook_limit_reached) |
403 | Authenticated but not permitted (insufficient_scope — key scope, domain scope) |
404 | Not found — tenant-scoped, never distinguishes "doesn't exist" from "not yours" (not_found) |
409 | Conflicting state transition (not_draft, not_editable, not_cancelable, alias_exists, …) |
422 | Validation failure (validation_failed, invalid_email, subject_required, from_domain_not_verified, template_not_found, …) |
429 | rate_limit_exceeded or quota_exceeded — see Rate limits |
5xx | Something broke on our side — safe to retry with an Idempotency-Key |
Common codes on the send path
| Code | Status | Meaning |
|---|---|---|
unauthorized | 401 | No/unknown API key |
insufficient_scope | 403 | sending_access key used on a management route |
from_domain_not_verified | 422 | from domain not verified (or key scoped to another domain) |
validation_failed | 422 | Payload failed a rule — detail says which (empty subject/body, bad recipient, …) |
template_not_found | 422 | Unknown template_alias |
scheduled_at_must_be_in_the_future | 422 | scheduled_at in the past |
missing_field | 422 | A required field is absent (field names it) |
rate_limit_exceeded | 429 | Per-minute burst limit (Retry-After included) |
quota_exceeded | 429 | Daily send quota used up (Retry-After included) |
idempotent_replay_failed | — | The stored result for this Idempotency-Key could not be replayed |
A suppressed recipient is not an error: the send returns 200
{ "status": "suppressed" } and nothing is sent.
Retrying errors
429— honourRetry-After, then retry with the sameIdempotency-Key.5xx/ timeouts — retry with the same key; the replay returns the original result without re-sending or re-consuming quota.4xx— fix the request. Retrying unchanged just repeats the failure. A rejected send refunds any quota unit it consumed, so a bug here costs you requests, not mail.
See also: Rate limits & quotas and the API reference for per-endpoint responses.