Bulk send and message lookup
#Send to many
Sends one text to up to 100 recipients, with a result for each. Each recipient is billed as one send.
Bulk is synchronous: 5 recipients at a time, returning when all are done. Set a client timeout longer than 120 seconds. After a timeout, retry with the same
Idempotency-Key.Parameters
Idempotency-Keystring, headerRecommended- One key for the whole batch.
tostring[]Required- Up to 100 recipients, E.164 recommended.
More
Entries that aren’t non-empty strings are skipped and not counted intotal. Duplicate numbers aren’t merged: the repeat is refused asduplicate_send. Agrouptext is the exception: its duplicates are merged before it is counted. 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).
More parameters
fromE.164 string- One of your numbers.
media_urlstring- Image URL, as in Send a message.
confirm_empty_mergeboolean- Send
trueto send even when a merge field is blank for some recipients. Without it, those recipients getempty_merge_fieldand are not sent. groupboolean- Send one group text: everyone in
togets it in one shared thread.More
Needs 2 to 10 different numbers and afromnumber on an approved texting campaign. No merge fields, since everyone sees the same text. Each person is billed as one MMS. Someone who opted out or is in quiet hours is left out of the group and isn’t charged. Replies come into each person’s own conversation.
Returns
Returns 200 once the batch is processed, even if every recipient failed: check data.failed and each results[].sent. With a test key, nothing is sent and each result has a test-msg-… message_id and its segments.
data.totalinteger- Recipients processed.
data.sentinteger- How many were accepted.
data.failedinteger- Every recipient not sent and not held. That includes a recipient whose result has
error_codesend_outcome_unknown: that text may have gone out, so check List messages before you send it again. data.heldinteger- How many are held for quiet hours and will send on their own.
data.groupbooleantrueon agroupsend. Missing otherwise.data.results[]object[]- One per recipient:
to,status(the HTTP status a single send would have returned),sent, and eithermessage_id,segmentsandsent_body, orerroranderror_code. A held recipient hasqueue_id,queued_for,send_at,recipient_tzandwarninginstead.More
A refused recipient’serror_codeis what a single send returns (inerror_codeorcode), else"send_failed", or"send_error"when the send failed on our side before anything went out (safe to retry).no_routeandsend_outcome_unknownmean it may have gone out: check List messages before you send it again.send_dedupe_unavailablemeans nothing was sent and it is safe to retry. Readerrorbefore retrying. data.results[].heldbooleantruewhen the text is held for quiet hours.queue_idis the held text,recipient_tzthe time zone used, andwarningsays why it was held: it isn’t sent yet, goes out atsend_at, and must not be sent again. A held recipient has"sent": falseand counts inheld, notfailed.
Errors
| HTTP | Code | When |
|---|---|---|
| 400 | invalid_request | to isn’t a list with at least one number, or there’s no body. |
| 400 | too_many_recipients | More than 100 recipients. |
| 400 | invalid_from | Not one of your numbers. |
| 400 | invalid_field | from is a list or an object, not one number. |
| 400 | invalid_media_url | media_url isn’t one web address that starts with http:// or https://. Nothing is sent. |
| 400 | invalid_group_size | group with fewer than 2 or more than 10 different numbers. |
| 400 | group_from_required | group without from. |
| 400 | group_from_unregistered | from isn’t on an approved texting campaign. |
| 400 | group_merge_fields | A group text with merge fields. |
| 402 | insufficient_credits | Your balance can’t cover the whole batch, so nothing is sent. Has required_cents, balance_cents and recipients. Top up in Billing by at least the difference, then try again. |
| 402 | client_card_required | A sub-account that pays with its own card and has not added one yet. Add one in Billing. |
| 409 | idempotency_in_progress | Same key while the batch still runs. |
| 429 | throughput_exceeded | The batch is over your carrier throughput, so nothing is sent. Has retry_after_ms and cap_per_sec. |
| 429 | rate_limited | Over the per-account limits. |
#Get a message
One message your account sent or received, including texts your team members sent. Returns a Message object in data.
Parameters
idstring, pathRequired- The
message_ida send returned.
Errors
| HTTP | Code | When |
|---|---|---|
| 404 | message_not_found | No such message on your account (or it’s another account’s). |
| 429 | rate_limited | Over the per-account limit. |
| 503 | message_reference_ambiguous | The id matches more than one message. Contact support before you send it again. |
Older paths
These paths predate /v1. They keep working and we aren’t turning them off; new code should use the /v1 endpoints. With an API key, the two GETs answer with a Deprecation: true header and a Link header (rel="successor-version") to the /v1 docs; the body doesn’t change.
POST /sms/send: the same send as POST /v1/messages. It stays for existing callers; new code should usePOST /v1/messages. The text goes inmessage(bodyis an alias;messagewins if you send both), and it also takesfrom_phone_number_id, which overridesfrom_strategy. A missingtoanswers 400 witherror_code: "missing_field".GET /sms/logs: the dashboard’s raw message log (50 rows a page by default; itssearchcovers only your most recent 10,000 messages). Use GET /v1/messages.hasMorebecomeshas_more,offsetbecomesbefore_id(pass backnext_before_id), a row on/v1has the same value in bothidandmessage_id, andstatususes the public words (queued, notsentorpending).GET /phone-numbers: use GET /v1/numbers.