ready. Try Ready →Sign up

#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

POST/v1/messages

Sends one text. A 200 means accepted, not delivered.

  • Idempotent Yes, with Idempotency-Key
  • Rate limit Paced to your carrier throughput; 6,000/min ceiling
  • Cost segments × your rate, plus fees (pricing)

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. message is an alias.
Details25 segments is about 3,825 plain characters, or about 1,600 with emoji or other Unicode.
fromE.164 stringOptional
One of your numbers; omit it and Ready picks.
Detailsfrom_phone_number_id isn't supported here; use from.
conversation_idintegerOptional
File the text in this conversation (from GET /conversations).
DetailsUnless you set from, Ready prefers the number that conversation uses. An id that isn't on your account is ignored.
quoted_message_idintegerOptional
Message id in that conversation to quote (inbox only).
DetailsGet it from GET /conversations/:id/messages. Ready saves its first 200 characters as quoted_text on the new message. The text the recipient gets is unchanged.
override_quiet_hoursbooleanOptional
Send anyway after a 409 QUIET_HOURS_BLOCK that has "can_override": true.
DetailsIt 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 http or https URL 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_strategyenumOptionalDefault local
How Ready picks the number when you don't set from.
  • local match the recipient's area code
  • even spread across your numbers, least used first (deliverability is the same)
  • random
confirm_empty_mergebooleanOptional
Send true to send even though a merge field is blank (see empty_merge_field below).

Returns

data.message_idstring
Store it. Opaque, up to 64 characters: don't parse it.
DetailsDon't assume a format either. Webhooks and GET /v1/messages/:id use 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.statusstring
queued; delivered or failed follows 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_body is the exact text sent and segments are 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, or null when nothing was added.
data.sent_atISO 8601 string
When Ready handed it to the carrier.
Detailssent_at_unix is 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.
Detailscarrier_balance is the same balance to 4 decimal places.
data.credits_remaininginteger
Approximate 1-segment texts your balance covers.
DetailsCounted at the standard registered rate.

What the response means

  • 202, error_code: "send_outcome_unknown": it may have been delivered and charged.
    DetailsThe body also has "success": false and outcome: "unknown". A retry with the same key returns this 202 again; see When to retry before you send it with a new key.
  • 200, error_code: "empty_merge_field": a blank merge field; nothing sent.
    DetailsThe body has "success": false, needs_confirmation and empty_fields. Send again with "confirm_empty_merge": true to send it with the blank.
  • Rarely, 200 with "recorded": false and no message_id: sent and charged, not saved. Don't resend.
    DetailsThe body also has warning.

Opt-outs, blocklisted numbers and known landlines are refused up front, uncharged.

Errors

HTTPCodeWhen
400no codeMissing to or body.
400invalid_fieldWrong type (field names it).
400invalid_fromNot one of your numbers.
400no_sender_numberNo number to send from.
DetailsAlso no_country_sender: none of your numbers can text the recipient's country.
400MESSAGE_TOO_LONGOver 25 segments (1,600 characters with an image).
400CONTENT_BLOCKEDBlocked content; a compliance fee may apply.
402insufficient_balanceBalance too low.
DetailsAlso CARD_REQUIRED, SANDBOX_LIMIT, and MONTHLY_CAP_REACHED, BILLING_PAST_DUE or FRAUD_HOLD (these three in code).
403opt_out_lockedContact opted out.
Detailsreason says how: STOP, marked opted out, or Do Not Disturb for texts. Also tcpa_block, global_blocklist, number_not_assigned.
403api_consent_attested_requiredconsent_attested missing, once enforced.
409QUIET_HOURS_BLOCKQuiet hours (in code, with next_allowed_at).
409RAMP_CAP_REACHEDWarm-up cap used up (in code).
409duplicate_sendSame text and number within 60 seconds, no key.
409number_activatingNumber still activating. Wait Retry-After.
422INVALID_DESTINATIONNot a valid phone number.
429rate_limitedOver carrier pace. Wait Retry-After, same key.
429daily_capDaily cap hit; resets midnight Pacific.
DetailsAlso number_daily_cap. No Retry-After.
500send_failedNot sent. New key, in a minute.
DetailsAlso hold_not_returned.
500INTERNAL_ERROR_OUTCOME_UNKNOWNOutcome unknown, like a 202 (in code).
503provider_busyCarrier busy. Wait Retry-After, new key.
503gate_check_failedA check couldn't run; not sent.
DetailsAlso carrier_capacity_busy, contact_unavailable, consent_check_failed, sending_paused, permission_check_unavailable, ADMISSION_SHED.
504REQUEST_TIMEOUT_OUTCOME_UNKNOWNOver 30 seconds, may still send. Same key, after a minute.

#Send to many

POST/v1/messages/bulk

Sends one text to up to 100 recipients, with a result for each.

  • Idempotent Yes, with Idempotency-Key
  • Rate limit About 120 requests/min, 140 bulk calls/min per account
  • Cost Each recipient billed as one send
Note. Bulk is synchronous: 5 recipients at a time, returning when all are done.
DetailsSet a client timeout of at least 60 seconds. After a timeout, retry with the same Idempotency-Key.

Headers

Idempotency-KeystringRecommended
One key for the whole batch.

Body parameters

tostring[]Required
Up to 100 recipients, E.164 recommended.
DetailsEntries that aren't non-empty strings are skipped and not counted in total. Duplicate numbers aren't merged: the repeat is refused as duplicate_send.
bodystringRequired
The text for every recipient. message is an alias.
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 either message_id, segments and sent_body (the exact text sent, see Send a message), or error and error_code.
Detailsstatus is 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

HTTPCodeWhen
400invalid_requestto isn't a list, or there's no body.
400too_many_recipientsMore than 100 recipients.
400invalid_fromNot one of your numbers.
402insufficient_creditsBalance can't cover the batch.
409idempotency_in_progressSame key while the batch still runs.
429throughput_exceededOver your carrier throughput.
429rate_limitedOver the per-account limits.

#Get a message

GET/v1/messages/:id

The delivery status of one message you sent or received.

  • Cost Free

Path parameters

idstringRequired
The message_id a send returned.

Returns

data.statusstring
queued, delivered, failed, undelivered or unknown for outbound; received for inbound. unknown means carrier acceptance could not be confirmed and does not prove the text was unsent. failed isn't always final (below).
data.error_reasonstring | null
A stable value to branch on, null unless status is failed or undelivered.
DetailsSpecific 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) and carrier_filtered_content (spam filter). General reasons when the carrier gives no cause: carrier_rejected, undeliverable, expired, invalid_destination, temporary_failure, rejected and unknown. 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.
DetailsIts wording can change. null when error_reason is.
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_body is the exact text sent and segments are 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.

  • expired and undeliverable (no more specific carrier reason): resent, usually once, about 30 minutes later.
    DetailsReady 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 a carrier_rejected where the carrier refused your number: resent once, within 3 hours of sending.
    DetailsThe 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.
    DetailsGET /v1/messages lists the message under the same public id. Its status goes from failed back to queued, then to delivered or failed.
  • Webhooks: message_failed, then the resend's outcome under the same public message_id. Match that id to track the message across attempts.
    DetailsThe resend's outcome is message_delivered or message_failed.

When to retry:

error_reasonRetry?
expired, undeliverable, rejectedNo. Ready resends these (above).
temporary_failureYes, later, with a new key. Ready doesn't resend it.
recipient_out_of_creditYes, 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_destinationNo. Check the number with the contact.
carrier_filtered_contentReword the text first.
sender_network_not_provisionedNo. Contact support.

Errors

HTTPCodeWhen
404message_not_foundNo such message on your account (or it's another account's).

#List messages

GET/v1/messages

Your sent and received messages, newest first. Pages with a cursor.

  • Rate limit About 120 requests/min per account
  • Cost Free

Query parameters

directionenumOptional
inbound or outbound.
toE.164 stringOptional
Only messages to this number.
fromE.164 stringOptional
Only messages from this number.
statusstringOptional
queued, delivered, failed, undelivered, unknown or received.
limitintegerOptionalDefault 50Max 200
Page size.
before_idintegerOptional
The next_before_id from the previous page (not a message id).
Detailsoffset isn't supported and is ignored.

Returns

data[]object[]
Messages.
Detailsid (the same value as message_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_body is the exact text sent and segments are 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.
DetailsIt's true on an exactly full page.
next_before_idinteger | null
Pass as before_id for the next page; null on 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_reasonWhat the carrier reported
recipient_destination_rejectedThe destination was rejected. The report does not establish that the number is disconnected.
recipient_out_of_creditThe recipient’s prepaid phone plan ran out of credit.
recipient_network_unknownThe carrier could not identify the recipient’s network. This does not establish that the number has no carrier.
carrier_delivery_failedDelivery failed without a specific cause.
recipient_unreachableThe 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

HTTPCodeWhen
429rate_limitedOver the per-account limit.