Errors

Every error is JSON with a stable envelope:

json
{
  "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

StatusCodeMeaning
400invalid_requestA parameter is missing or malformed; param names it.
400link_domain_requiredshorten_links was set but the account has no active link domain. Nothing was sent. See link shortening.
401invalid_api_keyMissing, malformed, revoked, or unknown key.
403key_pausedThis 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.
403number_purchase_heldNew 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.
403live_access_requiredNeeds 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).
403test_mode_onlySandbox-only endpoint called with a live key.
403tenant_suspendedAccount suspended.
403account_closedThe account is closed, so nothing is sent or changed (test and live keys alike; its keys are revoked as well). See closing your account.
403email_unconfirmedThe account's email isn't confirmed yet: no new keys, webhooks or integrations until it is. Enter the emailed code in the console.
403forbiddenKey is valid but can't act on this resource (e.g. a from you don't own).
403insufficient_scopeA restricted key called outside its scopes; required_scope names the one needed. See scopes.
402insufficient_creditsYour credit balance cannot cover the call; nothing was done or charged. The body carries balance_micro_usd, required_micro_usd and top_up_url.
403spend_limit_reachedYour monthly spend limit is reached; the message was not sent or charged.
403recipient_opted_outThe recipient opted out of your messages. The body says when and how. See Opt-out.
403quiet_hoursA 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.
403consent_requiredNo 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.
403campaign_mismatchThe 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.
403reconfirmation_pendingA 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.
403reconfirmation_expiredThey did not answer the re-confirmation within 7 days; marketing to them is refused until they opt in again.
403held_for_reviewA 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.
403review_rejectedOur 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.
403campaign_pausedMarketing 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.
403account_pausedAll 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.
403sender_rotationThis 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.
403recipient_not_verifiedA free account sent to a number it has not verified.
403sender_not_registeredThe 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.
404not_foundNo 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).
405method_not_allowedThe path exists but not for this HTTP method. The Allow header lists the ones it supports
409idempotency_conflictIdempotency-Key reused with a different payload.
409idempotency_in_progressThe first request with this key is still running; retry after Retry-After (the SDKs do).
409verification_already_approvedThe verification was already approved; send a new code to verify again.
429rate_limitedToo many requests, or the sending number's registration is over its sending pace; check Retry-After.
429quota_exceededA daily quota was reached.
503screen_unavailableOur 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.
503channel_unavailableVerify email codes cannot go out right now. Nothing was sent or charged; send a text instead.
502carrier_errorThe 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).
500internal_errorOur 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.

CodeTitleWhat happened, and what to do
carrier_filteredFiltered as spamTheir 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.
unreachablePhone unreachableThe 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_numberNumber not in serviceThat number doesn't exist or isn't in service. Recommended: stop texting it and ask for a current number.
opted_outRecipient opted outThey opted out of your texts, so we didn't send it. Recommended: don't retry. They can opt back in by texting START.
campaign_mismatchOutside the registration's use caseThis 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_blockedContent blockedCarriers 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_unavailableContent check unavailableOur 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_limitedSending too fastToo 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.
landlineNumber can't receive textsThat'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_rejectedRejected by the carrierThe 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_reachedSpending limit reachedIt 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_creditsOut of creditsYou'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_registeredSending number not registered yetCarriers 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_hoursHeld back for quiet hoursIt 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_requiredNo opt-in on fileMarketing 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_pendingWaiting for the recipient to confirmYou 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_expiredRecipient did not confirmThey 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_reviewWaiting for reviewIn 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_rejectedNot approved in reviewWe 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_expiredReview did not finish in timeWe didn't review it within 3 days, so we dropped it. That's on us. Recommended: send it again.
campaign_pausedPaused for this registrationSending 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_rotationToo many of your numbersTwo 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_verifiedNot a verified test recipientFree 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_exceededMessage allowance reachedYour 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_allowedDestination not servedWe only text US and Canadian numbers. Recommended: remove the number, or tell us which country you need.
account_closedAccount closedThis account is closed, so it can't send. Recommended: if we closed it by mistake, email support before the data is deleted.
account_suspendedAccount suspendedThis account is suspended, so it can't send. Recommended: email support. We'll tell you why and how to restore it.
account_pausedSending paused for reviewLive 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_unsubscribedUnsubscribed from this topicThey unsubscribed from this topic, so we didn't send it. Recommended: don't retry. They still get your other topics.
unknownNot deliveredThe 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:

json
{
  "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.