> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ryzeapi.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Enviar botones

> Envía un mensaje con hasta 3 botones interactivos (REPLY, URL, CALL o COPY) y un encabezado de media opcional

**Auth:** `TokenAccount` o `TokenInstance` • **Rate-limit:** `Global` (100/min) • **Idempotente:** no

## Descripción

Envía un mensaje con **hasta 3 botones interactivos** (límite de WhatsApp). Los botones aceptan cuatro tipos: `REPLY` (default, retorna el ID al hacer clic), `URL` (abre un enlace), `CALL` (marca un número) y `COPY` (copia un código). El encabezado puede ser texto (`headerText`) **o** media (`mediaUrl` + `mediaType`). Si se envía `mediaUrl`, `mediaType` es requerido y debe estar en **mayúsculas**: `IMAGE`, `VIDEO` o `DOCUMENT`. Cuando hay media presente, `headerText` se ignora (la media reemplaza al título).

## Ejemplos

### 3 botones REPLY

El botón `REPLY` se usa para **responder a un mensaje específico dentro de la conversación**: cuando el cliente lo toca, WhatsApp envía una respuesta citando el mensaje original con el **texto** del botón (`displayText`), eso es lo que aparece en el chat. En el **webhook/websocket**, lo que llega a tu aplicación es el `id` del botón clicado, permitiéndote identificar la opción sin depender del texto mostrado.

<Warning>
  Cuando `REPLY` se **mezcla** con otros tipos (`URL`, `CALL`, `COPY`) en el mismo mensaje, el botón `REPLY` **no aparece en WhatsApp Web/Desktop**, solo es visible en la app del smartphone. Lo ideal es usar **solo botones `REPLY`** o **solo combinaciones de `URL`/`CALL`/`COPY`**, sin mezclar.
</Warning>

Caso clásico de un menú rápido. Cada botón retorna su `id` en el webhook al hacer clic.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://ryzeapi.cloud/api/message/button/$Instance_Name" \
    -H "token: $Token_Instance" \
    -H "Content-Type: application/json" \
    -d '{
      "number":      "5511999999999",
      "headerText":  "RyzeAPI Support",
      "contentText": "How can we help you today?",
      "footerText":  "Available 24/7",
      "buttons": [
        { "id": "menu_sales",   "displayText": "Talk to sales",    "type": "REPLY" },
        { "id": "menu_support", "displayText": "Technical support", "type": "REPLY" },
        { "id": "menu_billing", "displayText": "Billing",          "type": "REPLY" }
      ]
    }'
  ```

  ```javascript JavaScript theme={null}
  await fetch(`https://ryzeapi.cloud/api/message/button/${process.env.Instance_Name}`, {
    method: "POST",
    headers: {
      "token":        process.env.Token_Instance,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      number:      "5511999999999",
      headerText:  "RyzeAPI Support",
      contentText: "How can we help you today?",
      footerText:  "Available 24/7",
      buttons: [
        { id: "menu_sales",   displayText: "Talk to sales",    type: "REPLY" },
        { id: "menu_support", displayText: "Technical support", type: "REPLY" },
        { id: "menu_billing", displayText: "Billing",          type: "REPLY" }
      ]
    })
  });
  ```

  ```python Python theme={null}
  import os, requests

  requests.post(
      f"https://ryzeapi.cloud/api/message/button/{os.environ['Instance_Name']}",
      headers={
          "token":        os.environ["Token_Instance"],
          "Content-Type": "application/json"
      },
      json={
          "number":      "5511999999999",
          "headerText":  "RyzeAPI Support",
          "contentText": "How can we help you today?",
          "footerText":  "Available 24/7",
          "buttons": [
              {"id": "menu_sales",   "displayText": "Talk to sales",    "type": "REPLY"},
              {"id": "menu_support", "displayText": "Technical support", "type": "REPLY"},
              {"id": "menu_billing", "displayText": "Billing",          "type": "REPLY"}
          ]
      }
  )
  ```

  ```go Go theme={null}
  package main

  import (
      "net/http"
      "os"
      "strings"
  )

  func main() {
      body := strings.NewReader(`{
          "number":      "5511999999999",
          "headerText":  "RyzeAPI Support",
          "contentText": "How can we help you today?",
          "footerText":  "Available 24/7",
          "buttons": [
              { "id": "menu_sales",   "displayText": "Talk to sales",    "type": "REPLY" },
              { "id": "menu_support", "displayText": "Technical support", "type": "REPLY" },
              { "id": "menu_billing", "displayText": "Billing",          "type": "REPLY" }
          ]
      }`)
      req, _ := http.NewRequest("POST", "https://ryzeapi.cloud/api/message/button/"+os.Getenv("Instance_Name"), body)
      req.Header.Set("token", os.Getenv("Token_Instance"))
      req.Header.Set("Content-Type", "application/json")
      http.DefaultClient.Do(req)
  }
  ```
</CodeGroup>

### Con encabezado de imagen

Cuando hay `mediaUrl` presente, `mediaType` es requerido y debe estar en **mayúsculas** (`IMAGE`, `VIDEO`, `DOCUMENT`). `headerText` se ignora en ese caso.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://ryzeapi.cloud/api/message/button/$Instance_Name" \
    -H "token: $Token_Instance" \
    -H "Content-Type: application/json" \
    -d '{
      "number":      "5511999999999",
      "contentText": "Flash sale: 30% OFF on annual subscriptions.",
      "footerText":  "Valid until 11:59pm today",
      "mediaUrl":    "https://example.com/img/promo.jpg",
      "mediaType":   "IMAGE",
      "buttons": [
        { "id": "promo_subscribe", "displayText": "Subscribe now", "type": "REPLY" },
        { "id": "promo_details",   "displayText": "See details",   "type": "REPLY" }
      ]
    }'
  ```

  ```javascript JavaScript theme={null}
  await fetch(`https://ryzeapi.cloud/api/message/button/${process.env.Instance_Name}`, {
    method: "POST",
    headers: {
      "token":        process.env.Token_Instance,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      number:      "5511999999999",
      contentText: "Flash sale: 30% OFF on annual subscriptions.",
      footerText:  "Valid until 11:59pm today",
      mediaUrl:    "https://example.com/img/promo.jpg",
      mediaType:   "IMAGE",
      buttons: [
        { id: "promo_subscribe", displayText: "Subscribe now", type: "REPLY" },
        { id: "promo_details",   displayText: "See details",   type: "REPLY" }
      ]
    })
  });
  ```

  ```python Python theme={null}
  import os, requests

  requests.post(
      f"https://ryzeapi.cloud/api/message/button/{os.environ['Instance_Name']}",
      headers={
          "token":        os.environ["Token_Instance"],
          "Content-Type": "application/json"
      },
      json={
          "number":      "5511999999999",
          "contentText": "Flash sale: 30% OFF on annual subscriptions.",
          "footerText":  "Valid until 11:59pm today",
          "mediaUrl":    "https://example.com/img/promo.jpg",
          "mediaType":   "IMAGE",
          "buttons": [
              {"id": "promo_subscribe", "displayText": "Subscribe now", "type": "REPLY"},
              {"id": "promo_details",   "displayText": "See details",   "type": "REPLY"}
          ]
      }
  )
  ```

  ```go Go theme={null}
  package main

  import (
      "net/http"
      "os"
      "strings"
  )

  func main() {
      body := strings.NewReader(`{
          "number":      "5511999999999",
          "contentText": "Flash sale: 30% OFF on annual subscriptions.",
          "footerText":  "Valid until 11:59pm today",
          "mediaUrl":    "https://example.com/img/promo.jpg",
          "mediaType":   "IMAGE",
          "buttons": [
              { "id": "promo_subscribe", "displayText": "Subscribe now", "type": "REPLY" },
              { "id": "promo_details",   "displayText": "See details",   "type": "REPLY" }
          ]
      }`)
      req, _ := http.NewRequest("POST", "https://ryzeapi.cloud/api/message/button/"+os.Getenv("Instance_Name"), body)
      req.Header.Set("token", os.Getenv("Token_Instance"))
      req.Header.Set("Content-Type", "application/json")
      http.DefaultClient.Do(req)
  }
  ```
</CodeGroup>

### Mezcla de URL + CALL + COPY

Combina tres tipos diferentes de botones en un único mensaje. El `id` transporta valores semánticos distintos por tipo.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://ryzeapi.cloud/api/message/button/$Instance_Name" \
    -H "token: $Token_Instance" \
    -H "Content-Type: application/json" \
    -d '{
      "number":      "5511999999999",
      "headerText":  "Order #12345 confirmed",
      "contentText": "Choose how you want to proceed:",
      "footerText":  "Coupon valid for 24h",
      "buttons": [
        { "id": "https://store.example.com/order/12345", "displayText": "Track order",   "type": "URL"  },
        { "id": "+551130000000",                          "displayText": "Talk to support", "type": "CALL" },
        { "id": "RYZE10",                                 "displayText": "Copy coupon",   "type": "COPY" }
      ]
    }'
  ```

  ```javascript JavaScript theme={null}
  await fetch(`https://ryzeapi.cloud/api/message/button/${process.env.Instance_Name}`, {
    method: "POST",
    headers: {
      "token":        process.env.Token_Instance,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      number:      "5511999999999",
      headerText:  "Order #12345 confirmed",
      contentText: "Choose how you want to proceed:",
      footerText:  "Coupon valid for 24h",
      buttons: [
        { id: "https://store.example.com/order/12345", displayText: "Track order",     type: "URL"  },
        { id: "+551130000000",                          displayText: "Talk to support", type: "CALL" },
        { id: "RYZE10",                                 displayText: "Copy coupon",    type: "COPY" }
      ]
    })
  });
  ```

  ```python Python theme={null}
  import os, requests

  requests.post(
      f"https://ryzeapi.cloud/api/message/button/{os.environ['Instance_Name']}",
      headers={
          "token":        os.environ["Token_Instance"],
          "Content-Type": "application/json"
      },
      json={
          "number":      "5511999999999",
          "headerText":  "Order #12345 confirmed",
          "contentText": "Choose how you want to proceed:",
          "footerText":  "Coupon valid for 24h",
          "buttons": [
              {"id": "https://store.example.com/order/12345", "displayText": "Track order",     "type": "URL"},
              {"id": "+551130000000",                          "displayText": "Talk to support", "type": "CALL"},
              {"id": "RYZE10",                                 "displayText": "Copy coupon",    "type": "COPY"}
          ]
      }
  )
  ```

  ```go Go theme={null}
  package main

  import (
      "net/http"
      "os"
      "strings"
  )

  func main() {
      body := strings.NewReader(`{
          "number":      "5511999999999",
          "headerText":  "Order #12345 confirmed",
          "contentText": "Choose how you want to proceed:",
          "footerText":  "Coupon valid for 24h",
          "buttons": [
              { "id": "https://store.example.com/order/12345", "displayText": "Track order",     "type": "URL"  },
              { "id": "+551130000000",                          "displayText": "Talk to support", "type": "CALL" },
              { "id": "RYZE10",                                 "displayText": "Copy coupon",    "type": "COPY" }
          ]
      }`)
      req, _ := http.NewRequest("POST", "https://ryzeapi.cloud/api/message/button/"+os.Getenv("Instance_Name"), body)
      req.Header.Set("token", os.Getenv("Token_Instance"))
      req.Header.Set("Content-Type", "application/json")
      http.DefaultClient.Do(req)
  }
  ```
</CodeGroup>

## Respuesta exitosa

El `content` retorna el `contentText` que enviaste, y `messageType` es fijo en `buttons`. Las definiciones de los botones en sí no regresan en la respuesta, guarda el `messageId` para correlacionar los clics que llegan vía webhook.

```json 200 OK theme={null}
{
  "success": true,
  "message": "Buttons message sent successfully",
  "status":  "sent",
  "data": {
    "messageId":   "3EB08FCF27E532F1F5F5",
    "direction":   "sent",
    "messageType": "buttons",
    "content":     "How can we help you today?",
    "source":      "api",
    "timestamp":   "2026-04-30T14:30:00Z",
    "chat": {
      "jid":     "5511999999999@s.whatsapp.net",
      "isGroup": false
    },
    "sender": {
      "jid":      "5511777777777@s.whatsapp.net",
      "instance": "minha-instancia"
    }
  }
}
```

<Note>
  Cuando el usuario toca un botón `REPLY`, la respuesta llega como un mensaje de texto que contiene el `id` del botón clicado, captúralo vía webhook para encadenar el flujo.
</Note>

## Parámetros de ruta

<ParamField path="instance" type="string" required>
  Nombre de la instancia (p. ej., `$Instance_Name`).
</ParamField>

## Cabeceras

<ParamField header="token" type="string" required>
  `TokenAccount` o `TokenInstance`.
</ParamField>

<ParamField header="Content-Type" type="string" required>
  `application/json`
</ParamField>

## Cuerpo de la solicitud

<ParamField body="number" type="string" required>
  Destino: teléfono (`5511999999999`) o JID (`@s.whatsapp.net`, `@lid`, `@g.us`, `@newsletter`).
</ParamField>

<ParamField body="contentText" type="string" required>
  Texto principal del cuerpo del mensaje (entre el encabezado y los botones).
</ParamField>

<ParamField body="buttons" type="ButtonOption[]" required>
  Lista de botones. **Mínimo 1, máximo 3** (límite de WhatsApp). Cada botón tiene:

  * `id` (string, **requerido**), la semántica varía según `type`: `REPLY` retorna este ID; `URL` abre esta URL; `CALL` marca este número; `COPY` copia este código.
  * `displayText` (string, **requerido**), texto visible en el botón.
  * `type` (string), `REPLY` (default), `URL`, `CALL` o `COPY`. Cualquier otro valor retorna `400 Button N: Type must be one of: REPLY, URL, CALL, COPY`.
</ParamField>

<ParamField body="headerText" type="string">
  Título mostrado en la parte superior del mensaje. **Ignorado si se proporciona `mediaUrl`** (la media reemplaza al encabezado).
</ParamField>

<ParamField body="footerText" type="string">
  Texto opcional mostrado debajo de los botones.
</ParamField>

<ParamField body="mediaUrl" type="string">
  URL de un archivo de media (imagen, video o documento) para usar como encabezado. Cuando se envía, `mediaType` se vuelve requerido.
</ParamField>

<ParamField body="mediaType" type="string">
  Tipo de la media en `mediaUrl`. **Requerido si `mediaUrl` está presente**. Acepta solo `IMAGE`, `VIDEO` o `DOCUMENT` (mayúsculas). Cualquier otro valor retorna `400 MediaType must be one of: IMAGE, VIDEO, DOCUMENT`.
</ParamField>

<ParamField body="delay" type="int" default="0">
  Tiempo en **segundos** a esperar antes de enviar. Durante el intervalo, el servidor envía el indicador "escribiendo..." y dispara "pausado" antes del envío real.
</ParamField>

<ParamField body="replyTo" type="string">
  ID del mensaje a citar (respuesta). El mensaje original debe pertenecer a la misma instancia y haber sido guardado en la base de datos.
</ParamField>

<ParamField body="replyPrivate" type="boolean" default="false">
  Cuando es `true` **y** `replyTo` apunta a un mensaje originado en un grupo, el mensaje se redirige al chat **privado** del autor original (manteniendo la cita).
</ParamField>

<ParamField body="source" type="string" default="api">
  Identificador de origen para trazabilidad (p. ej., `crm`, `bot-suporte`, `n8n`). Guardado en el registro del mensaje y propagado a los webhooks.
</ParamField>

## Notas

<Note>
  * **`delay` es en segundos** (no milisegundos).
  * WhatsApp acepta **hasta 3 botones** por mensaje. Cuatro o más retornan `400 Maximum of 3 buttons allowed`.
  * Si se envía `mediaUrl`, `headerText` se ignora silenciosamente.
  * `mediaType` se normaliza a mayúsculas internamente, envía siempre `IMAGE`, `VIDEO` o `DOCUMENT`.
  * Para botones `URL`, asegúrate de que el enlace comience con `https://` para evitar ser bloqueado por el cliente.
  * Para botones `CALL`, usa el formato internacional (`+5511...`).
  * Los dispositivos antiguos pueden caer a texto y mostrar los botones como un mensaje regular.
</Note>

## Errores

| HTTP | Status interno    | Mensaje                                                     |
| ---- | ----------------- | ----------------------------------------------------------- |
| 400  | ,                 | `Instance name is required`                                 |
| 400  | ,                 | `Invalid request body: <detail>`                            |
| 400  | ,                 | `Number is required`                                        |
| 400  | ,                 | `ContentText is required`                                   |
| 400  | ,                 | `At least one button is required`                           |
| 400  | ,                 | `Maximum of 3 buttons allowed`                              |
| 400  | ,                 | `MediaType is required when MediaURL is provided`           |
| 400  | ,                 | `MediaType must be one of: IMAGE, VIDEO, DOCUMENT`          |
| 400  | ,                 | `Button N: ID is required`                                  |
| 400  | ,                 | `Button N: DisplayText is required`                         |
| 400  | ,                 | `Button N: Type must be one of: REPLY, URL, CALL, COPY`     |
| 400  | `invalid_number`  | `Invalid phone number format: <detail>`                     |
| 400  | `invalid_request` | (motivo de la solicitud inválida detectado por el servicio) |
| 404  | ,                 | `Instance not found`                                        |
| 500  | `send_failed`     | `Failed to send buttons message: <reason>`                  |
| 503  | `disconnected`    | `Instance is not connected to WhatsApp`                     |

Envoltorio de error:

```json theme={null}
{
  "success": false,
  "error": { "message": "Maximum of 3 buttons allowed" }
}
```
