Errors
One error shape everywhere, a stable code for every case, and what to do about each.
Every error, from any endpoint, has the same shape:
{
"error": {
"code": "insufficient_balance",
"message": "Insufficient balance: have 120, need 600 (cents)",
"details": null
}
}Prop
Type
The HTTP status tells you the category; code tells you exactly what happened.
Validation errors
A request that fails validation returns 400. details maps each problem field to its messages, and code is the code of the first problem:
{
"error": {
"code": "required",
"message": "This field is required.",
"details": {
"body": ["This field is required."],
"recipients": ["Ensure this field has no more than 1000 elements."]
}
}
}Problems that aren't tied to one field, such as invalid phone numbers or suppressed email addresses, appear under details.non_field_errors.
All codes
| Status | code | Meaning | What to do |
|---|---|---|---|
400 | required, blank, null, invalid, invalid_choice, max_length, min_length, max_value, min_value | A field is missing or invalid. See details. | Fix the request. Don't retry as-is. |
400 | message_too_long | The SMS needs more than 3 segments. | Shorten it; see segments. |
400 | invalid_idempotency_key | Idempotency-Key is empty or longer than 255 characters. | Use a UUID. |
400 | invalid_webhook_url | The webhook URL isn't public https:// on port 443. | Fix the URL. |
400 | invalid_event_type | Unknown webhook event type. | See Events. |
401 | not_authenticated | No API key was sent. | Send X-Api-Key. |
401 | authentication_failed | The key is malformed, unknown, revoked, or its account is inactive. | Check the key. |
402 | insufficient_balance | The wallet can't cover the whole send. Nothing was sent. | Top up, then retry. |
403 | insufficient_scope | The key lacks the scope this endpoint needs. | Use a key with that scope. |
403 | traffic_class_not_permitted | The account isn't approved for this traffic class. details.status says where the access request stands. | See Sending access. |
403 | permission_denied | Not allowed for another reason. | Contact support. |
404 | not_found | No such resource on your account. | Check the id. |
405 | method_not_allowed | Wrong HTTP method for this path. | Check the API reference. |
409 | idempotency_in_progress | A request with this Idempotency-Key is still running. | Retry after a second. |
422 | idempotency_key_reused | This Idempotency-Key was used with a different body. | Use a new key for a new request. |
429 | throttled | Too many requests. | Wait Retry-After seconds. See Rate limits. |
503 | pricing_unavailable | We can't price this destination right now. Nothing was charged. | Retry later, or contact support. |
5xx | – | Something went wrong on our side. The body may not be JSON. | Retry with the same Idempotency-Key. |
Which errors to retry
Retry
409, 429 (after Retry-After), 503, other 5xx, and timeouts or connection errors. Always send an Idempotency-Key so a retry can't send twice.
Don't retry
400, 401, 402, 403, 404, 422. The same request will fail the same way until you change something.