Skip to main content
Every failure is a JSON object with a single message. The status is the machine-readable part.
The full catalogue is in API errors. This page is about what your client should do.

The retry table

There is no Retry-After on a 429. Back off on your own schedule — exponential, with jitter. A fixed interval across many workers re-synchronises them into the next burst, which is how a brief rate-limit becomes a sustained one.

Why a 500 is safe to retry

500 Cannot send message means the gateway accepted your request and could not queue it. Any charge is reversed. That is a deliberate property of the ingress rather than an accident: credit is taken before queueing and given back when queueing fails. So a retry costs one message, not two — which is exactly the guarantee that makes an automatic retry defensible here and not on a 402.

Build for the receipt, not the response

The single most common integration mistake is treating a 200 as delivery. The submit response is sent before routing runs, so acceptance and delivery are unrelated statements. A message can be accepted and then refused for an unapproved sender, dropped for want of a route, or rejected by the carrier — and none of that can appear on the original call.
1

Store the messageId

It is the only thing that correlates your record with everything that follows.
2

Treat the send as 'submitted', not 'sent'

A separate state in your own model, so the difference stays visible.
3

Move to a final state only on a level 2 receipt

ACCEPTD and BUFFRED arrive as level 1 and another receipt follows.
4

Time out on your own clock

A message that never gets a final receipt should expire in your system too, or it sits “submitted” forever.

Errors you will meet in production, not in testing

Your account has sender-ID approval enforced. The from you used has not been approved, or you omitted it and your login name is not approved either.Not retryable, and not fixable in code — it needs an approval from your provider.
Your account sends only approved wording. A message that works in staging and fails in production is usually this.Whitespace is folded before matching, so extra spaces and newlines are not the cause. A changed word, an added full stop, or a variable in a different position is.
Credit is reserved in blocks, not per message — around twenty messages’ worth at a time — and refills are asynchronous. A message arriving while the next reservation is in flight is refused even though credit remains.That one is transient: retry it. A 402 that persists with a healthy balance is not, and is worth raising.
Every other field treats _ and - as the same character. custom_tlvs is the one exception and keeps its underscore.

Every status

The full catalogue with what provokes each.

Delivery receipts

The half of the story the response cannot tell you.