Errors
API errors use this JSON envelope. A network failure or a response from an intermediary may not include it, so handle those separately.
{
"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. 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
- For validation and permission errors, correct the request or configuration first. A definitively rejected POST can have a cached 4xx result; use a new idempotency key for the corrected request.
- For
rate_limit_exceededoridempotency_in_progress, respectRetry-Afterand retry within a bounded deadline. - For daily or monthly quota errors, wait for the allowance to reset or change the plan. An immediate retry loop will not help.
- For
internal_error,service_unavailableor a transport failure, use bounded backoff and keep the original idempotency key. An uncertain response can follow partial work; inspect message state before manually resubmitting.
See handling errors for SDK error handling and idempotency for the deduplication window and its limits.
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 |