Resources
What each code means, and whether the message was billed.
The column most references leave out is the last one. A message refused before it reaches an operator costs nothing; one the operator accepted and then failed to deliver is billed, because the operator charges us for it either way. Knowing which is which is the difference between a bug and an invoice question.
Returned synchronously when you call the API. None of these are billed — the message never existed.
| HTTP | Code | Meaning | Billed |
|---|---|---|---|
| 401 | UNAUTHORIZED | The key is unknown, revoked, missing from the Authorization header, or lacks the scope for this endpoint. | No |
| 403 | ACCOUNT_NOT_ACTIVE | The account is suspended or closed. Keys keep working the moment it is reactivated. | No |
| 403 | IP_NOT_ALLOWED | Your account has a source allowlist and this request came from outside it. An empty allowlist accepts any address. | No |
| 413 | PAYLOAD_TOO_LARGE | The request body exceeded the limit. A single send is small. | No |
| 429 | RATE_LIMIT_EXCEEDED | Above your account TPS. The Retry-After header says how long to wait. | No |
| 402 | INSUFFICIENT_BALANCE | Prepaid balance below the cost of this send. | No |
| 422 | INVALID_NUMBER | Not a valid Indian mobile number after normalisation. +91, 91 and leading zeroes are all handled. | No |
| 422 | VAR_COUNT_MISMATCH | The number of values in vars does not match the {#var#} slots in the template. | No |
| 422 | VAR_TOO_LONG | A variable exceeds the length the template was registered with. | No |
| 422 | SUPPRESSED | The number is on your own opt-out list or the platform blacklist. | No |
| 400 | INVALID_MESSAGE_ID | The message id in the path is empty or implausibly long. | No |
| 404 | MESSAGE_NOT_FOUND | No message with that id on your account. An id belonging to another account returns this too, deliberately. | No |
| 502 | NO_GATEWAY | No vendor could carry it — none eligible, or every eligible one refused. The reason names the vendors considered. Nothing was sent. | No |
Checked in order — entity, chain, sender ID, template, content — and the first failure is the one reported. The later checks usually fail as a consequence of the earlier ones, so fixing the reported code is the right place to start. None of these are billed.
| Code | Meaning | What to do |
|---|---|---|
| DLT-201 | PE not verified | No Principal Entity is registered for the account, or it is not verified. Indian traffic cannot be enabled without one. |
| DLT-202 | Chain not verified | The PE-TM chain is missing, pending, rejected or stale for the selected route. |
| DLT-203 | Header invalid | The sender ID is not registered on this account, is not approved, or has not been re-confirmed with the operator recently. |
| DLT-204 | Template invalid | The template is not registered, not approved, not mapped to this sender ID, or has not been re-confirmed recently. |
| DLT-205 | Content mismatch | The message does not match the approved template. Fixed wording must be identical; only the variable parts may differ. |
| DLT-206 | Category or policy | Header and template categories disagree, or a policy check failed. |
| DLT-207 | Upstream rejected | The operator refused it. The raw provider code is preserved alongside ours — ask us for it if you are disputing a charge. |
These arrive on the delivery receipt, after the message reached an operator. Every one of them is billed — charging happens on submission, and the outcome does not change the price. The vocabulary is ours and it is small on purpose: carriers each number their failures differently, so a report built on one carrier’s numbering breaks the day we route you through another. We keep the carrier’s original alongside it, so a disputed charge can be traced to exactly what they said.
| error_code | Meaning | What it tells you |
|---|---|---|
| INVALID_NUMBER | Number not in service | The number is unallocated, disconnected or malformed. Remove it from the list. |
| HANDSET_UNREACHABLE | Handset unreachable | Switched off, out of coverage, memory full or roaming-barred at the time. |
| NETWORK | Operator network error | A fault on the operator side. Usually transient — a resend later often lands. |
| EXPIRED | Validity expired | The operator held it past its validity period without delivering it. |
| SUPPRESSED | On your suppression list | You have suppressed this number, so it was never sent. |
| CONTENT_REJECTED | Content or sender not accepted | The content or sender ID did not match what is registered. Check the template. |
| OPERATOR_REJECTED | Declined by the operator | The operator did not accept the message for this subscriber. |
| UNDELIVERED | Not delivered | The operator returned no usable reason. |
The interim codes are the ones that catch people out
Several operator codes look like failures and are not — the operator has simply not finished. Treating them as final marks live messages dead and produces a delivery rate that is wrong in your favour, which is worse than one that is wrong against you: it hides a real problem. We hold them as in-flight until a terminal receipt arrives, which is why a small number of messages stay SUBMITTED for a while.
Operators add codes without notice, and a code we do not recognise is reported as UNKNOWN_REASON with the original preserved rather than being guessed at. If you are seeing one, send us the message id and we will tell you what the operator actually returned.