Errors
Status code + error code catalog across every endpoint.
All error responses share the same shape:
{
"error": {
"code": "domain_not_verified",
"message": "The sender domain has not been verified yet."
}
}
code is stable — branch on it. message is for humans (logs,
dashboards) and may change over time.
Status code semantics
| Status | Meaning |
|---|
200 | OK. Resource returned. |
201 | Created. New resource returned with its ID. |
202 | Accepted. Work is queued — used for POST /emails. |
400 | Bad request. Body failed validation or required state is missing. |
401 | Unauthorized. Missing / invalid Authorization header. |
403 | Forbidden. Token doesn't have the required scope. |
404 | Not found. Resource doesn't exist (or belongs to another workspace). |
409 | Conflict. Resource already exists (e.g. duplicate domain). |
422 | Unprocessable. Semantically valid but can't be applied (e.g. domain not yet verified). |
429 | Rate limited. Retry-After header indicates how long to wait. |
5xx | Server-side problem. Retry with exponential backoff. |
Common error codes
Auth
| Code | Status | Cause |
|---|
unauthorized | 401 | Missing/invalid token. |
forbidden | 403 | Token lacks required scope. |
key_revoked | 401 | Key was revoked — issue a new one. |
Emails
| Code | Status | Cause |
|---|
domain_not_verified | 422 | from domain not yet verified for this workspace. |
body_required | 400 | Both html and text missing. |
invalid_recipient | 400 | to/cc/bcc parse failed or address rejected by validator. |
template_not_found | 404 | templateId doesn't exist (or was deleted). |
Domains
| Code | Status | Cause |
|---|
domain_already_exists | 409 | Domain is already added to this workspace. |
domain_taken_elsewhere | 409 | Domain registered against another SES identity in our account. |
dns_lookup_failed | 422 | DoH lookups for DKIM/SPF/MX records didn't return matching values. |
Webhooks
| Code | Status | Cause |
|---|
url_must_be_https | 400 | Webhook URL uses http://. |
events_required | 400 | Empty events array. |
delivery_not_found | 404 | Redeliver target doesn't exist. |
Rate limiting
| Code | Status | Cause |
|---|
rate_limited | 429 | Per-workspace plan limit. Retry-After in seconds. |
Provider upstream
| Code | Status | Cause |
|---|
provider_unavailable | 503 | AWS SES upstream returned 5xx. We retry on send-path; surfaced here so you know. |
Don't pattern-match on message strings — they're for humans
and can change. Use the stable code field for any
programmatic branching.