> ## 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 Botões

> Envia mensagem com até 3 botões interativos (REPLY, URL, CALL ou COPY) e header opcional de mídia

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

## Descrição

Envia uma mensagem com **até 3 botões interativos** (limite do WhatsApp). Os botões aceitam quatro tipos: `REPLY` (padrão, retorna o ID quando clicado), `URL` (abre link), `CALL` (disca número) e `COPY` (copia código). O header pode ser texto (`headerText`) **ou** mídia (`mediaUrl` + `mediaType`). Se `mediaUrl` for enviado, `mediaType` é obrigatório e deve ser **maiúsculo**: `IMAGE`, `VIDEO` ou `DOCUMENT`. Quando há mídia, `headerText` é ignorado (a mídia substitui o título).

## Exemplos

### 3 botões REPLY

O botão `REPLY` serve para **responder uma mensagem específica dentro da conversa**: quando o cliente toca, o WhatsApp envia uma resposta citando a mensagem original com o **texto** do botão (`displayText`), é isso que aparece no chat. Já no **webhook/websocket**, o que chega para sua aplicação é o `id` do botão clicado, permitindo identificar a opção sem depender do texto exibido.

<Warning>
  Quando `REPLY` é **misturado** com outros tipos (`URL`, `CALL`, `COPY`) na mesma mensagem, o botão `REPLY` **não aparece no WhatsApp Web/Desktop**, só fica visível no app do smartphone. O ideal é usar **apenas botões `REPLY`** ou **apenas combinações de `URL`/`CALL`/`COPY`**, sem misturar.
</Warning>

Caso clássico de menu rápido. Cada botão retorna seu `id` no webhook quando clicado.

<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":  "Atendimento RyzeAPI",
      "contentText": "Como podemos ajudar você hoje?",
      "footerText":  "Disponível 24/7",
      "buttons": [
        { "id": "menu_sales",   "displayText": "Falar com vendas",   "type": "REPLY" },
        { "id": "menu_support", "displayText": "Suporte técnico",    "type": "REPLY" },
        { "id": "menu_billing", "displayText": "Financeiro",         "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:  "Atendimento RyzeAPI",
      contentText: "Como podemos ajudar você hoje?",
      footerText:  "Disponível 24/7",
      buttons: [
        { id: "menu_sales",   displayText: "Falar com vendas", type: "REPLY" },
        { id: "menu_support", displayText: "Suporte técnico",   type: "REPLY" },
        { id: "menu_billing", displayText: "Financeiro",        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":  "Atendimento RyzeAPI",
          "contentText": "Como podemos ajudar você hoje?",
          "footerText":  "Disponível 24/7",
          "buttons": [
              {"id": "menu_sales",   "displayText": "Falar com vendas", "type": "REPLY"},
              {"id": "menu_support", "displayText": "Suporte técnico",   "type": "REPLY"},
              {"id": "menu_billing", "displayText": "Financeiro",        "type": "REPLY"}
          ]
      }
  )
  ```

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

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

  func main() {
      body := strings.NewReader(`{
          "number":      "5511999999999",
          "headerText":  "Atendimento RyzeAPI",
          "contentText": "Como podemos ajudar você hoje?",
          "footerText":  "Disponível 24/7",
          "buttons": [
              { "id": "menu_sales",   "displayText": "Falar com vendas", "type": "REPLY" },
              { "id": "menu_support", "displayText": "Suporte técnico",   "type": "REPLY" },
              { "id": "menu_billing", "displayText": "Financeiro",        "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>

### Com header de imagem

Quando `mediaUrl` está presente, `mediaType` é obrigatório e deve ser **maiúsculo** (`IMAGE`, `VIDEO`, `DOCUMENT`). `headerText` é ignorado neste 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": "Promoção relâmpago: 30% OFF nas assinaturas anuais.",
      "footerText":  "Válido até 23h59 de hoje",
      "mediaUrl":    "https://exemplo.com/img/promo.jpg",
      "mediaType":   "IMAGE",
      "buttons": [
        { "id": "promo_subscribe", "displayText": "Assinar agora", "type": "REPLY" },
        { "id": "promo_details",   "displayText": "Ver detalhes",  "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: "Promoção relâmpago: 30% OFF nas assinaturas anuais.",
      footerText:  "Válido até 23h59 de hoje",
      mediaUrl:    "https://exemplo.com/img/promo.jpg",
      mediaType:   "IMAGE",
      buttons: [
        { id: "promo_subscribe", displayText: "Assinar agora", type: "REPLY" },
        { id: "promo_details",   displayText: "Ver detalhes",  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": "Promoção relâmpago: 30% OFF nas assinaturas anuais.",
          "footerText":  "Válido até 23h59 de hoje",
          "mediaUrl":    "https://exemplo.com/img/promo.jpg",
          "mediaType":   "IMAGE",
          "buttons": [
              {"id": "promo_subscribe", "displayText": "Assinar agora", "type": "REPLY"},
              {"id": "promo_details",   "displayText": "Ver detalhes",  "type": "REPLY"}
          ]
      }
  )
  ```

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

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

  func main() {
      body := strings.NewReader(`{
          "number":      "5511999999999",
          "contentText": "Promoção relâmpago: 30% OFF nas assinaturas anuais.",
          "footerText":  "Válido até 23h59 de hoje",
          "mediaUrl":    "https://exemplo.com/img/promo.jpg",
          "mediaType":   "IMAGE",
          "buttons": [
              { "id": "promo_subscribe", "displayText": "Assinar agora", "type": "REPLY" },
              { "id": "promo_details",   "displayText": "Ver detalhes",  "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>

### Mix de URL + CALL + COPY

Combina três tipos diferentes de botão em uma só mensagem. O `id` carrega 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":  "Pedido #12345 confirmado",
      "contentText": "Escolha como quer prosseguir:",
      "footerText":  "Cupom válido por 24h",
      "buttons": [
        { "id": "https://loja.exemplo.com/pedido/12345", "displayText": "Acompanhar pedido", "type": "URL"  },
        { "id": "+551130000000",                          "displayText": "Falar com SAC",     "type": "CALL" },
        { "id": "RYZE10",                                 "displayText": "Copiar cupom",      "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:  "Pedido #12345 confirmado",
      contentText: "Escolha como quer prosseguir:",
      footerText:  "Cupom válido por 24h",
      buttons: [
        { id: "https://loja.exemplo.com/pedido/12345", displayText: "Acompanhar pedido", type: "URL"  },
        { id: "+551130000000",                          displayText: "Falar com SAC",     type: "CALL" },
        { id: "RYZE10",                                 displayText: "Copiar cupom",      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":  "Pedido #12345 confirmado",
          "contentText": "Escolha como quer prosseguir:",
          "footerText":  "Cupom válido por 24h",
          "buttons": [
              {"id": "https://loja.exemplo.com/pedido/12345", "displayText": "Acompanhar pedido", "type": "URL"},
              {"id": "+551130000000",                          "displayText": "Falar com SAC",     "type": "CALL"},
              {"id": "RYZE10",                                 "displayText": "Copiar cupom",      "type": "COPY"}
          ]
      }
  )
  ```

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

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

  func main() {
      body := strings.NewReader(`{
          "number":      "5511999999999",
          "headerText":  "Pedido #12345 confirmado",
          "contentText": "Escolha como quer prosseguir:",
          "footerText":  "Cupom válido por 24h",
          "buttons": [
              { "id": "https://loja.exemplo.com/pedido/12345", "displayText": "Acompanhar pedido", "type": "URL"  },
              { "id": "+551130000000",                          "displayText": "Falar com SAC",     "type": "CALL" },
              { "id": "RYZE10",                                 "displayText": "Copiar cupom",      "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>

## Resposta de sucesso

O `content` retorna o `contentText` enviado, e o `messageType` é fixo em `buttons`. A definição dos botões em si não vem na resposta, guarde o `messageId` para correlacionar os cliques que chegam via webhook.

```json 200 OK theme={null}
{
  "success": true,
  "message": "Buttons message sent successfully",
  "status":  "sent",
  "data": {
    "messageId":   "3EB08FCF27E532F1F5F5",
    "direction":   "sent",
    "messageType": "buttons",
    "content":     "Como podemos ajudar você hoje?",
    "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>
  Quando o usuário toca em um botão `REPLY`, a resposta chega como mensagem de texto contendo o `id` do botão clicado, capture isso pelo webhook para encadear o fluxo.
</Note>

## Parâmetros de rota

<ParamField path="instance" type="string" required>
  Nome da instância (ex.: `$Instance_Name`).
</ParamField>

## Headers

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

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

## Request body

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

<ParamField body="contentText" type="string" required>
  Texto principal do corpo da mensagem (entre header e botões).
</ParamField>

<ParamField body="buttons" type="ButtonOption[]" required>
  Lista de botões. **Mínimo 1, máximo 3** (limite do WhatsApp). Cada botão tem:

  * `id` (string, **obrigatório**), semântica varia por `type`: `REPLY` retorna esse ID; `URL` abre essa URL; `CALL` disca esse número; `COPY` copia esse código.
  * `displayText` (string, **obrigatório**), texto visível no botão.
  * `type` (string), `REPLY` (padrão), `URL`, `CALL` ou `COPY`. Outro valor retorna `400 Button N: Type must be one of: REPLY, URL, CALL, COPY`.
</ParamField>

<ParamField body="headerText" type="string">
  Título exibido no topo da mensagem. **Ignorado se `mediaUrl` for fornecido** (a mídia substitui o header).
</ParamField>

<ParamField body="footerText" type="string">
  Texto opcional exibido abaixo dos botões.
</ParamField>

<ParamField body="mediaUrl" type="string">
  URL de uma mídia (imagem, vídeo ou documento) para usar como header. Quando enviado, `mediaType` torna-se obrigatório.
</ParamField>

<ParamField body="mediaType" type="string">
  Tipo da mídia em `mediaUrl`. **Obrigatório se `mediaUrl` estiver presente**. Aceita apenas `IMAGE`, `VIDEO` ou `DOCUMENT` (maiúsculo). Outro valor retorna `400 MediaType must be one of: IMAGE, VIDEO, DOCUMENT`.
</ParamField>

<ParamField body="delay" type="int" default="0">
  Tempo em **segundos** para aguardar antes de enviar. Durante o intervalo, o servidor envia o indicador de "digitando..." e dispara o "paused" antes do envio real.
</ParamField>

<ParamField body="replyTo" type="string">
  ID da mensagem a ser citada (reply). A mensagem original precisa pertencer à mesma instância e ter sido salva no banco.
</ParamField>

<ParamField body="replyPrivate" type="boolean" default="false">
  Quando `true` **e** `replyTo` aponta para uma mensagem originária de um grupo, a mensagem é redirecionada para o **privado** do autor original (mantendo a citação).
</ParamField>

<ParamField body="source" type="string" default="api">
  Identificador de origem para rastreabilidade (ex.: `crm`, `bot-suporte`, `n8n`). Salvo no registro da mensagem e propagado para webhooks.
</ParamField>

## Notas

<Note>
  * **`delay` é em segundos** (não milissegundos).
  * O WhatsApp aceita **no máximo 3 botões** por mensagem. Quatro ou mais retornam `400 Maximum of 3 buttons allowed`.
  * Se `mediaUrl` é enviado, `headerText` é silenciosamente ignorado.
  * `mediaType` é normalizado para maiúsculo internamente, envie sempre `IMAGE`, `VIDEO` ou `DOCUMENT`.
  * Para botões `URL`, garanta que o link comece com `https://` para evitar bloqueio pelo cliente.
  * Para botões `CALL`, use formato internacional (`+5511...`).
  * Aparelhos antigos podem cair em fallback de texto e exibir os botões como mensagem comum.
</Note>

## Erros

| HTTP | Status interno    | Mensagem                                                |
| ---- | ----------------- | ------------------------------------------------------- |
| 400  | ,                 | `Instance name is required`                             |
| 400  | ,                 | `Invalid request body: <detalhe>`                       |
| 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: <detalhe>`                |
| 400  | `invalid_request` | (motivo do request inválido detectado pelo serviço)     |
| 404  | ,                 | `Instance not found`                                    |
| 500  | `send_failed`     | `Failed to send buttons message: <reason>`              |
| 503  | `disconnected`    | `Instance is not connected to WhatsApp`                 |

Envelope de erro:

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