> ## 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.

# Send Buttons

> Send a message with up to 3 interactive buttons (REPLY, URL, CALL or COPY) and an optional media header

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

## Description

Sends a message with **up to 3 interactive buttons** (WhatsApp limit). Buttons accept four types: `REPLY` (default, returns the ID when clicked), `URL` (opens a link), `CALL` (dials a number) and `COPY` (copies a code). The header can be text (`headerText`) **or** media (`mediaUrl` + `mediaType`). If `mediaUrl` is sent, `mediaType` is required and must be **uppercase**: `IMAGE`, `VIDEO` or `DOCUMENT`. When media is present, `headerText` is ignored (the media replaces the title).

## Examples

### 3 REPLY buttons

The `REPLY` button is used to **reply to a specific message inside the conversation**: when the customer taps, WhatsApp sends a reply quoting the original message with the **text** of the button (`displayText`), that is what appears in the chat. In the **webhook/websocket**, what reaches your application is the `id` of the clicked button, allowing you to identify the option without depending on the displayed text.

<Warning>
  When `REPLY` is **mixed** with other types (`URL`, `CALL`, `COPY`) in the same message, the `REPLY` button **does not appear in WhatsApp Web/Desktop**, it is only visible on the smartphone app. The ideal is to use **only `REPLY` buttons** or **only combinations of `URL`/`CALL`/`COPY`**, without mixing.
</Warning>

Classic case of a quick menu. Each button returns its `id` in the webhook when clicked.

<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>

### With image header

When `mediaUrl` is present, `mediaType` is required and must be **uppercase** (`IMAGE`, `VIDEO`, `DOCUMENT`). `headerText` is ignored in this case.

<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>

### Mix of URL + CALL + COPY

Combines three different button types in a single message. The `id` carries distinct semantic values per type.

<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>

## Success response

The `content` returns the `contentText` you sent, and `messageType` is fixed at `buttons`. The button definitions themselves do not come back in the response, store the `messageId` to correlate the clicks that arrive via 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>
  When the user taps a `REPLY` button, the response arrives as a text message containing the `id` of the clicked button, capture that via webhook to chain the flow.
</Note>

## Path parameters

<ParamField path="instance" type="string" required>
  Instance name (e.g., `$Instance_Name`).
</ParamField>

## Headers

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

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

## Request body

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

<ParamField body="contentText" type="string" required>
  Main body text of the message (between header and buttons).
</ParamField>

<ParamField body="buttons" type="ButtonOption[]" required>
  List of buttons. **Minimum 1, maximum 3** (WhatsApp limit). Each button has:

  * `id` (string, **required**), semantics vary by `type`: `REPLY` returns this ID; `URL` opens this URL; `CALL` dials this number; `COPY` copies this code.
  * `displayText` (string, **required**), text visible on the button.
  * `type` (string), `REPLY` (default), `URL`, `CALL` or `COPY`. Any other value returns `400 Button N: Type must be one of: REPLY, URL, CALL, COPY`.
</ParamField>

<ParamField body="headerText" type="string">
  Title displayed at the top of the message. **Ignored if `mediaUrl` is provided** (the media replaces the header).
</ParamField>

<ParamField body="footerText" type="string">
  Optional text displayed below the buttons.
</ParamField>

<ParamField body="mediaUrl" type="string">
  URL of a media file (image, video or document) to use as the header. When sent, `mediaType` becomes required.
</ParamField>

<ParamField body="mediaType" type="string">
  Type of the media in `mediaUrl`. **Required if `mediaUrl` is present**. Accepts only `IMAGE`, `VIDEO` or `DOCUMENT` (uppercase). Any other value returns `400 MediaType must be one of: IMAGE, VIDEO, DOCUMENT`.
</ParamField>

<ParamField body="delay" type="int" default="0">
  Time in **seconds** to wait before sending. During the interval, the server sends the "typing..." indicator and fires "paused" before the actual send.
</ParamField>

<ParamField body="replyTo" type="string">
  ID of the message to be quoted (reply). The original message must belong to the same instance and have been saved in the database.
</ParamField>

<ParamField body="replyPrivate" type="boolean" default="false">
  When `true` **and** `replyTo` points to a message originating from a group, the message is redirected to the original author's **private** chat (keeping the quote).
</ParamField>

<ParamField body="source" type="string" default="api">
  Origin identifier for traceability (e.g., `crm`, `bot-suporte`, `n8n`). Saved on the message record and propagated to webhooks.
</ParamField>

## Notes

<Note>
  * **`delay` is in seconds** (not milliseconds).
  * WhatsApp accepts **up to 3 buttons** per message. Four or more return `400 Maximum of 3 buttons allowed`.
  * If `mediaUrl` is sent, `headerText` is silently ignored.
  * `mediaType` is normalized to uppercase internally, always send `IMAGE`, `VIDEO` or `DOCUMENT`.
  * For `URL` buttons, make sure the link starts with `https://` to avoid being blocked by the client.
  * For `CALL` buttons, use the international format (`+5511...`).
  * Older devices may fall back to text and display the buttons as a regular message.
</Note>

## Errors

| HTTP | Internal status   | Message                                                  |
| ---- | ----------------- | -------------------------------------------------------- |
| 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` | (reason for the invalid request detected by the service) |
| 404  | ,                 | `Instance not found`                                     |
| 500  | `send_failed`     | `Failed to send buttons message: <reason>`               |
| 503  | `disconnected`    | `Instance is not connected to WhatsApp`                  |

Error envelope:

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