Errors
One error envelope, one code per failure, a matching HTTP status.
Every 4xx and 5xx response uses the same envelope:
{
"error": {
"code": "invalid_request",
"message": "Body must be JSON."
}
}codeis stable and machine-readable — branch on it.messageis human-readable and may change.
Some send failures that were still logged include a message_id alongside
code and message, so you can look the attempt up later.
Status codes
| Status | code | Meaning |
|---|---|---|
400 | invalid_request | The body failed validation (bad JSON, missing or malformed field). |
401 | unauthorized | Missing key, invalid/revoked key, or the key lacks the required scope. |
404 | template_not_found | No template with that slug in this project. |
404 | not_found | No such message in this project. |
409 | template_not_published | The template exists but has no published version. |
409 | sends_paused | The project's kill switch is on — no sends leave until it's cleared. |
422 | suppressed | The recipient is on the suppression list (bounce, complaint, or manual block). |
429 | daily_cap | The project's daily send cap has been reached. |
429 | monthly_cap | The provider plan's monthly send limit has been reached. |
429 | rate_limited | Too many requests in a short window. Retry after a short backoff. |
502 | provider_error | The email provider rejected the request. |
500 | internal | An unexpected server error. Safe to retry with the same Idempotency-Key. |
suppressed, sends_paused, daily_cap, and monthly_cap are policy outcomes, not bugs —
the request was well-formed but the send was deliberately blocked. Handle them
distinctly from invalid_request.