> For the complete documentation index, see [llms.txt](https://docs.quickreply.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.quickreply.ai/developer/apis/rcs-send-template-api.md).

# RCS Send Template API

**Base URL:** `https://app.quickreply.ai`

Send an approved RCS template to a recipient. The request carries **only the dynamic values** for the template — the structure (which components exist, their `{{n}}` placeholders and `paramLength`) lives on the stored template and is resolved by `templateId`.\
\ <mark style="color:green;">`POST`</mark>  <https://app.quickreply.ai/api/v4/external/template-message/rcs/send>

***

### Authentication

External secret-key flow, via headers:

| Header         | Value              | Description                                                                                                                                                                        |
| -------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `x-client-id`  | Company id         | Click the **profile/account name** in the bottom-left corner of the QuickReply dashboard. In the menu that opens, the **Company ID** is displayed directly below the company name. |
| `x-secret-key` | Company secret key | You can find client-id of your account in [Settings > Developers > API credentials](https://app.quickreply.ai/v2/settings/dev/api-credentials)                                     |
| `Content-Type` | `application/json` |                                                                                                                                                                                    |

***

### Request body

<table><thead><tr><th>Field</th><th>Type</th><th width="198.578125">Description</th></tr></thead><tbody><tr><td><code>to</code><mark style="color:red;">*</mark></td><td>string</td><td>Recipient phone number in E.164 format, e.g. <code>+919800000000</code>.</td></tr><tr><td><code>templateId</code><mark style="color:red;">*</mark></td><td>string</td><td>You can find the template id by clicking on the 3 dots at the right of template block and clicking "Copy Template Id"<br><br>The stored RCS template's <code>_id</code>. Must be an <code>APPROVED</code> template.</td></tr><tr><td><code>templateParams (Optional)</code></td><td>object</td><td>Dynamic values for the template. Send only the fields the template needs (see below).</td></tr><tr><td><code>customEngage (Optional)</code></td><td>object</td><td>Follow-up trigger fired on custom engagement.</td></tr><tr><td><code>buttonEngage (Optional)</code></td><td>object</td><td>Follow-up trigger fired on button click.</td></tr></tbody></table>

#### `templateParams`

Send only what the matched template requires. All fields are optional at the schema level; the template's structure determines what is actually needed.

| Field              | Shape                                                                    | Notes                                                                                                                                                                                                   |
| ------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `headerParam`      | `{ type, value, filename?, size? }`                                      | `type` ∈ `IMAGE` / `VIDEO` / `DOCUMENT`. `value` **must be a valid URL** (the media link). Required for media and carousel-card headers. `filename` applies to `DOCUMENT` (defaults to `document.pdf`). |
| `bodyParams`       | `[{ value }]`                                                            | Count must equal the body's `paramLength`, in `{{1}},{{2}},…` order.                                                                                                                                    |
| `titleParams`      | `[{ value }]`                                                            | Count must equal the title's `paramLength`.                                                                                                                                                             |
| `buttonParams`     | `[{ value, index, key? }]`                                               | `index` = button position in the template. `key` is used for `VIEW_LOCATION` (`lat` / `long`) and `CALENDAR` (`eventTitle` / `eventDescription` / `startDateTime` / `endDateTime`).                     |
| `suggestionParams` | `[{ value, index, key? }]`                                               | Same shape as `buttonParams`, for suggestion chips.                                                                                                                                                     |
| `carouselParams`   | `[{ cardIndex, headerParam, bodyParams?, titleParams?, buttonParams? }]` | One entry per card; each card's `headerParam` is required.                                                                                                                                              |

> **Media is mandatory for interactive templates.** Any template that defines **buttons or suggestions** must also carry a media header (`IMAGE` / `VIDEO` / `DOCUMENT`). Every send for such a template must include a `headerParam` with a valid media URL — this holds even for static buttons (`QUICK_REPLY` / `SHARE_LOCATION`) that otherwise take no value.

**Params that need nothing sent (but still require the media header above):**

* Static text templates with **no buttons/suggestions** (`paramLength: 0`) — omit `bodyParams` or send `[]`; no header needed.
* `QUICK_REPLY` / `SHARE_LOCATION` buttons and suggestions — no `buttonParams` / `suggestionParams` value.
* URL buttons — only need a `buttonParam` when the URL contains `{{1}}`. Dynamic URL buttons whose URL includes the template's `shortLinkDomain` are auto-shortened via swift-link.

#### `customEngage` / `buttonEngage`

Passed through and attached to the outgoing message (`channel.custom_engage` / `channel.button_engage`). For each object supply **either `playbook` or `event`**, not both.

| Field      | Type   | Meaning                           |
| ---------- | ------ | --------------------------------- |
| `playbook` | string | Playbook id to run on engagement. |
| `event`    | string | Event name to fire on engagement. |

***

### Response

**`200 OK`**

```json
{
  "conversationId": "6a42273ab57bb20850cfb9a5_conv",
  "messageId": "6a477e55e06664018546504f_msg",
  "messageStatus": "SENT"
}
```

***

### Examples

The examples below cover every send-time scenario. Substitute `<client-id>`, `<secret-key>`, and `<template-id>`.

#### 1. Text body with dynamic params

Body-only template `"Hello {{1}}!"`, no buttons — so no media header. Static text templates work the same way with `bodyParams: []`.

```bash
curl -sS -X POST "<https://app.quickreply.ai/api/v4/external/template-message/rcs/send>" \
  -H "Content-Type: application/json" \
  -H "x-client-id: <client-id>" \
  -H "x-secret-key: <secret-key>" \
  -d '{
    "to": "+919800000000",
    "templateId": "<template-id>",
    "templateParams": { "bodyParams": [{ "value": "John" }] }
  }'
```

#### 2. Dynamic URL button (with mandatory media header)

For a URL button whose url is `"<https://example.link/{{1}>}"`. Because the template has a button, a media `headerParam` is required.

```bash
curl -sS -X POST "<https://app.quickreply.ai/api/v4/external/template-message/rcs/send>" \
  -H "Content-Type: application/json" \
  -H "x-client-id: <client-id>" \
  -H "x-secret-key: <secret-key>" \
  -d '{
    "to": "+919800000000",
    "templateId": "<template-id>",
    "templateParams": {
      "headerParam": { "type": "IMAGE", "value": "<https://cdn.example.com/img.jpg>" },
      "bodyParams": [{ "value": "John" }],
      "buttonParams": [{ "index": 0, "value": "<https://example.link/order123>" }]
    }
  }'
```

#### 3. Keyed buttons — VIEW\_LOCATION and CALENDAR (with mandatory media header)

Dynamic fields on special button types are passed via `key` at the button's `index`. A `VIEW_LOCATION` button uses `lat` / `long`; a `CALENDAR` button uses the event keys shown here. The media `headerParam` is required because the template has buttons.

```bash
curl -sS -X POST "<https://app.quickreply.ai/api/v4/external/template-message/rcs/send>" \
  -H "Content-Type: application/json" \
  -H "x-client-id: <client-id>" \
  -H "x-secret-key: <secret-key>" \
  -d '{
    "to": "+919800000000",
    "templateId": "<template-id>",
    "templateParams": {
      "headerParam": { "type": "IMAGE", "value": "<https://cdn.example.com/img.jpg>" },
      "bodyParams": [{ "value": "John" }],
      "buttonParams": [
        { "index": 0, "key": "lat",  "value": "19.0760" },
        { "index": 0, "key": "long", "value": "72.8777" },
        { "index": 1, "key": "eventTitle",       "value": "Demo Call" },
        { "index": 1, "key": "eventDescription", "value": "Product walkthrough" },
        { "index": 1, "key": "startDateTime",    "value": "2026-07-10T10:00:00Z" },
        { "index": 1, "key": "endDateTime",      "value": "2026-07-10T10:30:00Z" }
      ]
    }
  }'
```

#### 4. Media header — IMAGE / VIDEO / DOCUMENT

One shape for all three. Set `type` to `IMAGE`, `VIDEO`, or `DOCUMENT`; `value` must be a valid media URL. `DOCUMENT` may include `filename`.

```bash
curl -sS -X POST "<https://app.quickreply.ai/api/v4/external/template-message/rcs/send>" \
  -H "Content-Type: application/json" \
  -H "x-client-id: <client-id>" \
  -H "x-secret-key: <secret-key>" \
  -d '{
    "to": "+919800000000",
    "templateId": "<template-id>",
    "templateParams": {
      "headerParam": { "type": "IMAGE", "value": "<https://cdn.example.com/img.jpg>" },
      "titleParams": [{ "value": "John" }],
      "bodyParams": [{ "value": "#123" }]
    }
  }'
```

> For `DOCUMENT`: `"headerParam": { "type": "DOCUMENT", "value": "<https://cdn.example.com/doc.pdf>", "filename": "invoice.pdf" }`

#### 5. Carousel — multiple cards

One `carouselParams` entry per card; each card's `headerParam` is required.

```bash
curl -sS -X POST "<https://app.quickreply.ai/api/v4/external/template-message/rcs/send>" \
  -H "Content-Type: application/json" \
  -H "x-client-id: <client-id>" \
  -H "x-secret-key: <secret-key>" \
  -d '{
    "to": "+919800000000",
    "templateId": "<template-id>",
    "templateParams": {
      "carouselParams": [
        {
          "cardIndex": 0,
          "headerParam": { "type": "IMAGE", "value": "<https://cdn.example.com/card0.jpg>" },
          "titleParams": [{ "value": "first title" }],
          "bodyParams":  [{ "value": "first body" }]
        },
        {
          "cardIndex": 1,
          "headerParam": { "type": "IMAGE", "value": "<https://cdn.example.com/card1.jpg>" },
          "titleParams": [{ "value": "second title" }],
          "bodyParams":  [{ "value": "second body" }]
        }
      ]
    }
  }'
```

#### 6. With engagement follow-ups

`customEngage` / `buttonEngage` attach to any template. Use `playbook` **or** `event` per object. (If the template defines buttons, include the media `headerParam` as noted above.)

```bash
curl -sS -X POST "<https://app.quickreply.ai/api/v4/external/template-message/rcs/send>" \
  -H "Content-Type: application/json" \
  -H "x-client-id: <client-id>" \
  -H "x-secret-key: <secret-key>" \
  -d '{
    "to": "+919800000000",
    "templateId": "<template-id>",
    "templateParams": { "bodyParams": [{ "value": "John" }] },
    "customEngage": { "playbook": "playbook_reengage_1" },
    "buttonEngage": { "event": "rcs_button_clicked" }
  }'
```

***

### Errors (`400` / `403`)

| Condition                                                          | Message                                                                           |
| ------------------------------------------------------------------ | --------------------------------------------------------------------------------- |
| Unknown channel                                                    | `Invalid channel. Supported: rcs`                                                 |
| Missing `to` or `templateId`                                       | `to and templateId are required`                                                  |
| `to` not a valid phone number                                      | `to must be a valid phone number`                                                 |
| `templateParams` fails schema (e.g. `headerParam.value` not a URL) | `Invalid templateParams`                                                          |
| Body param count mismatch                                          | `Body parameter count mismatch: expected N, received M`                           |
| Body requires params, none sent                                    | `Body requires N parameter(s), but none were provided`                            |
| Media header param missing                                         | `Header component requires a parameter with media link...`                        |
| Carousel card header missing                                       | `Carousel card at index N requires a header parameter with media link`            |
| Dynamic URL button without value                                   | `URL button "..." requires a variable parameter, but none was provided`           |
| Keyed dynamic field missing                                        | `... has dynamic '<field>' field but no variable with key '<field>' was provided` |
| Template not approved                                              | `Template is not approved (status: ...)`                                          |
