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. |
| 400 | link_domain_required | shorten_links was set but the account has no active link domain. Nothing was sent. See link shortening. |
| 401 | invalid_api_key | Missing, malformed, revoked, or unknown key. |
| 403 | key_paused | This live key is paused: it suddenly made far more requests than usual from a new country. An admin resumes or rolls it under API keys. See unusual key activity. |
| 403 | number_purchase_held | New live numbers are on hold: a number carriers were filtering was released and replaced within 7 days. Existing numbers keep working; contact support. See replacing a filtered number. |
| 403 | live_access_required | Needs live access (or a live key before approval), or a live key for data the whole account shares once it is live (test and live data). |
| 403 | test_mode_only | Sandbox-only endpoint called with a live key. |
| 403 | tenant_suspended | Account suspended. |
| 403 | account_closed | The account is closed, so nothing is sent or changed (test and live keys alike; its keys are revoked as well). See closing your account. |
| 403 | email_unconfirmed | The account's email isn't confirmed yet: no new keys, webhooks or integrations until it is. Enter the emailed code in the console. |
| 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. |
| 402 | insufficient_credits | Your credit balance cannot cover the call; nothing was done or charged. The body carries balance_micro_usd, required_micro_usd and top_up_url. |
| 403 | spend_limit_reached | Your monthly spend limit is reached; the message was not sent or charged. |
| 403 | recipient_opted_out | The recipient opted out of your messages. The body says when and how. See Opt-out. |
| 403 | quiet_hours | A single marketing send (or any send with "quiet_hours": "block") outside 8:00am to 9:00pm in the recipient's local time (8:00pm in Florida, Maryland and Oklahoma). Other messages are held, not refused. The body carries recipient_local_time, timezone and send_after; nothing was sent or charged. See quiet hours. |
| 403 | consent_required | No opt-in on file for this message: a marketing message, or a recurring message from a number linked to a registration (which needs an opt-in for that registration); nothing was sent or charged. Record the opt-in (consent API with campaign_id, the console's Record opt-in, contact import, or a START reply), or, for a non-marketing message, send consent with it. See opt-in on file. |
| 403 | campaign_mismatch | The message is outside the sending number's registration: promotional wording or "category": "marketing" on a non-marketing registration, anything but a one-time code from a two-factor registration, or a kind of message our registration check found doesn't match (mismatch: "off_use_case", with checked_by: "consistency_check" and fingerprint). Nothing was sent or charged. The body carries registration_id, use_case and mismatch. See which messages are marketing. |
| 403 | reconfirmation_pending | A marketing message to someone your registered number has not texted in 30 days: we asked them to confirm first and are holding this message (held: true), or the question is still open (held: false). See re-confirmation. |
| 403 | reconfirmation_expired | They did not answer the re-confirmation within 7 days; marketing to them is refused until they opt in again. |
| 403 | held_for_review | A new kind of message from an account in its first 30 days of live sending: held for our team to review (held: true, with fingerprint, registration_id and expires_at), or not held because 10,000 of the same kind already wait (held: false). Nothing was charged. See review of new kinds of message. |
| 403 | review_rejected | Our team reviewed this kind of message for the sending number's registration and did not approve it; the reason is in the message. Nothing was sent or charged. |
| 403 | campaign_paused | Marketing from this number is paused because more than 4% of its registration's recipients opted out in 24 hours; nothing was sent or charged. Transactional messages still go out. See opt-out rate. |
| 403 | account_paused | All live sending from your account is paused while our team reviews messages that were blocked as phishing, impersonation or fraud (three to two or more people within 7 days). Your keys and data still work; batches and scheduled messages wait. Nothing was sent or charged. Reply to our email to appeal. |
| 403 | sender_rotation | This person was texted from two of your other numbers in the last 24 hours, so it was not sent from a third (carriers treat it as filter evasion). Text them from one of those numbers; a reply to someone who texted this number is never refused. Nothing was sent or charged. |
| 403 | recipient_not_verified | A free account sent to a number it has not verified. |
| 403 | sender_not_registered | The from number is not linked to an approved registration yet, so it can only text your verified numbers. X-TextKit-Sender-State says where it stands. See Numbers. |
| 404 | not_found | No such resource on your account, or it belongs to the other mode: a test key sees only sandbox data, a live key only live data (test and live data). |
| 405 | method_not_allowed | The path exists but not for this HTTP method. The Allow header lists the ones it supports |
| 409 | idempotency_conflict | Idempotency-Key reused with a different payload. |
| 409 | idempotency_in_progress | The first request with this key is still running; retry after Retry-After (the SDKs do). |
| 409 | verification_already_approved | The verification was already approved; send a new code to verify again. |
| 429 | rate_limited | Too many requests, or the sending number's registration is over its sending pace; check Retry-After. |
| 429 | quota_exceeded | A daily quota was reached. |
| 503 | screen_unavailable | Our content screen could not check the message just now, so it was not sent or charged (we never send a message we could not check). Send it again after Retry-After; batches and scheduled messages wait and retry for about 15 minutes before giving up. See content screen. |
| 503 | channel_unavailable | Verify email codes cannot go out right now. Nothing was sent or charged; send a text instead. |
| 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 | Their carrier flagged it as spam, so it never reached the phone. Recommended: say who you are up front, drop link shorteners and all-caps, and change wording that keeps getting filtered. |
unreachable | Phone unreachable | The phone was off, out of coverage or full, and the carrier gave up. Recommended: retry in a few hours, and confirm the number if it keeps failing. |
invalid_number | Number not in service | That number doesn't exist or isn't in service. Recommended: stop texting it and ask for a current number. |
opted_out | Recipient opted out | They opted out of your texts, so we didn't send it. Recommended: don't retry. They can opt back in by texting START. |
campaign_mismatch | Outside the registration's use case | This number's registration doesn't cover this kind of message, so we didn't send it. Recommended: send it from a number registered for it, or cut the promotional wording. |
content_blocked | Content blocked | Carriers don't allow this content, usually a banned topic or a blocked link. Recommended: change the wording or link (see the Messaging Policy) and send again. |
screen_unavailable | Content check unavailable | Our content check was briefly down, so we didn't send it. That's on us. Recommended: send it again after Retry-After. Batches and scheduled sends retry on their own for 15 minutes. |
rate_limited | Sending too fast | Too many texts went out too fast for this number, registration or carrier, so we held this one. Recommended: retry with backoff and honor Retry-After. If a carrier's daily cap was hit, try tomorrow. |
landline | Number can't receive texts | That's a landline or another line that can't get texts. Recommended: get a mobile number. GET /v1/lookup shows the line type before you send. |
carrier_rejected | Rejected by the carrier | The carrier refused it, usually because this sender isn't allowed to reach this number. Recommended: check the sending number is set up for this kind of message. If one carrier rejects everything, send us the message id. |
spend_limit_reached | Spending limit reached | It would have gone over your monthly spend limit, so we didn't send it. Recommended: raise the limit in Settings (or PATCH /v1/spend-limit) and send it again. |
insufficient_credits | Out of credits | You're out of credit, so we didn't send it. Recommended: add credits in Billing, or turn on auto-recharge, and send it again. |
sender_not_registered | Sending number not registered yet | Carriers haven't approved this number yet, so it can only text numbers you've verified. Recommended: open the number in Numbers to see where approval stands, and send again once it's Active. |
quiet_hours | Held back for quiet hours | It was outside 8am to 9pm (8pm in Florida, Maryland and Oklahoma) in the recipient's time zone, so we didn't send it. Recommended: send it after send_after, or pass quiet_hours: "defer" and we'll send it when the window opens. |
consent_required | No opt-in on file | Marketing and recurring texts need an opt-in on file, and this recipient has none. Recommended: record it first with POST /v1/consent/{phone}. For non-marketing texts, pass "consent": {"source", "collected_at"} instead. |
reconfirmation_pending | Waiting for the recipient to confirm | You hadn't texted them in 30 days, so we asked them to confirm they still want marketing. Recommended: nothing. It sends if they reply YES within 7 days. |
reconfirmation_expired | Recipient did not confirm | They didn't confirm they still want marketing within 7 days, so we dropped it. Recommended: don't retry. Marketing resumes if they reply YES or START, or you record a new opt-in. |
held_for_review | Waiting for review | In your first 30 days live we review each new kind of message, and this one is waiting on us. Recommended: nothing. We usually review within a business day, then it sends. |
review_rejected | Not approved in review | We didn't approve this kind of message for this registration. The reason is in your audit log. Recommended: match what you send to the registration, or email support if we got it wrong. |
review_expired | Review did not finish in time | We didn't review it within 3 days, so we dropped it. That's on us. Recommended: send it again. |
campaign_paused | Paused for this registration | Sending from this registration is paused, either for over 4% opt-outs in 24 hours or for our review. Recommended: make sure everyone you text opted in, then email support to resume. |
sender_rotation | Too many of your numbers | Two of your other numbers already texted this person in the last 24 hours, and carriers read a third as dodging filters. Recommended: text each person from one number. A pool with sticky sender does this for you. |
recipient_not_verified | Not a verified test recipient | Free accounts can only text verified numbers, and this isn't one. Recommended: verify it under Home, Test recipients, or add credits in Billing to text anyone. |
quota_exceeded | Message allowance reached | Your daily or monthly message allowance ran out, so we didn't send it. Recommended: wait for the reset (midnight UTC daily, the 1st monthly) or add credits, then send it again. |
destination_not_allowed | Destination not served | We only text US and Canadian numbers. Recommended: remove the number, or tell us which country you need. |
account_closed | Account closed | This account is closed, so it can't send. Recommended: if we closed it by mistake, email support before the data is deleted. |
account_suspended | Account suspended | This account is suspended, so it can't send. Recommended: email support. We'll tell you why and how to restore it. |
account_paused | Sending paused for review | Live sending is paused while we review messages blocked as phishing, impersonation or fraud. Recommended: reply to our email to appeal, saying who sent them and why. |
topic_unsubscribed | Unsubscribed from this topic | They unsubscribed from this topic, so we didn't send it. Recommended: don't retry. They still get your other topics. |
unknown | Not delivered | The carrier says it wasn't delivered and won't say why. Recommended: retry once later. If it keeps failing, send us the message id. |
Some sends are refused by one of our checks
before any carrier sees them: the recipient opted out, it is quiet hours for
a marketing message, the sending number is not registered yet, the content
is blocked, the number is rate limited, or the send would pass your
spending limit or your credit balance. Those answer
402, 403 or 429, the error carries the same failure
object, and the attempt is stored as a message with status: "blocked"
(its id is message_id in the error), so one handler covers both and
your log shows every attempt:
{
"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, screen_unavailable, spend_limit_reached,
insufficient_credits 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.