Errors
Every failed request returns the same JSON body. Only type and details change.
{
"status": 422,
"type": "invalid_schedule",
"message": "scheduled_at must be within 72 hours",
"request_id": "req_01J8K2P...",
"doc_url": "https://docs.epostix.com/errors/invalid_schedule",
"details": []
}
Branch on type, never on message. Messages are written for people and are rewritten
whenever the wording can be improved. Types are part of the contract and only ever get added
to, so treat an unrecognised type as a generic failure rather than crashing on it.
request_id identifies the request in our logs. Quote it in a support ticket and we can find
the exact call. doc_url is the page for that type on this site.
details is populated when a specific field is at fault. Each entry names the field, a
message, and a code you can match on.
{
"details": [
{"field": "from", "message": "property \"from\" is missing", "code": "required"}
]
}
Deciding whether to retry
Three groups behave differently, and the difference matters more than the status code.
Never retry the same request. The request is wrong and will stay wrong. Fix it and send a new one. Everything in the 4xx range except the four below is in this group.
Retry after waiting. rate_limit_exceeded, daily_quota_exceeded and
idempotency_in_progress all carry a Retry-After header in seconds. Wait that long, then
send the identical request again.
Retry with backoff. internal_error and service_unavailable are ours, not yours. Retry
with exponential backoff. If you sent an Idempotency-Key, reuse the same key so a request
that actually succeeded is not sent twice.
The catalogue
| Type | Status | Meaning |
|---|---|---|
invalid_request | 400 | The body is not parseable JSON |
authentication_failed | 401 | The API key is missing, malformed or unknown |
api_key_expired | 401 | The key passed its expiry date |
monthly_quota_exceeded | 402 | The monthly send allowance is used up |
insufficient_scope | 403 | The key lacks the permission this route needs |
api_key_ip_restricted | 403 | The key is not allowed from your address |
domain_scope_restricted | 403 | The key is limited to other domains |
domain_not_verified | 403 | The sending domain is not verified |
workspace_suspended | 403 | The workspace is suspended |
test_mode_restricted | 403 | A test key attempted a live-only operation |
not_found | 404 | No such endpoint, or no such record |
domain_not_found | 404 | The domain is unknown to this workspace |
template_not_found | 404 | The template is unknown to this workspace |
attachment_not_found | 404 | The attachment is unknown to this workspace |
method_not_allowed | 405 | Wrong HTTP method for this path |
email_not_cancellable | 409 | The message has already left this stage |
idempotency_conflict | 409 | The key was reused with a different body |
idempotency_in_progress | 409 | A request with this key is still running |
api_key_in_use | 409 | You tried to delete the key you are using |
payload_too_large | 413 | The request body is over the size limit |
attachment_too_large | 413 | The attachments exceed the total size limit |
unsupported_media_type | 415 | Content-Type is not application/json |
validation_error | 422 | The body parsed but a field is invalid |
invalid_from_address | 422 | The from address is not a usable mailbox |
invalid_schedule | 422 | scheduled_at is in the past or too far ahead |
duplicate_detected | 422 | The same message was sent repeatedly |
batch_too_large | 422 | The batch is outside the allowed size |
template_render_failed | 422 | The template could not be rendered |
attachment_expired | 422 | The pre-uploaded attachment has expired |
attachment_fetch_failed | 422 | An attachment URL could not be read |
webhook_limit_reached | 422 | The workspace has all the endpoints it may have |
webhook_url_not_allowed | 422 | The webhook URL is not a public address |
rate_limit_exceeded | 429 | Too many requests in the current window |
daily_quota_exceeded | 429 | The daily send allowance is used up |
internal_error | 500 | Something failed on our side |
service_unavailable | 503 | The service is restarting |