#Messages
POST /v1/messages is the send to use. POST /sms/send is an older equivalent, kept under Legacy endpoints. For what a text costs, see How a text is priced.
#Send a message
/v1/messagesSends one text. A 200 means accepted, not delivered.
Headers
Idempotency-KeystringRecommended- Unique per message; reuse only to retry (Idempotency).
Body parameters
toE.164 stringRequired- Recipient, E.164 recommended (
+12125550199). A 10-digit US number is treated as +1. bodystringRequired- The text, up to 25 segments.
messageis an alias.Details
25 segments is about 3,825 plain characters, or about 1,600 with emoji or other Unicode. consent_attestedbooleanRequired- Your promise this recipient agreed to texts from you. Not enforced yet.
Details
Once enforced, a send without it is refused with403 api_consent_attested_required. Today it still goes through with the normal response, and Ready records a warning on its side."true"and1are also accepted. fromE.164 stringOptional- One of your numbers; omit it and Ready picks.
Details
from_phone_number_idisn't supported here; usefrom. conversation_idintegerOptional- File the text in this conversation (from
GET /conversations).Details
Unless you setfrom, Ready prefers the number that conversation uses. An id that isn't on your account is ignored. quoted_message_idintegerOptional- Message
idin that conversation to quote (inbox only).Details
Get it fromGET /conversations/:id/messages. Ready saves its first 200 characters asquoted_texton the new message. The text the recipient gets is unchanged. override_quiet_hoursbooleanOptional- Send anyway after a
409 QUIET_HOURS_BLOCKthat has"can_override": true.Details
It lifts only the quiet hours set on your account, never the federal and state sending hours. Ready records it as your statement that this is a consented, conversational text.
More parameters (3)
media_urlstringOptional- A public
httporhttpsURL of an image to attach. Any other value is ignored and the text goes out as plain SMS. With an image, the text is limited to 1,600 characters and the message is billed as one image message. from_strategyenumOptionalDefaultlocal- How Ready picks the number when you don't set
from.localmatch the recipient's area codeevenspread across your numbers, least used first (deliverabilityis the same)random
confirm_empty_mergebooleanOptional- Send
trueto send even though a merge field is blank (seeempty_merge_fieldbelow).
Returns
data.message_idstring- Store it. Opaque, up to 64 characters: don't parse it.
Details
Don't assume a format either. Webhooks andGET /v1/messages/:iduse the same value, including when Ready resends a failed text for you: the public id stays the same (see Failures Ready resends for you). data.statusstringqueued;deliveredorfailedfollows in 5 to 30 seconds.data.segmentsinteger- What you're billed for, counted on
sent_body. data.sent_bodystring- The exact text sent, which can be longer than your
body. Ready may add your opt-out line and business name to meet carrier rules;sent_bodyis the exact text sent andsegmentsare counted on it. Include your own opt-out in the body to stop us adding ours. data.compliance_text_addedstring | null- Just the text Ready added, such as
Reply STOP to opt out. -Acme Co, ornullwhen nothing was added. data.sent_atISO 8601 string- When Ready handed it to the carrier.
Details
sent_at_unixis the same time in Unix seconds. data.carrier_feenumber- Carrier fee in USD (
segments × carrier_fee_usd_per_segment). data.balance_usdnumber- Your balance after this send.
Details
carrier_balanceis the same balance to 4 decimal places. data.credits_remaininginteger- Approximate 1-segment texts your balance covers.
Details
Counted at the standard registered rate.
What the response means
202,error_code: "send_outcome_unknown": it may have been delivered and charged.Details
The body also has"success": falseandoutcome: "unknown". A retry with the same key returns this202again; see When to retry before you send it with a new key.200,error_code: "empty_merge_field": a blank merge field; nothing sent.Details
The body has"success": false,needs_confirmationandempty_fields. Send again with"confirm_empty_merge": trueto send it with the blank.- Rarely,
200with"recorded": falseand nomessage_id: sent and charged, not saved. Don't resend.Details
The body also haswarning.
Opt-outs, blocklisted numbers and known landlines are refused up front, uncharged.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | no code | Missing to or body. |
| 400 | invalid_field | Wrong type (field names it). |
| 400 | invalid_from | Not one of your numbers. |
| 400 | no_sender_number | No number to send from.DetailsAlsono_country_sender: none of your numbers can text the recipient's country. |
| 400 | MESSAGE_TOO_LONG | Over 25 segments (1,600 characters with an image). |
| 400 | CONTENT_BLOCKED | Blocked content; a compliance fee may apply. |
| 402 | insufficient_balance | Balance too low.DetailsAlsoCARD_REQUIRED, SANDBOX_LIMIT, and MONTHLY_CAP_REACHED, BILLING_PAST_DUE or FRAUD_HOLD (these three in code). |
| 403 | opt_out_locked | Contact opted out.Detailsreason says how: STOP, marked opted out, or Do Not Disturb for texts. Also tcpa_block, global_blocklist, number_not_assigned. |
| 403 | api_consent_attested_required | consent_attested missing, once enforced. |
| 409 | QUIET_HOURS_BLOCK | Quiet hours (in code, with next_allowed_at). |
| 409 | RAMP_CAP_REACHED | Warm-up cap used up (in code). |
| 409 | duplicate_send | Same text and number within 60 seconds, no key. |
| 409 | number_activating | Number still activating. Wait Retry-After. |
| 422 | INVALID_DESTINATION | Not a valid phone number. |
| 429 | rate_limited | Over carrier pace. Wait Retry-After, same key. |
| 429 | daily_cap | Daily cap hit; resets midnight Pacific.DetailsAlsonumber_daily_cap. No Retry-After. |
| 500 | send_failed | Not sent. New key, in a minute.DetailsAlsohold_not_returned. |
| 500 | INTERNAL_ERROR_OUTCOME_UNKNOWN | Outcome unknown, like a 202 (in code). |
| 503 | provider_busy | Carrier busy. Wait Retry-After, new key. |
| 503 | gate_check_failed | A check couldn't run; not sent.DetailsAlsocarrier_capacity_busy, contact_unavailable, consent_check_failed, sending_paused, permission_check_unavailable, ADMISSION_SHED. |
| 504 | REQUEST_TIMEOUT_OUTCOME_UNKNOWN | Over 30 seconds, may still send. Same key, after a minute. |
#Send to many
/v1/messages/bulkSends one text to up to 100 recipients, with a result for each.
Details
Set a client timeout of at least 60 seconds. After a timeout, retry with the sameIdempotency-Key.Headers
Idempotency-KeystringRecommended- One key for the whole batch.
Body parameters
tostring[]Required- Up to 100 recipients, E.164 recommended.
Details
Entries that aren't non-empty strings are skipped and not counted intotal. Duplicate numbers aren't merged: the repeat is refused asduplicate_send. bodystringRequired- The text for every recipient.
messageis an alias. consent_attestedbooleanRequired- Your promise that every recipient agreed to texts from you. Not refused yet (as on
/v1/messages). fromE.164 stringOptional- One of your numbers.
media_urlstringOptional- Image URL, as in Send a message.
Returns
Returns 200 once the batch is processed, even if every recipient failed. Check failed and each results[].sent.
Details
With a test key, each result has a test-msg-… message_id and its segments, and nothing is sent.
data.totalinteger- Recipients processed.
data.sentinteger- How many were accepted.
data.failedinteger- How many were refused.
data.results[]object[]- One per recipient:
to,status,sent, and eithermessage_id,segmentsandsent_body(the exact text sent, see Send a message), orerroranderror_code.Details
statusis the HTTP status a single send would have returned.
Ready checks the whole batch against your balance and carrier throughput first; if either fails, nothing is sent.
Details
402 insufficient_credits has required_cents, balance_cents and recipients. 429 throughput_exceeded has retry_after_ms and cap_per_sec.
A refused recipient's error_code is what a single send returns (in error_code or code), else "send_failed". Read error before retrying.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | to isn't a list, or there's no body. |
| 400 | too_many_recipients | More than 100 recipients. |
| 400 | invalid_from | Not one of your numbers. |
| 402 | insufficient_credits | Balance can't cover the batch. |
| 409 | idempotency_in_progress | Same key while the batch still runs. |
| 429 | throughput_exceeded | Over your carrier throughput. |
| 429 | rate_limited | Over the per-account limits. |
#Get a message
/v1/messages/:idThe delivery status of one message you sent or received.
Path parameters
idstringRequired- The
message_ida send returned.
Returns
data.statusstringqueued,delivered,failed,undeliveredorunknownfor outbound;receivedfor inbound.unknownmeans carrier acceptance could not be confirmed and does not prove the text was unsent.failedisn't always final (below).data.error_reasonstring | null- A stable value to branch on,
nullunlessstatusisfailedorundelivered.Details
Specific reasons:recipient_destination_rejected(destination rejected, check the number and text service),sender_network_not_provisioned(sending account lacks network coverage),recipient_cannot_receive_sms(text service unavailable or inactive, check with the contact),recipient_out_of_credit(prepaid plan with no credit),recipient_network_unknown(new, ported or out-of-service number),recipient_unreachable(phone off or out of coverage),recipient_blocked(the recipient's carrier blocks texts to this number),carrier_delivery_failed(accepted, then not delivered, no reason given) andcarrier_filtered_content(spam filter). General reasons when the carrier gives no cause:carrier_rejected,undeliverable,expired,invalid_destination,temporary_failure,rejectedandunknown. What to do with each is in the table below. data.error_detailstring | null- A plain sentence to show a person. Don't match on it.
Details
Its wording can change.nullwhenerror_reasonis. data.from, data.toE.164 string- Sender and recipient.
data.bodystring- The text you asked to send.
data.sent_bodystring- The exact text sent. Ready may add your opt-out line and business name to meet carrier rules;
sent_bodyis the exact text sent andsegmentsare counted on it. Include your own opt-out in the body to stop us adding ours. data.segmentsinteger- Billed segments, counted on
sent_body. data.created_at, sent_at, delivered_atISO 8601 string | null- When it was created, sent and delivered.
Failures Ready resends for you
Ready resends some failed texts itself. Don't resend these yourself, or the recipient gets it twice and you pay twice.
expiredandundeliverable(no more specific carrier reason): resent, usually once, about 30 minutes later.Details
Ready resends the same text. It never resends a text more than 6 hours after you sent it, so time-sensitive copy isn't sent late.rejected, and acarrier_rejectedwhere the carrier refused your number: resent once, within 3 hours of sending.Details
The resend may go from another of your numbers.- Each resend is billed like the original send.
- The resend keeps the same public
message_id; your original id still resolves.Details
GET /v1/messageslists the message under the same public id. Itsstatusgoes fromfailedback toqueued, then todeliveredorfailed. - Webhooks:
message_failed, then the resend's outcome under the same publicmessage_id. Match that id to track the message across attempts.Details
The resend's outcome ismessage_deliveredormessage_failed.
When to retry:
error_reason | Retry? |
|---|---|
expired, undeliverable, rejected | No. Ready resends these (above). |
temporary_failure | Yes, later, with a new key. Ready doesn't resend it. |
recipient_out_of_credit | Yes, a day later with a new key. Ready doesn't resend it today.DetailsBy then the recipient may have topped up. |
recipient_unreachable, carrier_delivery_failed, recipient_blocked, recipient_destination_rejected, recipient_cannot_receive_sms, invalid_destination | No. Check the number with the contact. |
carrier_filtered_content | Reword the text first. |
sender_network_not_provisioned | No. Contact support. |
Errors
| HTTP | Code | When |
|---|---|---|
| 404 | message_not_found | No such message on your account (or it's another account's). |
#List messages
/v1/messagesYour sent and received messages, newest first. Pages with a cursor.
Query parameters
directionenumOptionalinboundoroutbound.toE.164 stringOptional- Only messages to this number.
fromE.164 stringOptional- Only messages from this number.
statusstringOptionalqueued,delivered,failed,undelivered,unknownorreceived.limitintegerOptionalDefault50Max200- Page size.
before_idintegerOptional- The
next_before_idfrom the previous page (not a message id).Details
offsetisn't supported and is ignored.
Returns
data[]object[]- Messages.
Details
id(the same value asmessage_id),direction,from,to,body(the text you asked to send),sent_body(the exact text sent),status,error_reason,error_detail,segments,cost_usd,media_url,created_at,delivered_at. Ready may add your opt-out line and business name to meet carrier rules;sent_bodyis the exact text sent andsegmentsare counted on it. Include your own opt-out in the body to stop us adding ours. has_moreboolean- Keep paging while
true. The last page can be empty.Details
It'strueon an exactly full page. next_before_idinteger | null- Pass as
before_idfor the next page;nullon an empty page.
Specific delivery failures
Every failed or undelivered message includes error_reason, a stable cause to group on, and error_detail, a readable explanation to display or show on hover. Both are null for messages that did not fail. These match GET /v1/messages/:id and the message_failed webhook. Display error_detail; match and count by error_reason, because the wording can change.
| error_reason | What the carrier reported |
|---|---|
recipient_destination_rejected | The destination was rejected. The report does not establish that the number is disconnected. |
recipient_out_of_credit | The recipient’s prepaid phone plan ran out of credit. |
recipient_network_unknown | The carrier could not identify the recipient’s network. This does not establish that the number has no carrier. |
carrier_delivery_failed | Delivery failed without a specific cause. |
recipient_unreachable | The phone could not be reached. The report does not establish why. |
For a campaign you track in your app, keep the message_id returned for each attempt and count matching messages by error_reason. Read every page using next_before_id, or update each message’s latest outcome from webhooks; do not count a repeated webhook as a new failure. status=failed returns failed and rejected attempts; check status=undelivered for legacy undelivered or expired attempts. A page’s counts alone are not the campaign’s complete breakdown. If no specific cause was recorded, the API preserves a general or unknown reason instead of inventing one.
Errors
| HTTP | Code | When |
|---|---|---|
| 429 | rate_limited | Over the per-account limit. |