Errors
Every failure uses one envelope:
{
"error": {
"code": "contact_already_exists",
"message": "A contact with this email already exists in this audience",
"param": "email",
"request_id": "req_8fk2..."
}
}codeis the contract. Branch on it, never onmessage.messageis for humans and may change anytime.paramnames the offending field, when there is one.request_idis always present. Include it when you contact support.
Codes
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Malformed body, or a value failed validation. |
| 400 | invalid_email | Not a valid email address. |
| 400 | invalid_filter | A stored segment filter no longer parses, so its members can’t be listed. A filter rejected on write is invalid_request. |
| 400 | batch_too_large | More than 1,000 items in one batch. |
| 400 | email_suppressed | On POST /emails: the address hard-bounced, complained, or was provider-suppressed. |
| 401 | invalid_api_key | Missing, malformed, or unknown key. |
| 401 | revoked_api_key | The key was revoked. |
| 403 | plan_limit_reached | Out of subscriber headroom or monthly send allowance. |
| 403 | sending_disabled | Sending is off for this account. |
| 403 | domain_not_verified | The from domain isn’t a verified sending domain. |
| 403 | sandbox_recipient_not_allowed | Free plan, and the recipient isn’t a member of your organization. |
| 403 | insufficient_scope | The key lacks campaigns:send, automations:enroll or webhooks:manage. |
| 403 | test_keys_not_supported | A day3_test_ key was used. |
| 403 | forbidden | Not allowed for this key or account state. |
| 404 | not_found | No such object. Also returned for another organization’s ids. |
| 409 | contact_already_exists | The email is already in the audience, and you didn’t pass ?upsert=true. |
| 409 | email_suppressed | On contact writes: the address is on the account’s suppression list. |
| 409 | idempotency_conflict | Same Idempotency-Key, different body. Or that key is already in flight. |
| 409 | invalid_request | Conflicts with the object’s state: editing a campaign that left draft, or enrolling into an unpublished automation. |
| 422 | immutable_field | Changing something that can’t change, such as a field’s key. |
| 429 | rate_limit_exceeded | Over the request limit. Carries Retry-After. |
| 500 | internal_error | Our fault. Retry, and send us the request_id. |
What to retry
| Code | Retry? |
|---|---|
rate_limit_exceeded | Yes, after Retry-After seconds. |
internal_error | Yes, with backoff. |
idempotency_conflict (“already in progress”) | Yes, shortly. |
plan_limit_reached | No. The user needs to upgrade. |
domain_not_verified | No. Verify the domain. |
email_suppressed | Never. Retrying damages your sending reputation. |
sandbox_recipient_not_allowed | No. Upgrade, or send to an organization member. |
insufficient_scope | No. Mint a key with the scope. |
invalid_request, invalid_email | No. Fix the payload; param names the field. |
invalid_filter | No. Rewrite the segment’s filter in the app or over the API. |
Why email_suppressed has two statuses
400onPOST /emails: you asked to send to an unmailable address.409on contact writes: the contact conflicts with account-wide suppression state.
Both mean the address is unmailable. Retry neither.
Last updated on