Errors
Every error is JSON with a stable envelope:
{
"error": {
"code": "invalid_request",
"message": "`to` must be a valid US/Canada number in E.164 format.",
"param": "to",
"request_id": "req_8Fq2ZkT0aLw4nXcV7pHs"
}
}Request IDs
Every response from an endpoint that takes your API key, success or error,
carries an X-Request-Id header
(req_…), and error bodies repeat it as request_id. Look it up in the
console under Logs to see the request and response as we received and
sent them (API keys, secrets, verification codes, and card data are
redacted; bodies are truncated at 8 KB; logs are kept for 30 days). Include
it when you contact support.
Codes
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A parameter is missing or malformed; param names it. |
| 401 | invalid_api_key | Missing, malformed, revoked, or unknown key. |
| 403 | live_access_required | Needs live access (or a live key before approval). |
| 403 | test_mode_only | Sandbox-only endpoint called with a live key. |
| 403 | tenant_suspended | Account suspended. |
| 403 | forbidden | Key is valid but can't act on this resource (e.g. a from you don't own). |
| 403 | insufficient_scope | A restricted key called outside its scopes; required_scope names the one needed. See scopes. |
| 403 | spend_limit_reached | Your monthly spend limit is reached; the message was not sent or charged. |
| 403 | sender_not_registered | The from number is not linked to an approved registration yet, so it can only text your verified numbers. X-SimpleSMS-Sender-State says where it stands. See Numbers. |
| 404 | not_found | No such resource on your account. |
| 409 | idempotency_conflict | Idempotency-Key reused with a different payload. |
| 429 | rate_limited | Too many requests; check Retry-After. |
| 429 | quota_exceeded | A daily quota was reached. |
| 502 | carrier_error | The carrier permanently rejected the message, or did not answer after we handed it over (so we will not risk sending it twice). Temporary carrier problems do not return this: the send answers 202 with the message queued and we retry it (Messages). |
| 500 | internal_error | Our fault. Retry, and tell us if it persists. |
Delivery failures
A message that was accepted but not delivered has status: "failed" and a
failure object, on the message and in the message.failed webhook:
{ code, title, explanation, action, carrier_code }. The codes are stable;
switch on code, show title and explanation to your team, and follow
action. carrier_code is the carrier's own code when it sent one, for
support conversations.
| Code | Title | What happened, and what to do |
|---|---|---|
carrier_filtered | Filtered as spam | The recipient's mobile carrier filtered this message as spam or unwanted traffic, so it never reached the phone. Recommended: identify your business in the first words, avoid link shorteners and all-caps, make sure the recipient opted in, and keep similar messages from going out in bursts. Repeated filtering on the same content usually means the wording needs to change. |
unreachable | Phone unreachable | The number is valid, but the phone could not be reached: it was switched off, out of coverage, or its inbox was full until the carrier gave up. Recommended: retry later (hours, not seconds). If it keeps failing, confirm the number with the recipient. |
invalid_number | Number not in service | The destination number does not exist or is no longer assigned to a phone. Recommended: stop sending to this number and ask the recipient for an up-to-date one. Retrying will not help. |
opted_out | Recipient opted out | The recipient has unsubscribed from your messages (for example by replying STOP), so the message was not sent. Recommended: do not retry. Only send again if the recipient opts back in, for example by texting START to your number. |
content_blocked | Content blocked | The message content is not allowed on the network, for example a prohibited topic or a blocked link. Recommended: review the Messaging Policy, change the wording or link, and send again. Resending the same text will fail the same way. |
rate_limited | Sending too fast | Too many messages went to this number or through this route in a short time, so this one was held back. Recommended: slow down and retry with backoff. Honor the Retry-After header when the API returns 429. |
landline | Number can't receive texts | The destination is a landline or another line that does not accept text messages. Recommended: use a mobile number for this recipient. A lookup (GET /v1/lookup) tells you the line type before you send. |
carrier_rejected | Rejected by the carrier | The carrier refused the message before trying to deliver it, for example because the route or sender is not allowed to reach this number. Recommended: check that the sending number is set up for this kind of traffic. If every message to a carrier is rejected, contact support with the message id. |
spend_limit_reached | Spending limit reached | Sending this message would take your account past the monthly spending limit you set, so it was not sent and nothing was charged. Recommended: raise or remove the limit (console Settings → Spend limit, or PATCH /v1/spend-limit), or wait until the 1st (UTC). Refused messages are not queued: send them again once there is room. |
sender_not_registered | Sending number not registered yet | The number this was sent from is not linked to an approved registration yet, so it can only text numbers you have verified. This recipient is not one of them, so the message was not sent and nothing was charged. Recommended: open the number in the console (Numbers) to see where its registration stands and what, if anything, is needed from you. Once the number shows Active, send again. Meanwhile you can text your verified numbers. |
unknown | Not delivered | The carrier reported the message as undelivered without giving a reason. Recommended: retry once later. If the same number keeps failing, contact support with the message id. |
Some sends are refused before a message exists: the recipient opted out, the
sending number is not registered yet, the content is blocked, the number is
rate limited, or the send would pass your
spending limit. Those answer 403 or 429 and the
error carries the same object, so one handler covers both:
{
"error": {
"code": "recipient_opted_out",
"message": "This recipient opted out of your messages on Oct 5, 2026 at 8:42 AM UTC by replying STOP. Nothing was sent and you were not charged. They can opt back in by texting START to your number.",
"param": "to",
"opted_out_at": "2026-10-05T08:42:00.000Z",
"opted_out_via": "sms_keyword",
"opted_out_method": "keyword",
"failure": { "code": "opted_out", "title": "Recipient opted out", "explanation": "...", "action": "Recommended: ...", "carrier_code": null }
}
}Every code except content_blocked, spend_limit_reached and
sender_not_registered (sandbox numbers are never registered) has a
sandbox number that produces it, so you can test the handling
before going live.
Success codes
200 and 201 mean done. 202 (on POST /v1/messages) means
accepted and queued: a temporary carrier problem is being retried for you.
A replayed idempotent request returns the original status code with an
Idempotent-Replayed: true header.
Rate limits
Default 60 requests/minute per key. 429s include a Retry-After header.
Successful quota-limited calls include X-Quota-Remaining.