API errors and error codes
Every error response has the same format, with a stable name you can handle in your code.
#Format
Responses with a 2xx status succeeded. When something goes wrong, the body is always a JSON object with three fields:
{
"statusCode": 422,
"name": "validation_error",
"message": "Missing `subject` field."
}
| Field | Description |
|---|---|
statusCode | The same HTTP status as the response. |
name | Stable error code, in English. Use this field in your code. |
message | Explanation for humans, in English (or in Portuguese, with Accept-Language: pt-BR). The wording can change; do not compare against it. See Message language. |
Some errors carry an extra field: errors. In the public form submission it is an object with the messages for each field; in automations it is a list with each problem in the definition (step_id and message).
#Error codes
| Status | Name | When it happens | What to do |
|---|---|---|---|
400 | invalid_idempotency_key | The Idempotency-Key is empty or longer than 256 characters. | Fix the key. |
401 | missing_api_key | The Authorization header is missing. | Send Authorization: Bearer ps_…. |
401 | restricted_api_key | A sending-only key called an endpoint that requires full access (it can only send, read GET /emails/{id} for the emails it sent, and read GET /usage). | Use a full-access key. |
403 | invalid_api_key | The key does not exist or was deleted. | Check the key or create another one. |
403 | restricted_api_key | The key is restricted to another domain. | Use the key's domain or another key. |
403 | validation_error | The sender's domain is not verified, does not have sending enabled or does not belong to your team. | Verify the domain. |
403 | sending_paused | Your team's sending was paused because of a high bounce or complaint rate. It applies to POST /emails, batches, SMTP, broadcasts and the test addresses. | Request a review in the dashboard or through the contact form. |
403 | plan_limit_reached | Your plan does not allow more items (domains, inboxes, templates, API keys, webhooks, contacts, segments, topics, forms, automations, broadcasts this month…) or does not include the requested feature (batch sending, scheduling, SMTP, test addresses, exports, attachments, MCP…). See the list of features. | Change plans or remove items. |
403 | paused_by_plan | The item (domain, API key, webhook, template, layout, inbox, automation, segment, topic or form) went over the limit of the current plan and was paused: it can be read and deleted, but not changed or used. A paused API key answers this on every request. | Change plans or delete other items: the item comes back on its own. |
403 | team_suspended | The account was suspended by the platform that manages it. | Contact that platform. |
403 | origin_not_allowed | A form received a submission from a site that is not in its allowed_domains. | Add the domain to the form. |
404 | not_found | The resource does not exist, belongs to another team, or the route does not exist. | Check the ID and the path. |
405 | method_not_allowed | The path exists, but not with this method. | Check the method. |
409 | invalid_idempotent_request | The same Idempotency-Key was used with a different body. | Use a new key for each send. |
409 | concurrent_idempotent_requests | A request with the same key is still in progress. | Wait and try again. |
409 | segment_in_use | The segment receives the contacts of a form and cannot be deleted. | Change the form's segment first. |
409 | tag_in_use | The tag is used by automations and cannot be deleted. | Remove the tag from the automations first. |
409 | merge_in_progress | Another segment merge is still running. | Wait for it to finish and try again. |
409 | invalid_merge_status | Only a failed merge can be retried. | Check the merge status. |
409 | invalid_segment_type | The segment is already static and cannot be converted. | Nothing to do. |
409 | invalid_broadcast_status | The broadcast (/broadcasts) is not in a status that allows the action: editing or deleting one that already went out, sending a canceled one, canceling a draft. | Check the broadcast status. |
410 | form_closed | A JSON submission reached a form that no longer takes answers: it passed its closing date (closes_at), reached its submission limit (max_submissions) or was paused with a closing message. The message is the form's closing message. | Show the message to the person; do not retry. |
422 | validation_error | A field is invalid. The message says which one. | Fix the body; do not repeat it as is. |
422 | email_suppressed | The address is on the suppression list and therefore cannot become a contact. | Remove the address from the list first, if appropriate. |
422 | ai_refused | The AI refused the request. | Rewrite the request. |
429 | rate_limit_exceeded | You went over your plan's requests-per-second limit. It also applies when there are already too many exports, contact imports or email validations running (the message says which), and to submissions to a public form (5 per minute per IP). | Wait Retry-After seconds, or for what is running to finish. |
429 | daily_quota_exceeded | The daily sending quota is used up (it resets at 00:00 UTC): the plan's daily limit or the account reputation ceiling, whichever is lower. When the reputation is what limits, the message gives the level and the ceiling. | Wait. The plan's limit changes with the plan; the reputation ceiling grows with each day of sending without bounces or complaints. |
429 | monthly_quota_exceeded | The monthly sending quota is used up (and so is the Sends add-on reserve, if the team has one). | Change plans, turn on sending beyond the quota or, when the plan offers it, buy the Sends add-on. |
429 | verification_credits_exhausted | The email validation credits are used up (or are not enough for the list). | Wait for the 1st of the month, buy the Validation add-on or change plans. |
429 | ai_credits_exhausted | The AI credits for the month are used up. | Wait for the 1st of the month or change plans. |
500 | internal_server_error | Unexpected error on Postal's side. | Retry with increasing backoff. |
502 | crm_request_failed | The connected CRM refused Postal's call or did not respond (connection test, fields, stages or sync). | Check the token and the CRM, then try again. |
502 | ai_error | The AI did not return a valid response. | Try again. |
503 | ai_unavailable | The AI service is busy. | Try again in a moment. |
503 | ai_not_configured | The AI assistant is unavailable. | Try again later. |
4xx/5xx | application_error | Another HTTP error that has no name of its own. The statusCode and the message say what happened. | Follow the status: 4xx, fix the request; 5xx, retry. |
#When to retry
- Retry on
429 rate_limit_exceeded,409 concurrent_idempotent_requests,5xxand network failures (timeout, connection refused). Wait a little longer after each attempt (1 s, 2 s, 4 s…) and honorRetry-Afterwhen it is present. - Do not repeat the request as is on
400,401,403,404,410and422: the response will be the same until you fix the request or the configuration. - When sending, always include an
Idempotency-Key: if the first attempt actually succeeded, the retry returns the same response without sending again.