<Intro>

<EndpointHeader />

<Description>

Sends up to 100 independent SMS messages in one request. Each item is a
complete send request with its own recipient, content, id, status, and
cost. For a single message, use
[Send an SMS message](/docs/api/reference/create-sms-message) instead.

Acceptance is all-or-nothing: every item is validated before any is queued,
and one invalid item rejects the whole batch with a `422` (nothing is
sent). A batch from a workspace with no wallet balance fails with a `402`.
The `202` response lists the accepted messages in submission order; each
delivers asynchronously and is tracked individually, like a single send.

</Description>

</Intro>

<Payload kind="request" array>

<Field name="to" type="string">

<Description>

Recipient phone number in E.164 format (for example `+15551234567`). One recipient per message.

</Description>

</Field>

<Field name="from" type="string">

<Description>

Sender to send from: an E.164 number (`+15557654321`), an alphanumeric sender ID (1-11 letters, digits, or spaces, for example `MyBrand`), or a short code (5-6 digits). A numeric sender must be a number your workspace owns; an alphanumeric sender is accepted where the destination country permits one. Required on a free-text send: omitting it returns a `422` `SMSNoEligibleSender`. Not accepted alongside `template`, which selects its sender automatically.

</Description>

</Field>

<Field name="text" type="string" required>

<Description>

Free-text message body. Required unless `template` is supplied (the two are mutually exclusive). At least 1 character, up to a 12-segment cap (roughly 1836 GSM-7 or 804 UCS-2 characters). Bird does not truncate; a body exceeding 12 segments is rejected with a 422. The limit is on segment count, not characters, because GSM-7 and UCS-2 encodings differ in characters per segment.

</Description>

</Field>

<Field name="category" type="object">

<Description>

Content classification. Tells Bird and carriers why you're sending; per-country compliance rules (opt-out policy, quiet hours) key on it as they roll out. Required on a free-text send; omit it on a template send, where the category is derived from the template.

</Description>

</Field>

<Field name="validity_period" type="integer">

<Description>

Preview feature: how long, in seconds (60-172800), Bird keeps trying to deliver before the message transitions to `expired`. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`.

</Description>

</Field>

<Field name="tags" type="array of object">

<Description>

Structured `{name, value}` labels for filtering and analytics. Tags become first-class query dimensions: filter the list endpoint by tag name, slice analytics by tag, and surface in webhook payloads. Maximum 20 tags per send. Use tags for low-cardinality dimensions (`category`, `experiment_variant`). For arbitrary structured context you do not need as a filter dimension, use `metadata` instead.

</Description>

<FieldChildren kind="request">

<Field name="name" type="string" prefix="tags." required>

<Description>

Tag name. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 32 characters.

</Description>

</Field>

<Field name="value" type="string" prefix="tags." required>

<Description>

Tag value. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 64 characters.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="metadata" type="object">

<Description>

Arbitrary JSON object stored on the message, returned on API reads, and echoed in webhook payloads. Maximum 2 KB serialized. Use metadata for per-send context like internal IDs and foreign keys. For low-cardinality filterable labels, use `tags` instead.

</Description>

</Field>

<Field name="media_urls" type="array of string">

<Description>

Preview feature: multimedia (MMS) attachments. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`.

</Description>

</Field>

<Field name="messaging_profile_id" type="string">

<Description>

Preview feature: sender selection from a messaging profile pool. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`.

</Description>

</Field>

<Field name="scheduled_at" type="string">

<Description>

Preview feature: send-later scheduling. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`.

</Description>

</Field>

<Field name="template" type="object" required>

<Description>

Send using a stored template instead of free text. Mutually exclusive with `text`; the message category is derived from the template, so `from`, `category`, and `media_urls` are not accepted alongside it.

</Description>

<FieldChildren kind="request">

<Field name="id" type="string" prefix="template." required>

<Description>

The template to send, by its id.

</Description>

</Field>

<Field name="name" type="object" prefix="template." required>

<Description>

The template to send, by its name handle (for example `bird_otp_verification`). Browse the available templates and their variables with the templates endpoint.

</Description>

</Field>

<Field name="language" type="string" prefix="template.">

<Description>

Language tag (BCP 47, for example `fr` or `pt-BR`) selecting the localized body. Falls back to the closest available language, then English, when the exact tag is not stocked. Omit for English.

</Description>

</Field>

<Field name="parameters" type="object" prefix="template.">

<Description>

Values for the template's variables, keyed by variable name. The accepted keys and their formats are fixed per template (the template's `variables` on the templates endpoint). A missing required variable, an undeclared key, a value that does not match its variable's format, or a serialized payload over 16 KB each return a `422`.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="broadcast_id" type="string">

<Description>

Preview feature: broadcast correlation. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`.

</Description>

</Field>

<Field name="campaign_id" type="string">

<Description>

Preview feature: campaign correlation for analytics. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`.

</Description>

</Field>

<Field name="audience_id" type="string">

<Description>

Preview feature: audience-targeted sends. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`.

</Description>

</Field>

<Field name="contact_id" type="string">

<Description>

Preview feature: contact-targeted sends. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`.

</Description>

</Field>

<Field name="topic_id" type="string">

<Description>

Preview feature: topic-gated sends. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`.

</Description>

</Field>

<Field name="max_price_per_segment" type="number">

<Description>

Preview feature: per-segment price ceiling. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`.

</Description>

</Field>

<Field name="personalization" type="object">

<Description>

Preview feature: per-recipient substitution for batch sends. Currently unavailable; supplying this field returns `422 SMSUnsupportedFeature`.

</Description>

</Field>

<Field name="track_clicks" type="boolean">

<Description>

Preview feature: link click tracking. Defaults to `false`. Currently unavailable; setting this to `true` returns `422 SMSUnsupportedFeature`.

</Description>

</Field>

</Payload>

<Payload kind="response">

<Field name="data" type="array of object" required>

<Description>

One entry per message in the batch, in submission order.

</Description>

<FieldChildren kind="response">

<Field name="id" type="string" prefix="data." required>

<Description>

ID of the message (`sms_`-prefixed), assigned when the send is accepted. Pass it as `message_id` to the get-message endpoint.

</Description>

</Field>

<Field name="direction" type="string" prefix="data." required>

<Description>

Whether the message was sent from a Bird sender (`outbound`) or received from a subscriber (`inbound`).

</Description>

<Description>

Possible values: `outbound`, `inbound`

</Description>

</Field>

<Field name="status" type="object" prefix="data." required />

<Field name="to" type="string" prefix="data." required>

<Description>

Recipient phone number in E.164 format.

</Description>

</Field>

<Field name="from" type="string" prefix="data." required>

<Description>

Sender the message was sent from: an E.164 number, an alphanumeric sender ID, or a short code.

</Description>

</Field>

<Field name="text" type="string" prefix="data." required>

<Description>

The message body as sent. For a template send, this is the rendered text after parameter substitution.

</Description>

</Field>

<Field name="category" type="nullable string" prefix="data.">

<Description>

Content classification supplied on the send. Null for inbound messages.

</Description>

</Field>

<Field name="segments" type="object" prefix="data." required>

<Description>

Segment breakdown for the body.

</Description>

<FieldChildren kind="response">

<Field name="count" type="integer" prefix="data.segments." required>

<Description>

Number of segments the body is split into. Each segment is a billable unit.

</Description>

</Field>

<Field name="encoding" type="string" prefix="data.segments." required>

<Description>

Encoding used for the body. `GSM_7BIT` fits 160 characters in a single segment (153 per part when multi-segment); `UCS2` is used when the body contains any character outside the GSM 03.38 alphabet (emoji, CJK, some accented characters) and fits 70 characters in a single segment (67 per part when multi-segment).

</Description>

<Description>

Possible values: `GSM_7BIT`, `UCS2`

</Description>

</Field>

<Field name="characters" type="integer" prefix="data.segments." required>

<Description>

Character count of the body under the selected encoding.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="cost" type="nullable object" prefix="data.">

<Description>

Cost of the message. Null until the message has been priced.

</Description>

<FieldChildren kind="response">

<Field name="currency_code" type="string" prefix="data.cost.">

<Description>

ISO 4217 currency code for the cost amount. Omitted when the cost is not denominated in a currency (for example a zero-priced internal send).

</Description>

</Field>

<Field name="amount" type="string" prefix="data.cost." required>

<Description>

Total cost as a decimal string: the per-segment rate multiplied by the segment count, plus any surcharges.

</Description>

</Field>

<Field name="breakdown" type="object" prefix="data.cost.">

<Description>

Per-component cost breakdown. Returned on single-message reads; omitted from list rows.

</Description>

<FieldChildren kind="response">

<Field name="per_segment" type="string" prefix="data.cost.breakdown." required>

<Description>

Per-segment price as a decimal string.

</Description>

</Field>

<Field name="segments" type="integer" prefix="data.cost.breakdown." required>

<Description>

Number of billable segments.

</Description>

</Field>

<Field name="country_code" type="string" prefix="data.cost.breakdown." required>

<Description>

ISO 3166-1 alpha-2 destination country the price was resolved for.

</Description>

</Field>

<Field name="carrier_surcharge" type="string" prefix="data.cost.breakdown." required>

<Description>

Carrier surcharge component as a decimal string (for example US 10DLC fees). `0.0000` when none applies.

</Description>

</Field>

</FieldChildren>

</Field>

</FieldChildren>

</Field>

<Field name="tags" type="array of object" prefix="data.">

<Description>

Structured `{name, value}` filter labels applied to this message.

</Description>

<FieldChildren kind="response">

<Field name="name" type="string" prefix="data.tags." required>

<Description>

Tag name. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 32 characters.

</Description>

</Field>

<Field name="value" type="string" prefix="data.tags." required>

<Description>

Tag value. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 64 characters.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="metadata" type="object" prefix="data.">

<Description>

Arbitrary JSON metadata stored on the message and echoed in webhook payloads.

</Description>

</Field>

<Field name="validity_period" type="integer" prefix="data.">

<Description>

How long, in seconds, Bird keeps trying to deliver before the message transitions to `expired`.

</Description>

</Field>

<Field name="carrier" type="nullable string" prefix="data.">

<Description>

Carrier that handled the message, when known. Populated once a delivery receipt identifies it.

</Description>

</Field>

<Field name="mcc_mnc" type="nullable string" prefix="data.">

<Description>

Mobile country code and mobile network code of the carrier, when known.

</Description>

</Field>

<Field name="last_error" type="nullable object" prefix="data.">

<Description>

Failure detail on a terminally failed or rejected message. Present only when the message failed.

</Description>

<FieldChildren kind="response">

<Field name="code" type="string" prefix="data.last_error." required>

<Description>

Bird-stable failure reason. `invalid_destination`: the number is not assigned, ported out, or malformed. `unreachable`: handset off or out of coverage. `blocked_by_carrier`: the carrier filtered the message. `blocked_by_recipient`: the recipient device blocked the sender. `landline_unreachable`: the destination is a landline that does not accept SMS. `content_rejected`: the carrier rejected the content. `sender_unregistered`: the sender is not registered for the destination. `recipient_opted_out`: the recipient is on a suppression list. `provider_unavailable`: an upstream failure after retries. `insufficient_balance`: the workspace wallet had insufficient balance to send the message. `unknown`: an unmapped failure.

</Description>

<Description>

Possible values: `invalid_destination`, `unreachable`, `blocked_by_carrier`, `blocked_by_recipient`, `landline_unreachable`, `content_rejected`, `sender_unregistered`, `recipient_opted_out`, `provider_unavailable`, `insufficient_balance`, `unknown`

</Description>

</Field>

<Field name="description" type="string" prefix="data.last_error." required>

<Description>

Human-readable explanation of the failure.

</Description>

</Field>

<Field name="carrier_error_code" type="nullable string" prefix="data.last_error.">

<Description>

Raw carrier-supplied error code, when available, for low-level debugging.

</Description>

</Field>

<Field name="occurred_at" type="string" prefix="data.last_error." required>

<Description>

When the failure occurred.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="created_at" type="string" prefix="data." required>

<Description>

When the message was accepted (outbound) or received (inbound).

</Description>

</Field>

<Field name="sent_at" type="nullable string" prefix="data.">

<Description>

When the message was handed to the carrier. Null until then.

</Description>

</Field>

<Field name="delivered_at" type="nullable string" prefix="data.">

<Description>

When delivery was confirmed. Null until then.

</Description>

</Field>

</FieldChildren>

</Field>

<Field name="summary" type="object" required>

<Description>

Aggregate result for the batch.

</Description>

<FieldChildren kind="response">

<Field name="accepted_count" type="integer" prefix="summary." required>

<Description>

Number of messages accepted in the batch. Acceptance is all-or-nothing, so this equals the number of messages submitted.

</Description>

</Field>

</FieldChildren>

</Field>

</Payload>