# Publishing Template API V3 — Developer Guide

**Applies to:** Sprinklr Publishing Template APIs V3 — `POST /api/v3/publishing/message`

**V2 API reference:** [Publishing Template](https://dev.sprinklr.com/publishing-template)

## 1. Overview

Publishing templates let you send **structured, interactive private messages** — quick replies, carousels, cards, list pickers, forms, appointment and payment requests — instead of plain text. You choose a template by setting the `type` field on the `attachment` object inside the message content. The channel determines which template types are valid.

All publishing templates in V3 are sent through a **single endpoint**:

| Operation | Method and path | operationId |
|  --- | --- | --- |
| Reply to or send a new private message | `POST /api/v3/publishing/message` | `PublishingApiV3_sendPrivateMessage` |


Tag: `Publishing V3`. Request body is **required**, media type `application/json`, schema `Reply`.

For more details refer to the [Publishing V3 API Reference](/apis/sprinklr-v3/publishing).

### 1.1 Request data model

```
Reply
├── content              (Content)      — required by schema
│   ├── title            (string)
│   ├── text             (string)
│   ├── richText / isRichText
│   ├── templateId       (string)
│   └── attachment       (Attachment)   — discriminated by "type"
├── taxonomy             (Taxonomy)     — required by schema
│   └── campaignId       (string)       — required by schema
├── scheduleDate         (int64, epoch ms) — required by schema
├── toProfile            (ProfileKey)
├── accountId            (int64)
├── inReplyToMessageId   (string)
├── approval             (Approval)
├── channelOptions[]     (ChannelOptions)
└── allowDuplicateMessages (boolean)
```

The `attachment` object is a `oneOf` with `discriminator.propertyName: type`. The value of `type` selects the template.

### 1.2 Channel and template-type support matrix

Template availability is documented per channel. A type that is valid on one channel is not automatically valid on another.

| Channel | `channelType` | Documented template types |
|  --- | --- | --- |
| Facebook | `FACEBOOK` | `QUICK_REPLY`, `BUTTON_TEMPLATE`, `CAROUSEL` |
| Instagram | `INSTAGRAM` | `QUICK_REPLY`, `CAROUSEL` |
| Twitter | `TWITTER` | `QUICK_REPLY` |
| WhatsApp | `WHATSAPP_BUSINESS` | `LIST_PICKER`, `CARD` (plain / image / video / document header), `GEO_LOCATION`, `REQUEST_LOCATION`, `ADDRESS`, `CTA_URL` |
| Apple Business Chat | `APPLE_BUSINESS_CHAT` | `RICH_LINK`, `LIST_PICKER`, plain text (no attachment), `AUTHENTICATION`, `QUICK_REPLY`, `TIME_PICKER`, `APP_LINK`, `APPLE_PAY`, `FORM` |
| Sprinklr Live Chat | `SPRINKLR_LIVE_CHAT` | `CARD`, `QUICK_REPLY`, `CAROUSEL`, `PRODUCT_LIST`, `APPOINTMENT`, `SURVEY`, `SECURE_FORM`, `RICH_TEXT_CAROUSEL` |


### 1.3 Where this endpoint sits in the wider Publishing V3 surface

This guide covers only `/publishing/message`. The remaining operations, listed for orientation:

| Area | Operations |
|  --- | --- |
| Draft | `POST` / `GET` / `PUT` / `DELETE /api/v3/publishing/draft`; `POST /api/v3/publishing/draft/schedule?draftId=` |
| Post | `POST /api/v3/publishing/post`; `GET /api/v3/publishing/post?postIds=`; `PUT /api/v3/publishing/post/workflow-properties?accountId=&channelId=`; `DELETE /api/v3/publishing/post?postId=` |
| Message | `POST /api/v3/publishing/message` — **this guide** |
| Reply | `POST /api/v3/publishing/reply`; `POST /api/v3/publishing/reply/by-case?caseId=` |
| Preview | `POST /api/v3/publishing/preview?messageId=&exportType=&name=`; `GET /api/v3/publishing/preview?previewId=` (annotated "Need to review" in the ticket) |


## 2. Base URLs and environments

```
https://api3.sprinklr.com/{env}/api/v3/publishing/message
```

`env` — default `prod`. Allowed values:

`prod`, `prod0`, `prod2`, `prod3`, `prod4`, `prod5`, `prod6`, `prod8`, `prod11`, `prod12`, `prod15`, `prod16`, `prod17`, `prod18`, `prod19`, `prod21`, `prod25`

Use the environment your Sprinklr partner instance is provisioned on. Sending to the wrong environment produces authentication or not-found errors, not a redirect.

## 3. Authentication and common headers

All Case API calls are authenticated with OAuth 2.0. See [Developer Tools in Sprinklr](https://www.sprinklr.com/help/articles/developer-tools/developer-tools-in-sprinklr/692e8b39f0afa271d18a5929) for API key and secret generation, and the Authorize flow.

| Header | Value | Purpose | Required on |
|  --- | --- | --- | --- |
| `Authorization` | `******` | Credential used by the API to authenticate a user with the server | All requests |
| `Key` | `{{apiKey}}` | API key that authenticates the application with the server | All requests |
| `Content-Type` | `application/json` | Declares the request body media type | `POST`, `PUT`, `PATCH` |
| `Accept` | `application/json` | Determines the acceptable response type from the server | All requests |


## 4. Shared request parameters

| Field | Type | Required (schema) | Required (channel docs) | Description |
|  --- | --- | --- | --- | --- |
| `content` | object (`Content`) | **Yes** | Yes | Message body and attachment. |
| `content.title` | string | No | Optional | Message title. |
| `content.text` | string | No | Optional | Message text. |
| `content.attachment` | object (`Attachment`) | No | Required (except Apple Business Chat plain text) | The template payload. Discriminated by `type`. |
| `content.templateId` | string | No | — | Identifier of a saved template. |
| `content.richText` / `content.isRichText` | string / boolean | No | — | Rich-text body and flag. |
| `taxonomy` | object (`Taxonomy`) | **Yes** | Required | Campaign and custom-property assignment. |
| `taxonomy.campaignId` | string | **Yes** | Required | Campaign the message is attributed to. |
| `taxonomy.subCampaignId` | string | No | — | Sub-campaign. |
| `taxonomy.clientCustomProperties` | map<string, string[]> | No | Optional | Client custom properties. Channel docs type this as "String"; the schema is a map of string arrays. |
| `taxonomy.partnerCustomProperties` | map<string, string[]> | No | Optional | Partner custom properties. Same type discrepancy. |
| `taxonomy.tags` | string[] | No | Optional | Tags. Channel docs type this as "String"; the schema is an array. |
| `taxonomy.urlShortenerId` | string | No | Optional | URL shortener configuration. |
| `scheduleDate` | int64 (epoch ms) | **Yes** | Optional on Facebook/Instagram; Required on WhatsApp/Twitter | Send time in epoch milliseconds. |
| `accountId` | int64 | No | Required | Account the message is sent from. |
| `inReplyToMessageId` | string | No | Required | Id of the message being replied to. |
| `toProfile` | object (`ProfileKey`) | No | Required | Recipient profile key. |
| `toProfile.channelType` | string | No | Required | Channel of the recipient. |
| `toProfile.channelId` | string | No | Required | Channel-specific recipient id. |
| `toProfile.screenName` | string | No | Required on Facebook / Twitter / Instagram / Live Chat; Optional on Apple Business Chat; absent from the WhatsApp table | Recipient screen name. |
| `approval` | object (`Approval`) | No | Optional | Approval routing. |
| `approval.type` | see §10 | No | Optional | Channel docs give the enum `ACCOUNT_OWNER`, `USER`, `APPROVAL_PATH`, `NONE` with default `NONE`; the schema types it as an `Option` object. |
| `approval.id` | string | No | Optional | Approver id. |
| `allowDuplicateMessages` | boolean | No | Used in Instagram examples | Permits sending a message identical to a recent one. |
| `channelOptions` | array (`ChannelOptions`) | No | — | Channel-specific options. Not exercised in any channel example. |
| `status` | string | No | — | e.g. `SCHEDULED`, `APPROVAL`, `SENT`. |
| `externalId`, `sourceLocale`, `contentLocale`, `messageCategory`, `conversationId`, `parentMessageId`, `accountGroupId`, `permalink`, `publishedDate`, `createdTime`, `modifiedTime`, `authorId`, `enrichments`, `customPropertiesMetadata`, `autoResponse` | mixed | No | — | Additional `Reply` fields available in the schema but not used by the template documentation. |


> **Dev note — Sprinklr Live Chat `accountId`.** Derive it from the Live Chat application id: if the app id is `app_12345`, then `accountId` is `12345`.


> **Dev note — retrieving the sent post.** After a successful send, use the **Read Post by Post Id** API or **Universal Search** to fetch the post details.


## 5. Channel reference

All payloads below are **illustrative**. Credentials are placeholders.

### 5.1 Facebook — `channelType: FACEBOOK`

Supports `QUICK_REPLY`, `BUTTON_TEMPLATE`, `CAROUSEL`.

**QUICK_REPLY**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `QUICK_REPLY` |
| `message` | string | No | Prompt shown above the choices. |
| `quickReplies[]` | array | Yes | Choice list. |
| `quickReplies[].title` | string | Yes | Choice label. |
| `quickReplies[].subtitle` | string | No | Secondary label. |
| `quickReplies[].imageUrl` | string | No | Icon for the choice. |
| `quickReplies[].actionDetail` | object | Yes | Action to run on tap. |
| `quickReplies[].actionDetail.action` | string | Yes | `TEXT` |


```bash
curl --location 'https://api3.sprinklr.com/prod/api/v3/publishing/message' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Content-Type: application/json' \
--data '{
  "accountId": 000000,
  "inReplyToMessageId": "MESSAGE_ID",
  "toProfile": {
    "channelType": "FACEBOOK",
    "channelId": "CHANNEL_ID",
    "screenName": "SCREEN_NAME"
  },
  "taxonomy": { "campaignId": "CAMPAIGN_ID" },
  "content": {
    "attachment": {
      "type": "QUICK_REPLY",
      "message": "Please pick an option",
      "quickReplies": [
        {
          "title": "Track my order",
          "actionDetail": { "action": "TEXT" }
        },
        {
          "title": "Talk to an agent",
          "actionDetail": { "action": "TEXT" }
        }
      ]
    }
  }
}'
```

**BUTTON_TEMPLATE**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `BUTTON_TEMPLATE` |
| `elementList[]` | array | Yes | Outer element list. |
| `elementList[].title` | string | Yes | The question or prompt. |
| `elementList[].elementList[]` | array | Yes | Buttons. |
| `elementList[].elementList[].title` | string | Yes | Button label. |
| `elementList[].elementList[].actionType` | string | Yes | `web_url` or `no_action`. |
| `elementList[].elementList[].url` | string | Conditional | Required when `actionType` is `web_url`. |
| `elementList[].elementList[].type` | string | No | e.g. `text`. |


**CAROUSEL**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `CAROUSEL` |
| `cardAttachmentList[]` | array | Yes | Cards. |
| `cardAttachmentList[].type` | string | Yes | `CARD` |
| `cardAttachmentList[].title` | string | Yes | Card title. |
| `cardAttachmentList[].description` | string | Yes | Card description. |
| `cardAttachmentList[].previewImageUrl` | string | Yes | Card image. |
| `cardAttachmentList[].buttons[]` | array | Yes | Card buttons. |
| `buttons[].title` | string | Yes | Button label. |
| `buttons[].actionDetail` | object | Yes | Action. |
| `buttons[].actionDetail.action` | string | Yes | `OPEN_URL` or `POST_BACK`. |
| `buttons[].actionDetail.url` | string | Conditional | Required when `action` is `OPEN_URL`. |
| `buttons[].actionDetail.data` | string | Conditional | Required when the action type is `TEXT`. |


Observed response shape:

```json
{ "data": ["POST_593685584"], "errors": [] }
```

V2 reference: [Facebook Dynamic Templates](https://dev.sprinklr.com/facebook-dynamic-templates)[doc:turn1doc4].

### 5.2 Instagram — `channelType: INSTAGRAM`

Supports `QUICK_REPLY` and `CAROUSEL`. Both documented examples set `allowDuplicateMessages: true`.

**QUICK_REPLY**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `QUICK_REPLY` |
| `message` | string | Yes | Prompt text. |
| `quickReplies[].title` | string | Yes | Choice label. |
| `quickReplies[].actionDetail` | object | Yes | `{ "action": "TEXT" }`. The parameter table spells this `actionDetails`; the working JSON uses `actionDetail`. See §10. |


**CAROUSEL**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `CAROUSEL` |
| `cardAttachmentList[].type` | string | Yes | `CARD` |
| `cardAttachmentList[].title` | string | Yes | Card title. |
| `cardAttachmentList[].description` | string | Yes | Card description. |
| `cardAttachmentList[].previewImageUrl` | string | Yes | Card image. |
| `cardAttachmentList[].buttons[].title` | string | Yes | Button label. |
| `cardAttachmentList[].buttons[].actionDetail` | object | Yes | e.g. `{ "action": "TEXT", "data": "TEXT_POST_BACK" }`. |


> **Caveat.** The V3 source document's "Carousel" example request body actually contains a `QUICK_REPLY` payload. Use the field table, not that example, when building a carousel. See §10.


V2 reference: [Instagram Dynamic Templates](https://dev.sprinklr.com/instagram-dynamic-templates)[doc:turn1doc8].

### 5.3 Twitter — `channelType: TWITTER`

Supports `QUICK_REPLY` only. `scheduleDate` is marked Required in the Twitter document.

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `QUICK_REPLY` |
| `message` | string | Yes | Prompt text. |
| `quickReplies[].id` | string | Yes | Choice id. |
| `quickReplies[].title` | string | Yes | Choice label. |
| `quickReplies[].subtitle` | string | No | Secondary label. |
| `quickReplies[].imageUrl` | string | No | Icon. |
| `quickReplies[].payload` | string | No | **Not supported on Facebook and Twitter.** |
| `quickReplies[].actionDetail` | object | Yes | `{ "action": "TEXT" }` |


> **Caveat.** The Twitter document's attachment table lists card fields (`title`, `previewImageUrl`, `disableManualResponse`, `buttons[]`) that its own working example does not send. The example sends `message` + `quickReplies[]`. The table above follows the example. See §10.


V2 reference: [Twitter Dynamic Templates](https://dev.sprinklr.com/twitter-dynamic-templates).

### 5.4 WhatsApp — `channelType: WHATSAPP_BUSINESS`

The WhatsApp table marks `scheduleDate` **Required** and does not list `toProfile.screenName`.

**LIST_PICKER**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `LIST_PICKER` |
| `header.text` | string | Yes | Header text. |
| `body` | string | Yes | Body text. |
| `footerText` | string | Yes | Footer text. |
| `buttonTitle` | string | Yes | Label of the button that opens the list. |
| `sections[]` | array | Yes | List sections. |
| `sections[].title` | string | Yes | Section title. |
| `sections[].items[].id` | string | Yes | Item id. |
| `sections[].items[].title` | string | Yes | Item title. |
| `sections[].items[].subtitle` | string | No | Item subtitle. |


**CARD** (plain, or with an image / video / document header)

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `CARD` |
| `header` | object | Yes | Header. For media headers: `type` (`IMAGE`, `VIDEO`, `DOC`) Required, `url` Required for image and document, `title` Optional. For text headers use `header.text`. |
| `footerText` | string | Yes | Footer text. |
| `buttons[].id` | string | Yes | Button id. |
| `buttons[].title` | string | Yes | Button label. |
| `buttons[].subtitle` | string | No | Button subtitle. |


> **Caveat.** The source document's "CARD with Image" example sends `"type": "LIST_PICKER"` with an image header rather than `CARD`. See §10.


**GEO_LOCATION**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `GEO_LOCATION` |
| `latitude` | number | Yes | Latitude. |
| `longitude` | number | Yes | Longitude. |
| `name` | string | No | Place name. |
| `address` | string | No | Street address. |


**REQUEST_LOCATION**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `REQUEST_LOCATION` |
| `message` | string | No | Prompt shown with the request. |


**ADDRESS**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `ADDRESS` |
| `header` / `message` / `footer` | string | No | Surrounding copy. |
| `addressFields[]` | array | Yes | Fields to collect. |
| `addressFields[].fieldName` | string | Yes | e.g. `CITY`. |
| `addressFields[].prefilledValuePlaceHolder` | object | No | `assetClass` (e.g. `MESSAGE`), `customFieldName`, `displayName`. |
| `addressFields[].copyResponseToCustomField` | string | No | Custom field to copy the answer into. |


**CTA_URL**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `CTA_URL` |
| `header.image` | object | No | `{ "type": "IMAGE", "url": "…" }`. |
| `body` | string | No | Body text. |
| `footerText` | string | No | Footer text. |
| `ctaUrl` | string | Yes | Destination URL. |
| `displayText` | string | Yes | Button label. |
| `urlType` | string | Yes | `STATIC`. |


V2 reference: [WhatsApp Dynamic Templates](https://dev.sprinklr.com/whatsapp-dynamic-templates)[doc:turn1doc5].

### 5.5 Apple Business Chat — `channelType: APPLE_BUSINESS_CHAT`

`toProfile.screenName` is Optional on this channel.

Documented image style dimensions: **Icon 40×40**, **Small 60×60**, **Large 263×150**. `style` values used on list items and on `receivedMessage` / `replyMessage`: `icon`, `small`, `large`.

**RICH_LINK**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `RICH_LINK` (default). |
| `mediaUrl` | string | Yes | Media to render. |
| `link` | string | No | Destination URL. |
| `title` | string | No | Link title. |
| `previewUrl` | string | No | Preview image. |


**LIST_PICKER**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `LIST_PICKER` |
| `multipleSelection` | boolean | Yes | Allow multi-select across the picker. |
| `sections[]` | array | Yes | Sections. |
| `sections[].title` | string | Yes | Section title. |
| `sections[].multipleSelection` | boolean | Yes | Per-section multi-select. |
| `sections[].items[]` | array | Yes | Items (`title`, `subtitle`, `imageUrl`, `style`). |
| `receivedMessage` | object | Yes | Bubble shown to the customer before opening the picker. |
| `replyMessage` | object | Yes | Bubble shown after the customer replies. |


**Plain text / LINK** — send only `content.text`. No attachment.

**AUTHENTICATION**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | Attachment type. |
| `loginAppConfigId` | string | No | Login app configuration id. In the source table this row's Parameter cell is blank; the name is taken from the example payload. |
| `additionalParameters` | object | No | Extra OAuth parameters. |
| `scope` | string[] | No | Requested scopes. |
| `receivedMessage` / `replyMessage` | object | No | `title`, `subTitle`, `secondarySubtitle`, `tertiarySubtitle`, `imageUrl`. |


**QUICK_REPLY**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `QUICK_REPLY` |
| `message` | string | Yes | Prompt text. |
| `quickReplies[]` | array | Yes | **2 to 5 choices**, single-select. |
| `quickReplies[].id` | string | No | Choice id. |
| `quickReplies[].title` | string | Yes | Choice label. |
| `quickReplies[].actionDetail` | object | Yes | `{ "action": "TEXT" }` |


**TIME_PICKER**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | Attachment type. |
| `timeSlots[]` | array | No | `startTime`, `duration`. The example payload spells the key `timeslots`. See §10. |
| `timezone` | string | No | Timezone for the slots. |
| `title` | string | No | Picker title. |
| `location` | object | No | `latitude`, `longitude`, `radius`, `title`. |
| `receivedMessage` / `replyMessage` | object | No | Bubble content. |


**APP_LINK** (iMessage app)

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `appId` | string | Yes | App id. |
| `appName` | string | Yes | App name. |
| `bId` | string | Yes | Bundle id. |
| `url` | string | Yes | App URL. |
| `useLiveLayout` | boolean | No | Use live layout. |
| `attachments[]` | array | Yes | `type` Required, `url` Required, `title` Optional, `description` Optional. |


**APPLE_PAY**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | Attachment type. |
| `paymentRequest` | object | Yes | `lineItems[]`, `total` (`amount`, `label`, `type`), `countryCode`, `currencyCode`, `requiredBillingContactFields`, `requiredShippingContactFields`, `supportedCountries`, `shippingMethods[]`, `applePay` (`supportedNetworks`, `merchantCapabilities`). |
| `paymentAccountId` | string | No | Payment account. |


**FORM**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | Attachment type. |
| `form` | object | Yes | `formTitle`, `repeatable`, `minCount`, `maxCount`, `fields[]`. |
| `form.fields[]` | array | Yes | `name`, `description`, `type` (`Form`, `Date Picker`, `Picker`, `Select`), `picklistValues[]` (`label`, `value`), `parentChild`, `multiValued`, `defaultValue`, `additional` (`maximumDate`, `minimumDate`, `dateFormat`). |
| `submit.title` | string | Yes | Submit button label. |
| `messageTitle` / `messageSubtitle` / `buttonTitle` | string | No | Entry-bubble copy. |
| `showSummary` | boolean | No | Show a summary after submission. |
| `receivedMessage` / `replyMessage` | object | No | Bubble content. |


V2 reference: [Apple Business Chat Templates](https://dev.sprinklr.com/apple-business-chat-templates).

### 5.6 Sprinklr Live Chat — `channelType: SPRINKLR_LIVE_CHAT`

Derive `accountId` from the Live Chat app id (`app_12345` → `12345`).

**CARD**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `CARD` |
| `title` | string | Yes | Card title. |
| `description` | string | No | Card description. |
| `previewImageUrl` | string | Yes | Card image. |
| `disableManualResponse` | boolean | Yes | Default `false`. Blocks free-text replies while the card is active. |
| `buttons[].id` | string | Yes | Button id. |
| `buttons[].title` | string | Yes | Button label. |
| `buttons[].subtitle` | string | No | Button subtitle. |
| `buttons[].payload` | string | No | Payload. |
| `buttons[].actionDetail.action` | string | Yes | `TEXT`, or `OPEN_URL` (requires `url`). |


> The document's CARD example sends `"type": "INFO_CARD"`. See §10.


**QUICK_REPLY** — `quickReplies[]` with `actionDetail.action: TEXT` only.

**CAROUSEL** and **RICH_TEXT_CAROUSEL** — `cardAttachmentList[]` of `CARD` objects, each with `buttons[]` using `actionDetail.action: TEXT`.

**PRODUCT_LIST**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `type` | string | Yes | `PRODUCT_LIST` |
| `cardAttachmentList[]` | array | Yes | Product cards. |
| `buttons[].actionDetail.action` | string | Yes | e.g. `OPEN_URL`. |
| `buttons[].actionDetail.url` | string | Yes | Destination. |
| `buttons[].actionDetail.target` | string | No | Link target. |
| `otherConfigs.layoutType` | string | No | e.g. `IMAGE_TOP`. |
| `screenReaderLabel` | string | No | Accessibility label. |


**APPOINTMENT**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `selectDateTitle` | string | Yes | Date step title. |
| `selectTimeTitle` | string | Yes | Time step title. |
| `selectDatePlaceholder` | string | No | Date placeholder. |
| `submitDateTitle` | string | No | Submit label for the date step. |
| `numDays` | integer | No | Number of days offered. |
| `dateFormat` | string | No | e.g. `MMM D YYYY`. |
| `submit` | object | Yes | `id`, `title`. |
| `postSubmit` | object | No | `id`, `title`. |
| `slotDefinitionConfigId` | string | Yes | Slot definition configuration. |
| `showSubmittedContent` | boolean | No | Echo the submission. |
| `postSubmitTitle` | string | No | Title after submit. |
| `fromFirstAvailableSlot` | boolean | No | Start from the first available slot. |


**SURVEY**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `title` | string | Yes | Survey title. |
| `surveyButton` | object | Yes | `id`, `title`. |
| `description` | string | No | Survey description. |
| `postSubmit` | object | No | Post-submit button. |
| `postSubmitTitle` | string | No | Post-submit title. |
| `showSubmittedContent` | boolean | No | Echo the submission. |
| `screenReaderLabel` | string | No | Accessibility label. |


**SECURE_FORM**

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `form` | object | Yes | `formTitle` Required; `fields[]` with `name` and `type` (e.g. `EMAIL`). |
| `submit` | object | Yes | `title`; `actionDetail.postBackHandlerType: SECURE_FORM_POST_BACK`; `actionDetail.action: POST_BACK`. |


> **Dev notes.** `action: OPEN_URL` redirects the customer to the supplied URL. Bold text inside Live Chat copy is expressed as `<p style="margin: 0;1"><strong>…</strong></p>`.
Related KB articles: **Quick Replies**, **Secure Forms**.


V2 reference: [Sprinklr Live Chat Templates](https://dev.sprinklr.com/sprinklr-live-chat-templates)[doc:turn1doc7].

## 6. Response format and status codes

### 6.1 Success

Every channel example in the V3 operation documents returns the standard Sprinklr envelope with an array of created post ids:

```json
{
  "data": ["POST_593685584"],
  "errors": []
}
```

> **Discrepancy.** The OpenAPI document declares the `200` response for `POST /publishing/message` as a bare array of strings (`type: array, items: {type: string}`), not the `{data, errors}` envelope. Code that parses the response should be defensive until this is confirmed. See §10.


### 6.2 Error responses

The operation declares the shared error responses:

| Status | Name | Body |
|  --- | --- | --- |
| 400 | Bad Request | `ErrorResponse` |
| 401 | Unauthorized | `ErrorResponse` |
| 403 | Forbidden | `ErrorResponse` |
| 404 | Not Found | `ErrorResponse` |


`ErrorResponse`:

```json
{
  "data": null,
  "errors": [
    {
      "id": "5f9b2c1a4e6d7b8c9a0f1e2d",
      "code": 4001,
      "message": "account.not.found"
    }
  ],
  "metadata": {}
}
```

`Error` requires `id` (24-character hexadecimal ObjectId), `code` (integer), and `message` (dotted key, e.g. `account.not.found`).

## 7. V2 → V3 migration

### 7.1 Endpoint mapping

| V2 | V3 |
|  --- | --- |
| `https://api3.sprinklr.com/{env}/api/v2/publishing/message` | `https://api3.sprinklr.com/{env}/api/v3/publishing/message` |


The **request body and every attachment object are unchanged**. For publishing templates, migration is a path-version bump. The same six channel documents exist in both versions[doc:turn1doc1]:

| Channel | V2 page |
|  --- | --- |
| Facebook | `/facebook-dynamic-templates`[doc:turn1doc4] |
| Instagram | `/instagram-dynamic-templates`[doc:turn1doc8] |
| WhatsApp | `/whatsapp-dynamic-templates`[doc:turn1doc5] |
| Sprinklr Live Chat | `/sprinklr-live-chat-templates`[doc:turn1doc7] |
| Twitter | `/twitter-dynamic-templates` |
| Apple Business Chat | `/apple-business-chat-templates` |


### 7.2 Field mapping

No field renames, removals, or type changes are documented between V2 and V3 for the publishing-template payload. Two observations worth confirming:

- The V2 Instagram carousel example includes a per-card `defaultButton` object (`actionDetail: { url, action: OPEN_URL }`) that appears in **no** V3 example[doc:turn1doc8].
- The V2 Instagram carousel button action is `{"action": "TEXT", "data": "TEXT_POST_BACK"}`, while the V3 Facebook carousel example uses `{"action": "OPEN_URL", "url": …}`. These are channel differences, not version differences, as far as the sources show.


### 7.3 Header differences

| Header | V2 | V3 |
|  --- | --- | --- |
| `Authorization: Bearer …` | Yes | Yes |
| `Key: …` | Yes[doc:turn1doc1] | Yes (declared as `apiKeyAuth` in the spec) |
| `Cookie: JSESSIONID=…` | No | Appears in captured V3 samples; **not** part of the contract — do not send. |


### 7.4 Migration steps

1. Change `/api/v2/` to `/api/v3/` in the request URL. Keep the same `{env}`.
2. Keep sending both `Authorization` and `Key`.
3. Leave the request body as-is. Verify against §4 that `content`, `taxonomy` (with `campaignId`), and `scheduleDate` are present — the V3 schema requires all three.
4. Confirm `taxonomy.tags` is sent as an array and `clientCustomProperties` / `partnerCustomProperties` as maps of string arrays, per the schema.
5. Make response parsing tolerant of both the `{data, errors}` envelope and a bare string array (§6.1).
6. Re-test each template type per channel in a non-production `{env}` before cutting over.
7. If you relied on the Instagram `defaultButton`, confirm with the API owner whether it is still honoured.


## 8. Reference — attachment types

`Attachment` is a `oneOf` with `discriminator.propertyName: type`. Canonical V3 wire values from the discriminator mapping:

`IMAGE`, `VIDEO`, `LINK`, `DOC`, `BASE64`, `CAROUSEL`, `MULTI_MEDIA`, `GEO_LOCATION`, `CARD`, `CO_BROWSE_INVITE`, `RICH_TEXT_CAROUSEL`, `AUDIO`, `REQUEST_LOCATION`, `CTA_URL`, `ADDRESS`, `RICH_LINK`, `EVENTS`, `CONTACT_DETAILS_FORM`, `FEEDBACK_FORM`, `FORM`, `SECURE_FORM`, `APPOINTMENT`, `SURVEY`, `PRODUCT_CARD`, `PRODUCT_SHOWCASE_CARD`, `INFO_CARD`, `PRODUCT_BANNER`, `PRODUCT_LIST`, `QUICK_SELECT_PILLS`, `VOICE`, `BUTTON_TEMPLATE`, `LIST_TEMPLATE`, `POLL`, `ALBUM`, `STORY`

> **Important gotcha.** Several discriminator keys in the spec are **schema names rather than wire values**: `QuickReplyAttachment`, `AuthenticationAttachment`, `ApplePayInteractiveAttachment`, `AppLinkAttachment`, `ListPickerAttachment`, `TimePickerAttachment`, `HSMAttachment`, `ProductAttachment`, `ThreadAttachment`. The channel documents and their working examples send `QUICK_REPLY`, `AUTHENTICATION`, `APPLE_PAY`, `APP_LINK`, `LIST_PICKER`, `TIME_PICKER`. Send the documented wire values; the mapping keys look like a spec defect. See §10.


Other enums:

| Field | Values |
|  --- | --- |
| `actionDetail.action` | `TEXT`, `OPEN_URL`, `POST_BACK` |
| `actionDetail.postBackHandlerType` | `SECURE_FORM_POST_BACK` (Live Chat secure form) |
| Facebook `actionType` | `web_url`, `no_action` |
| WhatsApp header `type` | `IMAGE`, `VIDEO`, `DOC` |
| WhatsApp `urlType` | `STATIC` |
| Apple Business Chat `style` | `icon`, `small`, `large` |
| Apple Business Chat form field `type` | `Form`, `Date Picker`, `Picker`, `Select` |
| Live Chat `otherConfigs.layoutType` | e.g. `IMAGE_TOP` |
| `approval.type` (per channel docs) | `ACCOUNT_OWNER`, `USER`, `APPROVAL_PATH`, `NONE` (default `NONE`) |
| `status` | e.g. `SCHEDULED`, `APPROVAL`, `SENT` |


## 9. Use cases and Best practices

**Use cases**

- **Deflect routine contacts.** Send a Facebook or Twitter `QUICK_REPLY` with 2–5 choices so the customer self-selects an intent before an agent is engaged.
- **Show a catalogue in-thread.** Use `CAROUSEL` (Facebook, Instagram, Live Chat) or `PRODUCT_LIST` (Live Chat) with `OPEN_URL` buttons to the product page.
- **Collect structured data safely.** Use Live Chat `SECURE_FORM` or Apple Business Chat `FORM` instead of asking for details in free text.
- **Book a slot without leaving the conversation.** Live Chat `APPOINTMENT` with a `slotDefinitionConfigId`, or Apple Business Chat `TIME_PICKER`.
- **Take payment in-thread.** Apple Business Chat `APPLE_PAY` with a full `paymentRequest`.
- **Confirm a location.** WhatsApp `REQUEST_LOCATION` to ask, `GEO_LOCATION` to send one, `ADDRESS` to collect a structured address.
- **Authenticate the customer.** Apple Business Chat `AUTHENTICATION` with a configured `loginAppConfigId`.


**Best practices**

- Check the channel matrix in §1.2 before choosing a template type. Types are not portable across channels.
- Always send `taxonomy.campaignId` — it is schema-required and drives reporting attribution.
- Send `scheduleDate` explicitly in epoch milliseconds. It is schema-required even where a channel document marks it optional.
- Keep Apple Business Chat quick replies within the documented **2 to 5** choices.
- Size Apple Business Chat images to the documented styles (Icon 40×40, Small 60×60, Large 263×150) to avoid cropping.
- Set Live Chat `disableManualResponse: true` when a card must be answered by tapping a button rather than typing.
- Use `allowDuplicateMessages` deliberately. It is only exercised in the Instagram examples; assume duplicate suppression is on unless you set it.
- Retrieve the sent post with the **Read Post by Post Id** API or **Universal Search** rather than assuming the send response contains the full object.
- Test every template type in a non-production `{env}` first. QA verified the templates on **2026-08-12** against the 26.10 build; re-verify against the environment you target.
- Do not send the `Cookie: JSESSIONID=…` header seen in captured samples, and never commit tokens or API keys.