Sending SMS
This guide covers the single-send endpoint, POST /v1/sms/messages. You build one JSON payload with a recipient, a body, and a category; Bird returns 202 Accepted with a message ID and delivers asynchronously. Each request sends one message to one recipient — to send many at once, use batch sending.
A minimal send
The smallest valid free-text payload is a to recipient, a text body, and a category.
Codebeispiel
curl -X POST https://us1.platform.bird.com/v1/sms/messages \
-H "Authorization: Bearer bk_us1_..." \
-H "Content-Type: application/json" \
-d '{
"to": "+15551234567",
"text": "Your Bird verification code is 481920.",
"category": "authentication"
}'Use your regional host (https://us1.platform.bird.com or https://eu1.platform.bird.com) with a matching bk_{region}_... key. Omitting from lets Bird auto-select a sender that's valid for the destination country; set it explicitly to send from a specific number, sender ID, or short code (see Sender).
Building up the payload
Recipient
to is a single recipient in E.164 format — a leading +, country code, and subscriber number, e.g. +15551234567. One message goes to one recipient; there is no cc/bcc and no recipient array. To reach many people, send a batch.
Sender
from is the sender the recipient sees. It can be one of three shapes, and which ones are allowed depends on the destination country and your account's registered senders:
- A phone number in E.164 (a long code or toll-free number you've provisioned).
- An alphanumeric sender ID — up to 11 characters (e.g. Bird). Supported in many countries but not all; where it isn't, delivery falls back or fails per local rules. Alphanumeric senders are one-way — recipients can't reply.
- A short code — a 5–6 digit number.
Omit from and Bird selects an eligible sender for the destination automatically.
Body and category
text is the message body, at least one character. It is billed and delivered in segments; a send is capped at 12 segments (about 1,836 GSM-7 characters, or 804 if the body uses the extended UCS-2 encoding). A body over the cap is rejected with a 422 rather than truncated.
category is required on a free-text send and classifies the message: transactional, marketing, authentication, or service. The category drives opt-out (STOP) handling, quiet-hours enforcement, and per-country compliance — so a one-time passcode (authentication) and a promotion (marketing) are treated differently by the rules that govern when and whether Bird may deliver them. Choose the category that matches the message's purpose.
Tags and metadata
Both attach your own data to a send, but they serve different jobs:
- tags are structured {name, value} pairs (max 20 per send; name 1–32 characters, value 1–64, ASCII [A-Za-z0-9_-] only, case-sensitive). They are first-class filter dimensions — filter the message log by tag and slice metrics by tag. Use them for low-cardinality labels like campaign, template, or experiment_variant.
- metadata is an arbitrary JSON object (max 2 KB serialized). It is stored, returned on API reads, and echoed on every webhook event — but it is not a filter dimension. Use it for round-trip context: internal IDs, foreign keys, anything you want handed back with each event.
Codebeispiel
{
"tags": [{ "name": "campaign", "value": "spring-2026" }],
"metadata": { "user_id": "usr_12345", "order_id": "ord_98765" }
}Field reference
| Field | Type | Required | Limits / notes |
|---|---|---|---|
| to | string (E.164) | yes | One recipient per message |
| from | string | no | E.164 number, alphanumeric sender ID (≤ 11 chars), or short code; auto-selected if omitted |
| text | string | yes* | At least 1 character; capped at 12 segments |
| category | string | yes* | transactional, marketing, authentication, or service |
| tags | {name, value}[] | no | Max 20; name 1–32 chars, value 1–64 chars; [A-Za-z0-9_-] only |
| metadata | object | no | Arbitrary JSON, max 2 KB serialized |
* A free-text send requires text and category. Sending from a pre-registered template is a separate path that supplies both from the template.
Segments and encoding
SMS carries a fixed number of characters per segment, and the limit depends on the encoding Bird picks for your body:
- GSM-7 — the default 7-bit alphabet (Latin letters, digits, common punctuation). A single-segment message fits 160 characters; longer messages are split at 153 characters per segment (the split reserves a few characters for reassembly headers).
- UCS-2 — used automatically when the body contains any character outside GSM-7, such as an emoji, a CJK character, or some accented letters. The limits drop to 70 characters single-segment and 67 per segment when split.
One out-of-range character switches the whole message to UCS-2 and more than halves its capacity, so a stray "smart quote" or emoji can turn a one-segment message into two. Each message read reports the resolved segments: the billable count, the encoding (GSM_7BIT or UCS2), and the character count. Segments are the unit you're billed on — see cost.
Batch sending
POST /v1/sms/batches sends up to 100 independent messages in one request. The body is an array of the same message objects described above:
Codebeispiel
curl -X POST https://us1.platform.bird.com/v1/sms/batches \
-H "Authorization: Bearer bk_us1_..." \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "to": "+15551234567", "text": "Order shipped.", "category": "transactional" },
{ "to": "+15557654321", "text": "Order shipped.", "category": "transactional" }
]
}'Validation is all-or-nothing: if any message in the batch is invalid, the whole request is rejected with a 422 and nothing is sent, so a batch never partially applies. On success the response returns each accepted message plus a summary with the accepted_count. Each message in the batch is independent from there — one recipient's failure never affects the others.
The async model: what 202 means
A successful send returns 202 Accepted with a message ID and status: accepted. The 202 is returned only after the send is durably accepted — it is never accepted and then silently dropped. Hard failures you can fix (an invalid recipient, a body over the segment cap, a missing category) fail immediately with a 422; a send that would exceed your workspace balance fails with a 402. Actual delivery happens asynchronously: the message moves to sent when Bird hands it to the carrier, then to a terminal status (delivered, undelivered, failed, or expired) when the receipt arrives, reported through events and webhooks and the read endpoints.
Two consequences of the async design worth knowing:
- Cost is priced after acceptance. The cost on a message is null at accept time and is populated once Bird prices the send during processing. Read the message back (or wait for the delivery event) to see the final segment count and charge.
- Reads can briefly trail the 202. There is a short window between the 202 and the message becoming visible on the read endpoints; a 404 immediately after a send resolves itself within moments.
Reserved fields
The following fields are part of the request vocabulary but are not yet available. Including any of them returns 422 unsupported_feature — they are reserved now so SDKs and integrations converge on one name before launch:
scheduled_at, validity_period, media_urls, messaging_profile_id, broadcast_id, campaign_id, audience_id, contact_id, topic_id, max_price_per_segment, personalization, track_clicks
Don't build around these yet.
Retrying safely
Send the Idempotency-Key header with a unique value per logical send, and retries become safe: if your first request succeeded but you never saw the response (timeout, dropped connection), replaying the same request with the same key returns the original result instead of sending a duplicate message. See idempotency for key format and retention.
Cost and billing
Outbound SMS is billed per segment. The final charge depends on the destination country and carrier — some routes add a surcharge (for example, US 10DLC carrier fees). Each single-message read includes a cost breakdown: the per_segment price, the billed segment count, the destination country_code, and any carrier_surcharge. Because pricing happens during processing, the cost is null until the message is priced. Aggregate spend and segment counts across a range are on the Metrics page.
Next steps
- SMS log — find a message, follow its lifecycle, and read its segments and cost
- Events — the per-message event stream behind the log and webhooks
- Tracking & metrics — delivery rate, failure rate, segments, and spend
- Idempotency — safe retries with the Idempotency-Key header