---
title: "Get an SMS message"
method: GET
path: "/v1/sms/messages/{message_id}"
tags: ["sms-messages"]
---

# Get an SMS message

`GET /v1/sms/messages/{message_id}`

## Request

- Base URL: `https://us1.platform.bird.com`
- URL: `https://us1.platform.bird.com/v1/sms/messages/{message_id}`
- Auth: one of:
  - HTTP bearer
  - API key in cookie `bird_session`

Returns a single SMS message: its current delivery status, segment breakdown, cost, and failure detail when it failed. The `status` advances asynchronously as delivery progresses, and `cost` is null until the message has been priced, so poll this operation (or subscribe to `sms.*` webhook events) after a send to confirm delivery. To scan messages in bulk, use [List SMS messages](/docs/api/reference/list-sms-messages) instead.

## Path parameters

- `message_id` string, required

## Headers

- `X-Workspace-Id` string
- `X-Organization-Id` string

## Response `200`

SMS message with its current delivery status.

- SMSMessage
  - `id` string, required
  - `direction` 'outbound' | 'inbound', required: Whether the message was sent from a Bird sender (`outbound`) or received from a subscriber (`inbound`).
  - `status` 'scheduled' | 'accepted' | 'sent' | 'delivered' | 'undelivered' | 'failed' | 'rejected' | 'canceled' | 'expired' | 'received', required: Delivery status: - `accepted`: Accepted and awaiting carrier handoff. - `sent`: Handed to the carrier and awaiting a delivery receipt. - `delivered`: Confirmed as delivered. - `undelivered`: The carrier reported delivery as failed for a reason that may clear later, such as a handset out of coverage or a carrier at capacity. Final all the same: the message is not retried, so reaching the recipient means sending again. - `failed`: The carrier reported delivery as failed for a reason that will not clear, such as an unassigned number, a recipient who has opted out, or content the carrier refused. - `rejected`: Refused before carrier handoff. - `expired`: Reached its validity limit without a final receipt. - `received`: Received as an inbound message. `scheduled` and `canceled` are declared ahead of the send-later scheduling feature that produces them, so their arrival is not a breaking change. No message carries either status today.
  - `to` string, required: Where the message went. On an outbound message this is the recipient's phone number in E.164 format; on an inbound one it is your own number that received it.
  - `from` string, required: Where the message came from. On an outbound message this is the sender you sent it from: an E.164 number, an alphanumeric sender ID, or a short code. On an inbound message, this is the phone number that sent it to you.
  - `text` string: The message body. Every message carries body text, attachments, or both, so this is absent only on a received message that carried attachments and no text. For a template send, this is the rendered text after parameter substitution. When `category` is `authentication` (a message carrying a one-time code), this is `**REDACTED**`: the code still reaches the recipient, but the API does not retain it for later reads.
  - `category` 'transactional' | 'marketing' | 'authentication' | 'service': Content classification: why you are sending. Carriers see it, and where a destination country requires the sender to be registered, that registration is approved for a category: a send outside what it covers returns a `422` `SenderCategoryNotPermitted`. A registration approved for `marketing` covers all four values; one approved for `transactional`, `authentication`, or `service` covers those three and not `marketing`. Use `authentication` for a one-time passcode and `marketing` for a promotion; otherwise pick the value matching the message's purpose.
  - `requested_language` string, required: A language tag in BCP-47 form, for example `en` or `pt-BR`.
  - `resolved_language` string, required: A language tag in BCP-47 form, for example `en` or `pt-BR`.
  - `template_id` string, required
  - `template_version_id` string, required
  - `template_content_hash` string, required: A fingerprint of SMS template text, prefixed with its algorithm. Compare it within this API version to identify the exact source without transferring it.
  - `segments` SMSSegments, required: Segment breakdown for the message body. Segment count drives billing.
    - `count` integer, required: Number of segments the body is split into. Each segment is a billable unit.
    - `encoding` 'GSM_7BIT' | 'UCS2', required: Encoding used for the body. The `GSM_7BIT` encoding fits 160 septets (seven-bit units) in one segment, or 153 per part in a multi-segment message. The `UCS2` encoding applies when the body contains a character outside the GSM 03.38 alphabet, including emoji, CJK, and some accented characters. It fits 70 UTF-16 code units in one segment, or 67 per part. Neither limit counts characters, and both alphabets have characters that cost two units. Under `GSM_7BIT` there are ten such entries, and they are the whole set: `^`, `{`, `}`, `\`, `[`, `]`, `~`, `|`, `€`, and the form feed control. Eighty of those fill a single segment. Under `UCS2` an emoji outside the Basic Multilingual Plane is a surrogate pair costing two code units, so 35 of those fill a single segment.
    - `characters` integer, required: Character count of the body, counted in Unicode code points under either encoding. This is not the segment measure: a `GSM_7BIT` extended-table character counts once here but costs two septets, and a `UCS2` emoji outside the Basic Multilingual Plane counts once here but costs two of the segment's 70 code units.
  - `cost` MessageCost, nullable: What was charged for a message, split into the components that make it up. `null` until at least one component has been priced.
    - `amount` string, required: Total charged, as a decimal string: the sum of the components below. Net of tax, which applies to your wallet balance rather than to an individual charge.
    - `currency_code` string, required: ISO 4217 three-letter currency code.
    - `transaction_amount` string, nullable, required: What we charged to carry the message, as a decimal string. `null` when this component was not priced; `"0.00000"` when it priced at zero.
    - `passthrough_amount` string, nullable, required: Third-party fees we pass on, as a decimal string, such as US 10DLC carrier surcharges. `null` when this component was not priced; `"0.00000"` when it priced at zero.
  - `tags` Tag[]: Structured `{name, value}` filter labels applied to this message.
    - `name` string, required: Tag name. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 32 characters.
    - `value` string, required: Tag value. ASCII letters, digits, underscore, and hyphen only. Case-sensitive. Maximum 64 characters.
  - `metadata` object: Arbitrary JSON metadata stored on the message and echoed in webhook payloads.
  - `options` SMSMessageEffectiveOptions: The settings Bird applied to this message. Every option is reported, whether you set it on the send or took the default that was in force at the time.
    - `smart_encoding` boolean, required: Whether Bird replaced characters outside the GSM-7 alphabet in this message's body with their closest equivalent before sending it. When `true`, `text` is the body as sent and `segments` describes that body.
  - `validity_period` integer: Preview feature: how long, in seconds, the carrier may keep attempting delivery before the message is marked `expired`. Not returned yet.
  - `carrier` string: Carrier that handled the message. Absent until a delivery receipt identifies it, and on a received message the carrier reports it only where a carrier fee applies.
  - `mcc_mnc` string: Mobile country code and mobile network code of the carrier. Absent until the carrier is identified.
  - `last_error` SMSError, nullable: Failure detail for a message that could not be delivered or was rejected.
    - `code` string, required: Standardized failure reason: - `invalid_destination`: The number is unassigned, ported out, or malformed. - `unreachable`: The handset is off or outside coverage. - `blocked_by_carrier`: The carrier filtered the message. - `blocked_by_fraud_protection`: Bird fraud protection blocked suspected SMS pumping. - `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`: The provider remained unavailable after retries. - `insufficient_balance`: The workspace wallet could not fund the send. - `unknown`: The failure could not be classified. This is an open enum. Accept unrecognized values.
    - `description` string, required: The failure in words: the provider's reason text, or Bird's explanation for a fraud protection block or a message refused before submission. Free-form, so branch on `code` and show this to a human.
    - `carrier_error_code` string, nullable: Raw provider-supplied error code, finer-grained than the `code` that normalizes it. Not a Bird-defined value, so quote it to support when asking why a message failed. Null when the provider sent none, including any failure decided before one was reached.
    - `occurred_at` string, date-time, required: When the failure occurred.
  - `created_at` string, date-time, required: When the message was accepted (outbound) or received (inbound).
  - `sent_at` string, date-time, nullable: When the message was handed to the carrier. Null until then.
  - `delivered_at` string, date-time, nullable: When delivery was confirmed. Null until then.

## Other responses

- `400`: Bad request
- `401`: Authentication required
- `403`: Insufficient permissions
- `404`: Resource not found
- `422`: The request has invalid field values, violates a business rule, or carries a query parameter the endpoint does not declare. Field validation errors use `type: validation_error` and include the affected fields in `details`. Business-rule errors identify the failed rule in `type`.
- `429`: Rate limit exceeded
- `500`: Internal server error
- `503`: The service is temporarily unavailable. If `Retry-After` is present, wait for that delay before retrying; otherwise, retry with exponential backoff. Reuse the same idempotency key and request when retrying a mutation.

---

[API](https://skmtc.dev/messagebird/apis/bird-api.md) · [All operations](https://skmtc.dev/messagebird/apis/bird-api/llms.txt) · [OpenAPI document](https://skmtc.dev/messagebird/apis/bird-api/revisions/3e2a50466454?raw)
