<Intro>

<EndpointHeader />

<Description>

Sends a WhatsApp message built from a message template to one recipient.
Name the template, optionally pick its language variant, and fill its
placeholders in `components`; Bird selects the sender number from the
template's category, so the request carries no sender field. Templates are
the only supported content type: a request without `template` is rejected
with a `422`. Browse your workspace's templates in the Bird dashboard.

The `202` response is the accepted message, echoing the resolved template
and language; it is not a delivery confirmation. Follow delivery with
[Get a WhatsApp message](/docs/api/reference/get-whats-app-message), the
per-message timeline from
[List events for a WhatsApp message](/docs/api/reference/list-whats-app-message-events),
or `whatsapp.*` webhook events.

A template slug or language the catalogue does not stock, parameter values
that do not match the template's declared placeholders, and a recipient
that is not a valid phone number each return a `422`.

</Description>

</Intro>

<Payload kind="request">

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

<Description>

The message recipient: a phone number in E.164 format (for example `+31612345678`), or the recipient's business-scoped user ID (for example `US.13491208655302741918`), which addresses a WhatsApp user whose phone number you do not have. A value that is neither returns a `422` `WhatsAppInvalidRecipient`. One-time-passcode templates require a phone number and return a `422` `WhatsAppRecipientNotSupportedForTemplate` when sent to a business-scoped user ID.

</Description>

</Field>

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

<Description>

The template to send. Bird selects the sender number from the template's category, so there is no sender field on this request. Templates are the only supported content type today: a request without one is rejected with a `422`.

</Description>

<FieldChildren kind="request">

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

<Description>

The template to send, by its id.

</Description>

</Field>

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

<Description>

The template to send, by its slug (for example `bird_otp`).

</Description>

</Field>

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

<Description>

Which of the template's languages to send, as a BCP-47 tag (for example `en` or `pt-BR`). Meta's underscore form (`pt_BR`) is accepted and normalized; the accepted message echoes the canonical BCP-47 form. May be omitted when the template has a single language; when it is stocked in several, omitting the language returns a `422` that names the available tags.

</Description>

</Field>

<Field name="components" type="array of object" prefix="template.">

<Description>

The values that fill the template's placeholders: one entry per content block that has placeholders, each carrying its `parameters`. A positional template takes its parameters in `{{n}}` order; a template with named parameters requires each parameter's `name` to match one the template declares. Either way, sending parameters that do not match what the template declares returns a `422` `WhatsAppTemplateParameterMismatch`.

</Description>

<FieldChildren kind="request">

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

<Description>

Which part of the template this fills in: `body` for the main text, `button` for a button's variable, `header` for the header. Bird manages header values itself, so a `header` entry supplied on a send is ignored.

</Description>

<Description>

Possible values (may grow over time): `header`, `body`, `button`

</Description>

</Field>

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

<Description>

The values that fill this part's placeholders. A positional template takes them in `{{n}}` placeholder order; a template with named parameters requires each parameter's `name` to match one the template declares, and order then carries no meaning.

</Description>

<FieldChildren kind="request">

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

<Description>

The kind of value this parameter carries. `text` is the only kind today.

</Description>

</Field>

<Field name="text" type="string" prefix="template.components.parameters." required>

<Description>

The value substituted into the placeholder, as a plain string.

</Description>

</Field>

<Field name="name" type="string" prefix="template.components.parameters.">

<Description>

Required when the template declares named parameters: the placeholder this value fills (for example `first_name`), matching exactly one of the names the template declares. Name every parameter in that case; order does not matter once names are supplied. Omit this field for a positional template, which takes its values in `{{n}}` order instead. Sending the wrong set of names, or leaving one out that the template requires, returns a `422` `WhatsAppTemplateParameterMismatch`.

</Description>

</Field>

</FieldChildren>

</Field>

</FieldChildren>

</Field>

</FieldChildren>

</Field>

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

<Description>

Structured `{name, value}` labels for filtering. Tags become first-class query dimensions: filter the list endpoint by tag name. 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 and returned on API reads. 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>

</Payload>

<Payload kind="response">

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

<Description>

ID of the message (`wam_`-prefixed), assigned when the send is accepted. Pass it as `message_id` to the get-message and list-events endpoints.

</Description>

</Field>

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

<Description>

Whether the message was sent by the business (`outbound`) or received from the contact (`inbound`).

</Description>

<Description>

Possible values: `outbound`, `inbound`

</Description>

</Field>

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

<Description>

Sender of the message. On outbound messages, the business number it was sent from; on inbound, the WhatsApp contact.

</Description>

<FieldChildren kind="response">

<Field name="phone_number" type="string" prefix="from.">

<Description>

Phone number in E.164 format, when known.

</Description>

</Field>

<Field name="bsuid" type="string" prefix="from.">

<Description>

Business-scoped user ID, Meta's identifier for the WhatsApp user. Present only on the WhatsApp-user side of the message.

</Description>

</Field>

</FieldChildren>

</Field>

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

<Description>

Recipient of the message. On outbound messages, the WhatsApp contact; on inbound, the business number.

</Description>

<FieldChildren kind="response">

<Field name="phone_number" type="string" prefix="to.">

<Description>

Phone number in E.164 format, when known.

</Description>

</Field>

<Field name="bsuid" type="string" prefix="to.">

<Description>

Business-scoped user ID, Meta's identifier for the WhatsApp user. Present only on the WhatsApp-user side of the message.

</Description>

</Field>

</FieldChildren>

</Field>

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

<Description>

The template the message was sent from. For authentication templates the filled-in values are not returned.

</Description>

<FieldChildren kind="response">

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

<Description>

The template's stable handle (for example `bird_otp`).

</Description>

</Field>

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

<Description>

Content classification applied to messages sent from this template.

</Description>

</Field>

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

<Description>

The canonical BCP-47 tag of the template variant that was sent.

</Description>

</Field>

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

<Description>

The values that filled the template's placeholders. Empty for an authentication template, whose content is never returned.

</Description>

<FieldChildren kind="response">

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

<Description>

Which part of the template this fills in: `body` for the main text, `button` for a button's variable, `header` for the header. Bird manages header values itself, so a `header` entry supplied on a send is ignored.

</Description>

<Description>

Possible values (may grow over time): `header`, `body`, `button`

</Description>

</Field>

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

<Description>

The values that fill this part's placeholders. A positional template takes them in `{{n}}` placeholder order; a template with named parameters requires each parameter's `name` to match one the template declares, and order then carries no meaning.

</Description>

<FieldChildren kind="response">

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

<Description>

The kind of value this parameter carries. `text` is the only kind today.

</Description>

</Field>

<Field name="text" type="string" prefix="template.components.parameters." required>

<Description>

The value substituted into the placeholder, as a plain string.

</Description>

</Field>

<Field name="name" type="string" prefix="template.components.parameters.">

<Description>

Required when the template declares named parameters: the placeholder this value fills (for example `first_name`), matching exactly one of the names the template declares. Name every parameter in that case; order does not matter once names are supplied. Omit this field for a positional template, which takes its values in `{{n}}` order instead. Sending the wrong set of names, or leaving one out that the template requires, returns a `422` `WhatsAppTemplateParameterMismatch`.

</Description>

</Field>

</FieldChildren>

</Field>

</FieldChildren>

</Field>

</FieldChildren>

</Field>

<Field name="status" type="object" required />

<Field name="last_error" type="nullable object">

<Description>

Failure detail for a message that did not reach the recipient. Present only when the message failed.

</Description>

<FieldChildren kind="response">

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

<Description>

Failure reason, uniform whether the failure happened internally or was reported by the WhatsApp network. `insufficient_balance`: the workspace could not afford the send. `price_not_found`: no price was configured for this destination/template combination. `internal_error`: an unexpected Bird-side failure. `undeliverable`: the recipient could not be reached (for example not on WhatsApp, or the number is invalid). `service_window_expired`: the 24-hour customer care window has closed and a free-form message cannot be sent; send a template instead. `rate_limited`: the send was throttled. `recipient_suppressed`: the recipient is on the workspace's suppression list; the message was rejected before sending. Open enum: new codes may be added over time, so treat any unrecognized value as a future code rather than an error.

</Description>

<Description>

Possible values (may grow over time): `insufficient_balance`, `price_not_found`, `internal_error`, `undeliverable`, `service_window_expired`, `rate_limited`, `recipient_suppressed`

</Description>

</Field>

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

<Description>

Human-readable explanation of the failure.

</Description>

</Field>

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

<Description>

Raw error code from the WhatsApp Cloud API, when available, for low-level debugging.

</Description>

</Field>

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

<Description>

When the failure occurred.

</Description>

</Field>

</FieldChildren>

</Field>

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

<Description>

When the message was accepted for delivery.

</Description>

</Field>

<Field name="sent_at" type="nullable string">

<Description>

When the message was handed to the WhatsApp network. Null until then.

</Description>

</Field>

<Field name="delivered_at" type="nullable string">

<Description>

When delivery was confirmed. Null until then.

</Description>

</Field>

<Field name="read_at" type="nullable string">

<Description>

When the message was read by the recipient. Null until then.

</Description>

</Field>

<Field name="cost" type="nullable object">

<Description>

Amount charged for this message, at full precision. Null until the message has been priced, and on messages that were rejected before pricing. The rate depends on the template category and the recipient's country.

</Description>

<FieldChildren kind="response">

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

<Description>

Decimal amount as a string, in major currency units.

</Description>

</Field>

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

<Description>

ISO 4217 currency code.

</Description>

</Field>

</FieldChildren>

</Field>

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

<Description>

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

</Description>

<FieldChildren kind="response">

<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 metadata stored on the message.

</Description>

</Field>

</Payload>