Errors
Envelope
Every error returns the same shape. Parse error.code, not the message text, which can change.
{
"error": {
"code": "recipient_opted_out",
"message": "This recipient opted out of campaign C7X4M2P on 2026-07-19T14:22:03Z.",
"status": 422,
"request_id": "req_01J8ZQ6D4H8V5X7Y",
"docs_url": "https://anthonyoliva.com/docs/errors#recipient_opted_out"
}
}
Include request_id in any support ticket. It lets us find the exact request in our logs.
HTTP status codes
| Status | Meaning |
|---|---|
400 |
Malformed request |
401 |
Missing or invalid credentials |
403 |
Authenticated but not permitted |
404 |
Resource does not exist on your account |
409 |
Conflict, usually idempotency or inventory |
422 |
Well-formed but cannot be processed |
429 |
Rate limited |
5xx |
Our fault. Safe to retry with backoff |
Authentication and access
| Code | Status | Fix |
|---|---|---|
invalid_credentials |
401 | Check the key ID and secret. Test keys do not work against live resources |
key_revoked |
401 | The key was revoked. Create a new one |
insufficient_scope |
403 | The key lacks the required scope. See Authentication |
ip_not_allowed |
403 | Source address is outside your allowlist |
account_suspended |
403 | Contact support. Usually billing or a policy issue |
Sending
| Code | Status | Fix |
|---|---|---|
recipient_opted_out |
422 | The recipient opted out. This cannot be overridden through the API |
campaign_not_approved |
422 | The campaign is not approved. Check its status |
number_not_attached |
422 | Attach the sending number to an approved campaign |
number_not_owned |
422 | The from number is not on your account |
invalid_destination |
422 | Not a valid E.164 mobile number |
landline_destination |
422 | Destination is a landline. Run a lookup first |
body_too_long |
400 | Over 1,600 characters |
media_too_large |
422 | Media exceeds 10 MB before transcoding |
media_unreachable |
422 | We could not fetch the media URL. It must be publicly reachable over HTTPS |
unsupported_media_type |
422 | See the supported list on the MMS page |
prohibited_content |
422 | Content matched a prohibited category. See the Acceptable Use Policy |
shortener_not_allowed |
422 | Public URL shorteners are blocked by carriers. Use a branded domain |
throughput_exceeded |
429 | Queued beyond your carrier-assigned throughput. Slow down or request an increase |
Registration
| Code | Status | Fix |
|---|---|---|
brand_verification_failed |
422 | Legal name, EIN, or address did not match public records |
ein_too_recent |
422 | An EIN issued in the last 15 days is not yet verifiable. Wait |
website_unreachable |
422 | The brand website did not load for the reviewer |
website_not_compliant |
422 | Missing privacy policy, missing terms, or missing the mobile sharing clause |
message_flow_insufficient |
422 | The opt-in description does not let a reviewer verify consent |
sample_messages_invalid |
422 | Unresolved merge fields, or samples that do not match the use case |
use_case_not_eligible |
422 | This business category cannot use 10DLC. Toll-free may be an option |
Idempotency and rate limits
| Code | Status | Fix |
|---|---|---|
idempotency_key_reused |
409 | The same key was used with a different body. Use a new key |
idempotency_in_progress |
409 | An identical request is still running. Retry after a moment |
rate_limited |
429 | Back off using Retry-After, with exponential backoff and jitter |
Retry guidance
Retry 429 and 5xx with exponential backoff and jitter, up to five attempts. Always send an Idempotency-Key on writes so a retry after a timeout cannot duplicate a message.
Do not retry 4xx other than 429. The request will fail identically until you change it.
Carrier error codes
When a carrier rejects or fails a message, the delivery receipt carries the carrier’s own code alongside ours:
{
"event": "message.undelivered",
"data": {
"id": "msg_01J8ZQ4X7K2M9N3P",
"status": "undelivered",
"error_code": "carrier_filtered",
"carrier_error_code": "4770",
"carrier_error_message": "Message filtered by carrier spam engine"
}
}
carrier_filtered on a small share of traffic is normal. A sustained rise usually means content, sending pattern, or list quality — not a platform fault. The console shows filtering rate per carrier over time so you can see when it started.