# Message API V3 — Developer Guide

- **Applies to:** Sprinklr Message API V3 (`/api/v3/message`, `/api/v3/conversation`)
- **V2 API reference:** [Message | Sprinklr Developer Portal](https://dev.sprinklr.com/message)


## 1. Overview

All data pulled in from digital channels is stored in Sprinklr as a standard **Universal Message** object. Messages are either **inbound** (posts coming from a channel) or **outbound** (posts to a channel from Sprinklr), and the object covers Ads, Listening, Organic, and any external data ingested into the platform. Once a message is ingested, Sprinklr automatically assigns it a unique message ID — also referred to as the **UMID**.

Message V3 exposes read, action, and update capabilities across a small, method-differentiated surface:

| Operation | Method | Path |
|  --- | --- | --- |
| Fetch messages (single, bulk, or by UM ID) | `GET` | `/api/v3/message?messageId=` or `?umId=` |
| Fetch conversation for a message | `GET` | `/api/v3/conversation?messageId=` |
| Perform an action on a message | `PATCH` | `/api/v3/message/action` |
| Mark a message as read (WhatsApp only) | `PATCH` | `/api/v3/message/read?messageId=` |
| Update message workflow, content, and attachments | `PATCH` | `/api/v3/message/workflow?messageId=` |
| Add associated application data | `POST` | `/api/v3/message/associated-app-data?messageId=` |
| Create / publish a message | — | Not part of Message V3. Use the Publishing APIs. |
| Delete a message | — | Exposed as the `DELETE` **action** on `PATCH /api/v3/message/action`, not as an HTTP `DELETE`. |


This is the central design change from V2, which spread reads across three endpoints (`/message/byMessageId`, `/message`, `/message/bulk-fetch`) and used `POST` for reads and `PUT` for partial updates.

For more details refer to [Message API Reference](/apis/sprinklr-v3/message-v3) and [Conversation API Reference](/apis/sprinklr-v3/conversation-v3).

### 1.1 The message data model

| Layer | Object | What it holds | Notes |
|  --- | --- | --- | --- |
| Identity | `messageId`, `id`, `channelMessageId`, `conversationId`, `parentMessageId`, `quotedMessageId` | Sprinklr and native identifiers | `messageId` is the UMID |
| Origin | `sourceType`, `sourceId`, `channelType`, `accountType`, `permalink`, `channelCreatedTime`, `createdTime`, `modifiedTime` | Where the message came from and when | `sourceType`, `sourceId`, `content` are **required** on the `Message` schema |
| Content | `content.title`, `content.text`, `content.richText`, `content.isRichText`, `content.attachment`, `content.templateId`, `content.postbackPayload`, `content.titleHash`, `content.textHash` | The message payload | See [§1.3](#13-the-content-object) |
| People | `senderProfile`, `receiverProfile`, `mentionedProfiles[]`, `authorId` | Profiles attached to the message | `Profile` objects |
| Workflow | `workflow.assignment`, `workflow.customProperties`, `workflow.queues[]`, `workflow.spaceWorkflows[]`, `workflow.campaignId`, `workflow.modifiedTime` | Partner and workspace-level routing and tagging state | `campaignId` is **required** on the `Workflow` schema |
| Enrichment | `enrichments`, `textEntities`, `insights`, `language`, `location`, `contextualInformation[]`, `customPropertiesMetadata[]`, `channelSpecificInfo`, `botApplicationInfo` | Derived and channel-specific metadata |  |
| Publishing / state | `draftId`, `postId`, `assetId`, `brandPost`, `deleted`, `autoImported`, `autoResponse`, `apiStatus`, `associatedCaseNumber` | Post lifecycle state | `apiStatus` reports when fields were removed under a resyndication policy |


### 1.2 Addressing a message

A message is addressed in two ways:

- **By message ID (UMID)** — `messageId`
- **By UM ID query parameter** — `umId`


**Message ID composition**:

```
messageId = sourceType + "_" + sourceId + "_" + channelCreatedTime + "_" + channelType + "_" + messageType + "_" + channelMessageId
```

Example:

```
ACCOUNT_12345678_1779725555000_WHATSAPP_BUSINESS_316_6a1474f3268905389646cce7
```

`sourceType` in that composition takes values such as `ACCOUNT`, `PERSISTENT_SEARCH`, and `LISTENING`. The full enum is in [§8.1](#81-sourcetype).

### 1.3 The `content` object

| Parameter | Type | Description |
|  --- | --- | --- |
| `title` | String | Title of the message |
| `titleHash` | String | Hashed title of the message |
| `text` | String | Text content of the message |
| `textHash` | String | Hashed text content of the message |
| `richText` | String | Rich text content of the message |
| `isRichText` | Boolean | Whether the provided text is rich text |
| `postbackPayload` | String | Postback payload of the button selected |
| `attachment` | Object | Attachment of the message to be published. Polymorphic — see below. |
| `templateId` | String | Content template ID |


`attachment` is a **discriminated union** keyed on the `type` property. Supported `type` discriminator values include `IMAGE`, `VIDEO`, `LINK`, `DOC`, `BASE64`, `CAROUSEL`, `MULTI_MEDIA`, `GEO_LOCATION`, `CARD`, `CO_BROWSE_INVITE`, `RICH_TEXT_CAROUSEL`, and `AUDIO`, among others. Always set `type` — without it the payload cannot be resolved to a concrete attachment schema.

## 2. Base URLs and environments

**All API calls are sent to the production endpoint:**

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

So the message resource in production is:

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

Replace `{env}` with your assigned environment identifier. The OpenAPI document enumerates: `prod` (default), `prod0`, `prod2`, `prod3`, `prod4`, `prod5`, `prod6`, `prod8`, `prod11`, `prod12`, `prod15`, `prod16`, `prod17`, `prod18`, `prod19`, `prod21`, `prod25`. See [APIs | Sprinklr Developer Portal](https://dev.sprinklr.com/apis) for the current environment list.

## 3. Authentication and common headers

All Message 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` | `Bearer {{accessToken}}` | Authenticates the user with the server | All requests |
| `Key` | `{{apiKey}}` | Authenticates the application with the server | All requests |
| `Content-Type` | `application/json` | Declares the request body media type | `POST`, `PATCH` |
| `Accept` | `application/json` | Declares the acceptable response type | All requests |


**Permissions:** Sprinklr's API governance model derives permissions from the Sprinklr user tied to the API key. Account permission is required to view content from, engage with, or publish from specific accounts. Missing permission returns `403 Forbidden`.

## 4. Write operations

### 4.1 `PATCH /api/v3/message/action` — Perform an action on a message

**`operationId`:** `MessageApiV3_performMessageAction`
**Summary:** Performs the provided action on the given message

Replaces `POST /api/v2/message/action`, which carried `action`, `messageId`, and `accountId` in the request body.

#### Query parameters

| Parameter | Type | Required | Description | Example |
|  --- | --- | --- | --- | --- |
| `messageId` | String | Required | Message ID | `ACCOUNT_1000070103_1649479490272_TWITTER_5_1512652734423512131` |
| `accountId` | String | Required | Account ID | `600039042` |
| `action` | String | Required | Action to perform | `HIDE`, `LIKE`, `DELETE` |


#### Supported actions

`HIDE`, `UNHIDE`, `LIKE`, `UNLIKE`, `FAVORITE`, `UNFAVORITE`, `DELETE`, `APPROVE`, `REJECT`.

**These actions are specific to native channel types.** A channel that has no concept of "favorite" will not honour `FAVORITE`.

#### Request

```bash
curl --location --request PATCH \
  'https://api3.sprinklr.com/{env}/api/v3/message/action?messageId=ACCOUNT_1000070103_1649479490272_TWITTER_5_1512652734423512131&accountId=600039042&action=HIDE' \
  --header 'Authorization: Bearer {{accessToken}}' \
  --header 'Key: {{apiKey}}' \
  --header 'Accept: application/json'
```

The `200` response is typed as a plain **string** in the OpenAPI document.

### 4.2 `PATCH /api/v3/message/read` — Mark a message as read

**`operationId`:** `MessageApiV3_notifyMessageRead`
**Summary:** Mark Message as Read (Supported for WhatsApp only)

Replaces `POST /api/v2/message/notify-read?messageId={id}`. The V2 path segment `notify-read` is renamed to `read`.

#### Query parameters

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `messageId` | String | Required | Message ID |


#### Request

```bash
curl --location --request PATCH \
  'https://api3.sprinklr.com/{env}/api/v3/message/read?messageId=ACCOUNT_12345678_1779725555000_WHATSAPP_BUSINESS_316_6a1474f3268905389646cce7' \
  --header 'Authorization: Bearer {{accessToken}}' \
  --header 'Key: {{apiKey}}' \
  --header 'Accept: application/json'
```

#### What this endpoint does

This endpoint surfaces Meta's *Mark message as read* capability. Sprinklr already invokes Meta's read API when an agent opens the Care console; this endpoint exists so that integrations whose agents are **not** in Sprinklr can trigger the same signal.

**Expected end result:** the customer sees that the brand has read the message — the WhatsApp blue tick.

**Channel support:** WhatsApp only. Calling this for any other channel is not supported.

### 4.3 `PATCH /api/v3/message/workflow` — Update workflow, content, and attachments

**`operationId`:** `MessageApiV3_updateMessageWorkflowWithAttachments`
**Summary:** Update message workflow with optional content/attachments (same content shape as source-agnostic send)

Replaces `PUT /api/v2/message/workflow`. The method is corrected from `PUT` to **`PATCH`**, because this is a partial update of workflow properties, not a full replacement of the message.

#### Query parameters

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `messageId` | String | Optional* | Message ID |
| `umId` | String | Optional* | Universal Message ID |


* Supply exactly one. The V2 `messageIds` **array in the body** is replaced by a **query parameter** — consistent with how other V3 endpoints handle ID lookup.

#### Request body — `MessageWorkflowWithContentDTO`

| Parameter | Type | Description |
|  --- | --- | --- |
| `assignment` | Object | Assignment details. See sub-table below. |
| `customProperties` | Map[String → Array[String]] | Partner custom properties — **replaces** the listed properties |
| `addedCustomProperties` | Map[String → Array[String]] | Partner custom properties to **add** |
| `removedCustomProperties` | Map[String → Array[String]] | Partner custom properties to **remove** |
| `clientCustomProperties` | Map[String → Array[String]] | Workspace (client) custom properties — **replaces** |
| `addedClientCustomProperties` | Map[String → Array[String]] | Workspace custom properties to **add** |
| `removedClientCustomProperties` | Map[String → Array[String]] | Workspace custom properties to **remove** |
| `addedQueues` | Array[Long] | Partner queue IDs to add |
| `removedQueues` | Array[Long] | Partner queue IDs to remove |
| `addedClientQueues` | Array[Long] | Workspace queue IDs to add |
| `removedClientQueues` | Array[Long] | Workspace queue IDs to remove |
| `sentiment` | Integer (int32) | Sentiment value on the message |
| `note` | String | Note to attach to the message |
| `message` | String | Message text |
| `content` | Object | Full `Content` object — text, title, rich text, attachment, template. See [§1.3](#13-the-content-object). |
| `clearAttachments` | Boolean | If `true`, clears existing attachments on the message |
| `appendAttachments` | Boolean | If `true`, appends rather than replaces attachments |


**`assignment` sub-parameters:**

| Parameter | Type | Description |
|  --- | --- | --- |
| `assigneeId` | String | ID of the assignee |
| `assigneeType` | String | Assignee type, for example `USER`, `BOT` |
| `assignedById` | Long | ID of the user who assigned |
| `assignmentTime` | Long | Assignment time (epoch ms) |


Custom properties are keyed by custom field ID (for example `_c_64dcb892e32de6530b5a8dbf`) and always take a **list** of values, even for single-valued fields.

#### Request — custom properties

```bash
curl --location --request PATCH \
  'https://api3.sprinklr.com/{env}/api/v3/message/workflow?messageId=ACCOUNT_1000070103_1649479490272_TWITTER_5_1512652734423512131' \
  --header 'Authorization: Bearer {{accessToken}}' \
  --header 'Key: {{apiKey}}' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data '{
    "addedCustomProperties": {
        "_c_64dcb892e32de6530b5a8dbf": ["escalated"]
    },
    "removedCustomProperties": {
        "_c_6512721b83353e6f3e80c1c5": ["pending-triage"]
    },
    "addedQueues": [17],
    "sentiment": 1,
    "note": "Escalated after second customer follow-up."
}'
```

#### Request — update message text

```bash
curl --location --request PATCH \
  'https://api3.sprinklr.com/{env}/api/v3/message/workflow?messageId={messageId}' \
  --header 'Authorization: Bearer {{accessToken}}' \
  --header 'Key: {{apiKey}}' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data '{
    "content": {
        "text": "Updated response text.",
        "isRichText": false
    }
}'
```

#### Request — clear attachments

```bash
curl --location --request PATCH \
  'https://api3.sprinklr.com/{env}/api/v3/message/workflow?messageId={messageId}' \
  --header 'Authorization: Bearer {{accessToken}}' \
  --header 'Key: {{apiKey}}' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data '{
    "clearAttachments": true
}'
```

#### Custom-property operation mapping (V2 → V3)

| V2 (`PUT /api/v2/message/workflow`) | V3 (`PATCH /api/v3/message/workflow`) — OpenAPI field | Semantics |
|  --- | --- | --- |
| `customProperties` action | `customProperties` | Replace the listed properties |
| `customPropertiesToAdd` action | `addedCustomProperties` | Add values without removing existing ones |
| `customPropertiesToRemove` action | `removedCustomProperties` | Remove the listed values |


### 4.4 `POST /api/v3/message/associated-app-data` — Add associated application data

**`operationId`:** `MessageApiV3_addAssociatedAppData`
**Summary:** Add Associated Application Data

Internal CRM integration endpoint to associate external application data with a message.This endpoint is primarily for internal CRM integrations and system-to-system communications.

#### Query parameters

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `messageId` | String | Yes | Message ID |


#### Request body — `AssociatedApplicationData` (required)

| Parameter | Type | Required | Description |
|---|---|---|
| `applicationType` | String | Required | Type of the external application |
| `applicationId` | String | Required | ID of the external application |
| `applicationUserId` | String | User ID within the external application |
| `applicationMessageType` | String | Optional | Message type within the external application |
| `applicationMessageId` | String | Required | Message ID within the external application |
| `applicationMessageNumber` | String | Optional | Message number within the external application |
| `applicationParentMessageId` | String | Optional | Parent message ID within the external application |
| `applicationParentMessageNumber` | String | Optional | Parent message number within the external application |
| `associationTime` | Long (int64) | Optional | Association time (epoch ms) |
| `applicationOrganizationId` | String | Optioanl | Organization ID within the external application |

#### Request

```bash
curl --location --request POST \
  'https://api3.sprinklr.com/{env}/api/v3/message/associated-app-data?messageId={messageId}' \
  --header 'Authorization: Bearer {{accessToken}}' \
  --header 'Key: {{apiKey}}' \
  --header 'Content-Type: application/json' \
  --header 'Accept: application/json' \
  --data '{
  "applicationType": "SALESFORCE",
  "applicationId": "00D3h000007R1LGEA0",
  "applicationUserId": "005xx000001X8UzAAK",
  "applicationMessageType": "Case",
  "applicationMessageId": "500xx000004TCxbAAG",
  "applicationMessageNumber": "00001234",
  "applicationParentMessageId": "500xx000004TCxaAAG",
  "applicationParentMessageNumber": "00001233",
  "associationTime": 1649479990000,
  "applicationOrganizationId": "00D3h000007R1LGEA0"
}'
```

### 4.5 Method comparison — when to use which

|  | `GET /message` | `GET /conversation` | `PATCH /message/action` | `PATCH /message/read` | `PATCH /message/workflow` | `POST /message/associated-app-data` |
|  --- | --- | --- | --- | --- | --- | --- |
| Purpose | Fetch one or many messages | Fetch a message thread | Act on a message on the native channel | Signal read receipt | Change workflow, content, attachments | Attach external app metadata |
| Addressing | `messageId` or `umId` | `messageId` | `messageId` + `accountId` | `messageId` | `messageId` or `umId` | `messageId` |
| Body | None | None | None | None | `MessageWorkflowWithContentDTO` | `AssociatedApplicationData` |
| Channel constraints | None | None | Action support is channel-specific | **WhatsApp only** | None | None |
| Bulk | Yes — comma-separated IDs | No | No | No | No | No |


## 5. Read operations

### 5.1 `GET /api/v3/message` — Fetch messages

**`operationId`:** `MessageApiV3_getMessages`
**Summary:** Fetch Messages by UM ID or Message Id. Supports bulk fetch.

This single endpoint replaces **three** V2 reads:

| V2 endpoint | V3 equivalent |
|  --- | --- |
| `GET /api/v2/message/byMessageId?messageId={id}` | `GET /api/v3/message?messageId={id}` |
| `GET /api/v2/message?id={umid}` | `GET /api/v3/message?umId={umid}` |
| `POST /api/v2/message/bulk-fetch` with a body array | `GET /api/v3/message?messageId={id1,id2,id3}` |


#### Query parameters

Choose **one** of the following methods to retrieve messages:

**Method 1: Fetch by Message ID**

Retrieve a single message using Sprinklr's internal message ID.

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `messageId` | String | Required | The unique Sprinklr message identifier. **Example:** `64e76b8ad9a9d946c006ca5f` |


**Method 2: Fetch by UMID**

Retrieve a message using its Universal Message ID (UMID).

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `umid` | String | Required | The Universal Message ID in the format: `sourceType_sourceId_timestamp_channelType_messageType_channelMessageId`  **Example:** `ACCOUNT_600056911_1742986039000_FACEBOOK_470_600275313172150` |


**Method 3: Bulk Fetch Messages**

Retrieve multiple messages at once (up to 50).

| Parameter | Type | Required | Description |
|  --- | --- | --- | --- |
| `messageIds` | String | Required | Comma-separated list of message IDs (maximum 50).  **Example:** `64e76b8ad9a9d946c006ca5f,64e76b8ad9a9d946c006ca60,64e76b8ad9a9d946c006ca61` |


> ⚠️ **Important**
Use only **ONE** of the above methods per request. Mixing parameters will result in a **400 Bad Request** error.


#### Request — single message

```bash
curl --location 'https://api3.sprinklr.com/{env}/api/v3/message?messageId=ACCOUNT_1000070103_1649479490272_TWITTER_5_1512652734423512131' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json'
```

#### Request — bulk fetch

```bash
# Up to 50 comma-separated message IDs in one call
curl --location 'https://api3.sprinklr.com/{env}/api/v3/message?messageId=ACCOUNT_1000070103_1649479490272_TWITTER_5_1512652734423512131,ACCOUNT_1000070103_1649437883290_TWITTER_5_1512478222092582025' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json'
```

#### Request — by UM ID

```bash
curl --location 'https://api3.sprinklr.com/{env}/api/v3/message?umId=69c17f4c82492af653b27af2' \
--header 'Authorization: Bearer {{accessToken}}' \
--header 'Key: {{apiKey}}' \
--header 'Accept: application/json'
```

**Response**

```json
{
  "id": "64e76b8ad9a9d946c006ca5f",
  "umid": "ACCOUNT_600056911_1742986039000_FACEBOOK_470_600275313172150",
  "text": "Need help with my order #12345",
  "channelType": "FACEBOOK",
  "messageType": "INBOUND",
  "senderName": "John Doe",
  "senderId": "facebook_user_123",
  "conversationId": "conv_abc123def456",
  "sentiment": "NEGATIVE",
  "language": "en",
  "createdTime": 1742986039000,
  "modifiedTime": 1742986039000,
  "customProperties": {
    "orderNumber": "12345",
    "productCategory": "Electronics"
  }
}
```

### 5.2 `GET /api/v3/conversation` — Fetch a message's conversation

**`operationId`:** `ConversationApiV3_getProfileConversations`
**Summary:** Fetch conversations (paginated)

Replaces [Message Conversations](https://dev.sprinklr.com/message-conversations) (`POST /api/v2/message/conversations`). The method changes from `POST` to `GET` and every parameter moves from the request body to the query string — semantically correct, because this is a read.

The endpoint is **dual-mode** and is shared with the Profile API:

- **Message conversations** — supply `messageId`. This is the Message-relevant mode.
- **Profile conversations** — supply `channelType` + `channelId`.


#### Query parameters

| Parameter | Type | Required | Description | Example |
|  --- | --- | --- | --- | --- |
| `messageId` | String | Required for message conversations | Message ID | `ACCOUNT_1000070103_1649479490272_TWITTER_5_1512652734423512131` |
| `channelType` | String | Required for profile conversations | Channel type | `TWITTER`, `FACEBOOK` |
| `channelId` | String | Required for profile conversations | Channel ID | `1647140850` |
| `sinceTime` | String | Optional | Since time lower bound (epoch ms) | `1581172111000` |
| `untilTime` | String | Optional | Until time upper bound (epoch ms) | `1649826760000` |
| `pageNumber` | String | Optional | 0-based page index; offset is `pageNumber * pageSize` | `0`, `1`, `2` |
| `pageSize` | String | Optional | Page size / rows | `20`, `50` |
| `sourceType` | String | Optional | `SourceType` enum name. See [§8.1](#81-sourcetype). | `ACCOUNT` |
| `sortKey` | String | Optional | Message field name, **case-sensitive** | `createdTime` |
| `sortOrder` | String | Optional | Sort order | `ASC`, `DESC` |
| `parentSnMsgId` | String | Optional | Parent message ID for message conversation filtering | `1647140850_902522372057571430` |
| `msgTypes` | String | Optional | Comma-separated message **type integers** for filtering | `5`, `5,7` |


**Supported `sortKey` values**: `associatedCaseNumber`, `channelCreatedTime`, `channelMessageId`, `createdTime`, `modifiedTime`, `parentMessageId`. Omit `sortKey` for the default.

#### Request

```bash
curl -X GET \
  'https://api3.sprinklr.com/{env}/api/v3/conversation?messageId=ACCOUNT_1000070103_1649479490272_TWITTER_5_1512652734423512131&sinceTime=1581172111000&untilTime=1649826760000&pageNumber=0&pageSize=20&sortKey=createdTime&sortOrder=DESC' \
  -H 'Authorization: Bearer {{accessToken}}' \
  -H 'Key: {{apiKey}}' \
  -H 'Accept: application/json'
```

#### Filtering by message type

`msgTypes` takes **comma-separated integers**, not names. The commonly used codes include:

| Channel | Message type | Code |
|  --- | --- | --- |
| Twitter | `REC_DM_TYPE_CODE` | `5` |
| Twitter | `REPLY_TYPE_CODE` | `7` |
| Twitter | `SENT_REPLY_TYPE_CODE` | `11` |
| Facebook | `FB_COMMENT_TYPE_CODE` | `14` |
| Facebook | `FB_POST_TYPE_CODE` | `15` |
| Facebook | `FB_PRIVATE_MESSAGE_TYPE_CODE` | `38` |
| Instagram | `INSTAGRAM_COMMENT_TYPE_CODE` | `37` |
| Instagram | `INSTAGRAM_DIRECT_MESSAGE` | `320` |
| YouTube | `YT_COMMENTS_TYPE_CODE` | `44` |
| LinkedIn | `LINKED_IN_POST_TYPE_CODE` | `16` |
| Email | `EMAIL` | `175` |
| WhatsApp Business | `WHATSAPP_BUSINESS` | `316` |


```bash
# Twitter DMs and replies only
curl -X GET \
  'https://api3.sprinklr.com/{env}/api/v3/conversation?messageId={messageId}&msgTypes=5,7' \
  -H 'Authorization: Bearer {{accessToken}}' \
  -H 'Key: {{apiKey}}' \
  -H 'Accept: application/json'
```

The complete code table — Twitter, Facebook, Instagram, YouTube, LinkedIn, TikTok, Weibo, VK, Xing, Line, WeChat, and message sub-type codes — is published on the [V2 Message reference page](https://dev.sprinklr.com/message).

**Mutual exclusivity:** provide either `messageId` OR (`channelType` + `channelId`), not both.

## 6. Response format and status codes

### 6.1 The V3 envelope

Per IN-12955, the response envelope `{ "data": {...}, "errors": [] }` is **consistent across all endpoints** — and was already present in V2 for most endpoints. The `APIResponse` schema in the OpenAPI document defines:

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

| Field | Type | Description |
|  --- | --- | --- |
| `data` | Object / Array | The payload — message objects, conversation messages, or an action result string |
| `errors` | Array[Error] | Array of error objects (empty if no errors) |
| `metadata` | Object | Pagination and response metadata |


### 6.2 Response codes

The OpenAPI document declares the same response set on **every** Message V3 and Conversation V3 operation:

| HTTP Code | Scenario | Description |
|  --- | --- | --- |
| `200 OK` | Success | Request completed. `GET /message` returns message objects; the `PATCH` and `POST` operations return a string. |
| `400 Bad Request` | Invalid parameters | Missing required parameters, invalid parameter combinations, or invalid parameter types |
| `401 Unauthorized` | Authentication failed | Invalid or missing `Authorization` token |
| `403 Forbidden` | Insufficient permissions | The user behind the API key lacks permission on the account or entity |
| `404 Not Found` | No message found | No message matches the supplied identifier |


## 7. V2 → V3 migration

### 7.1 Endpoint mapping

#### Read

| What you want to do | V2 | V3 |
|  --- | --- | --- |
| Fetch message by message ID | `GET /api/v2/message/byMessageId?messageId={id}` | `GET /api/v3/message?messageId={id}` |
| Fetch message by UMID | `GET /api/v2/message?id={umid}` | `GET /api/v3/message?umId={umid}` |
| Fetch multiple messages in bulk | `POST /api/v2/message/bulk-fetch` with body array | `GET /api/v3/message?messageId={id1,id2,id3}` — up to 50, comma-separated |


#### Conversations

| What you want to do | V2 | V3 |
|  --- | --- | --- |
| Fetch all conversation messages for a message | `POST /api/v2/message/conversations` with `messageId` in body | `GET /api/v3/conversation?messageId={id}` |
| Filter conversations by date range | `POST` — `sinceDate` + `untilDate` in body | `GET /api/v3/conversation?messageId={id}&sinceTime={epoch}&untilTime={epoch}` |
| Filter conversations by message type | `POST` — `msgTypes` in body | `GET /api/v3/conversation?messageId={id}&msgTypes={type}` — comma-separated |
| Paginate conversations | `POST` — `start` + `rows` in body | `GET /api/v3/conversation?messageId={id}&pageNumber={n}&pageSize={n}` |


#### Actions

| What you want to do | V2 | V3 |
|  --- | --- | --- |
| Perform an action (`HIDE`, `UNHIDE`, `LIKE`, `UNLIKE`, `FAVORITE`, `UNFAVORITE`, `DELETE`, `APPROVE`, `REJECT`) | `POST /api/v2/message/action` with `action`, `messageId`, `accountId` in body | `PATCH /api/v3/message/action` with `action`, `messageId`, `accountId` |
| Mark message as read (WhatsApp only) | `POST /api/v2/message/notify-read?messageId={id}` | `PATCH /api/v3/message/read?messageId={id}` |


#### Update

| What you want to do | V2 | V3 |
|  --- | --- | --- |
| Replace all custom properties on a message | `PUT /api/v2/message/workflow` — `customProperties` action | `PATCH /api/v3/message/workflow?messageId={id}` — `customProperties` |
| Add / merge custom properties on a message | `PUT /api/v2/message/workflow` — `customPropertiesToAdd` action | `PATCH /api/v3/message/workflow?messageId={id}` — `addedCustomProperties` |
| Remove specific custom properties from a message | `PUT /api/v2/message/workflow` — `customPropertiesToRemove` action | `PATCH /api/v3/message/workflow?messageId={id}` — `removedCustomProperties` |
| Update message text content | `PUT /api/v2/message/workflow` — `message` field in body | `PATCH /api/v3/message/workflow?messageId={id}` — `content.text` |


*(The story description expresses the last four rows as `op: replace` / `op: add` / `op: remove` and a `text` field. See the conflict callouts in [§4.3](#43-patch-apiv3messageworkflow--update-workflow-content-and-attachments).)*

### 7.2 Key structural changes

- Three separate read endpoints (`/byMessageId`, `/`, `/bulk-fetch`) consolidated into one `GET /api/v3/message` using query params
- Conversations moves from `POST` with body params to `GET` with query params — semantically correct since it is a read operation
- `notify-read` renamed to `/read` — cleaner and more consistent
- `PUT /message/workflow` corrected to `PATCH` — since it is a partial update of workflow properties, not a full replacement
- Three custom property actions (`customProperties`, `customPropertiesToAdd`, `customPropertiesToRemove`) map to standard `op: replace`, `op: add`, `op: remove` operations — consistent with SCIM PATCH semantics
- `messageIds` array in workflow update moves to a `message_id` query parameter — consistent with how other V3 endpoints handle ID lookup


### 7.3 Parameter renames

| V2 | V3 | Where |
|  --- | --- | --- |
| `id` (UMID) | `umId` | `GET /api/v3/message` |
| `sinceDate` | `sinceTime` | `GET /api/v3/conversation` |
| `untilDate` | `untilTime` | `GET /api/v3/conversation` |
| `start` | `pageNumber` (`pageNumber = start / rows`) | `GET /api/v3/conversation` |
| `rows` | `pageSize` | `GET /api/v3/conversation` |
| `sort.key` | `sortKey` | `GET /api/v3/conversation` |
| `sort.order` | `sortOrder` | `GET /api/v3/conversation` |
| `messageIds` (body array) | `messageId` query parameter | `PATCH /api/v3/message/workflow` |
| `customPropertiesToAdd` | `addedCustomProperties` | `PATCH /api/v3/message/workflow` |
| `customPropertiesToRemove` | `removedCustomProperties` | `PATCH /api/v3/message/workflow` |


Note the naming inversion on custom properties: V2 used a `customProperties…` suffix pattern, V3 uses an `…CustomProperties` prefix pattern. A find-and-replace migration will not work.

### 7.4 Method changes — the migration trap

Three of the five V2 write/read operations change HTTP method:

| Operation | V2 method | V3 method |
|  --- | --- | --- |
| Message conversations | `POST` | `GET` |
| Message action | `POST` | `PATCH` |
| Mark as read | `POST` | `PATCH` |
| Workflow update | `PUT` | `PATCH` |
| Bulk fetch | `POST` | `GET` |


Every one of these also moves parameters **out of the request body and into the query string**. A client that only swaps the base URL from `/api/v2` to `/api/v3` will fail on all five. Note also that V2 Message Conversations documented `Content-Type: multipart/form-data` — V3 sends no body at all for that call.

### 7.5 Migration steps

1. **Update the base URL.** Point at `https://api3.sprinklr.com/{env}/api/v3`.
2. **Collapse your three read paths into one.** Route `byMessageId`, UMID, and bulk-fetch traffic to `GET /api/v3/message` with `messageId` or `umId`.
3. **Batch your reads.** Replace bulk-fetch body arrays with comma-separated `messageId` values, up to 50 per call.
4. **Convert conversations from `POST` to `GET`.** Move `messageId`, `sinceDate`→`sinceTime`, `untilDate`→`untilTime`, `start`→`pageNumber`, `rows`→`pageSize`, `msgTypes`, and `parentSnMsgId` into the query string.
5. **Recalculate pagination.** `pageNumber = start / rows`, `pageSize = rows`. V2 defaulted `start` to `0` and `rows` to `21`; do not assume V3 keeps a `21` default — send `pageSize` explicitly.
6. **Change action and read calls to `PATCH`** and move `action` / `messageId` / `accountId` into the query string.
7. **Change workflow updates from `PUT` to `PATCH`**, move `messageIds` from the body to the `messageId` query parameter, and rename the custom-property maps per [§7.3](#73-parameter-renames).
8. **Send `sortKey` and `sortOrder` explicitly** rather than relying on a default — the sources disagree on what the default is.
9. **Update response parsing.** Read through `response.data` and check `response.errors` for the structured `{id, code, message}` error objects.
10. **Re-verify the three known issues** in [§10](#10-caveats-and-best-practices) against your own environment before cutover.


## 8. Supported enums

### 8.1 `SourceType`

Applies to the `sourceType` query parameter on `GET /api/v3/message` and `GET /api/v3/conversation`, and to the `sourceType` field on the `Message` object.

| Value |
|  --- |
| `ACCOUNT` |
| `PERSISTENT_SEARCH` |
| `LISTENING` |
| `BENCHMARKING` |
| `AUDIENCE` |
| `AUDIENCE_STUDY` |
| `FEED_DATA` |
| `ADVOCACY` |
| `EXTERNAL` |
| `SPRINKLR_COMMERCE` |
| `WHATSAPP_COMMERCE` |
| `SELF_SERVE` |
| `HULK` |
| `STORY_MESSAGE` |


### 8.2 Message actions

`HIDE`, `UNHIDE`, `LIKE`, `UNLIKE`, `FAVORITE`, `UNFAVORITE`, `DELETE`, `APPROVE`, `REJECT` — subject to native channel support.

### 8.3 Attachment `type` discriminator

`IMAGE`, `VIDEO`, `LINK`, `DOC`, `BASE64`, `CAROUSEL`, `MULTI_MEDIA`, `GEO_LOCATION`, `CARD`, `CO_BROWSE_INVITE`, `RICH_TEXT_CAROUSEL`, `AUDIO`, and further values including quick reply, authentication, Apple Pay interactive, app link, list picker, location request, CTA URL, address, time picker, rich link, events, contact details form, feedback, form, secure form, appointment, survey, product card, product showcase card, info card, product banner, product list, quick select pills, voice, HSM, button template, list template, product, poll, album, story, and thread attachments.

### 8.4 Sort keys (`GET /api/v3/conversation`)

`associatedCaseNumber`, `channelCreatedTime`, `channelMessageId`, `createdTime`, `modifiedTime`, `parentMessageId`. **Case-sensitive.**

## 9. Use cases

### 9.1 Hydrate a batch of messages from a webhook feed

**Scenario:** a webhook delivers 40 message IDs; you need text, sender, and workflow state for a dashboard.

Use bulk fetch rather than 40 individual calls:

```
GET /api/v3/message?messageId=id1,id2,id3,...
```

Stay at or below 50 IDs per call. In V2 this required `POST /api/v2/message/bulk-fetch` with a body array; in V3 it is a plain `GET` and is cacheable.

### 9.2 Reconstruct a full customer thread from one inbound message

**Scenario:** an agent tool receives one message and needs the surrounding conversation, newest first.

```
GET /api/v3/conversation?messageId={messageId}&pageNumber=0&pageSize=50&sortKey=createdTime&sortOrder=DESC
```

Send `sortKey` and `sortOrder` explicitly

### 9.3 Pull only direct messages from a Twitter thread

**Scenario:** you need DMs only, excluding public replies and retweets.

```
GET /api/v3/conversation?messageId={messageId}&msgTypes=5
```

`msgTypes` takes integer codes, not names. `5` is `REC_DM_TYPE_CODE` for Twitter. Combine codes with commas: `msgTypes=5,7`.

### 9.4 Backfill a conversation over a fixed window

**Scenario:** a nightly job reconciles the last 24 hours of a thread.

```
GET /api/v3/conversation?messageId={messageId}&sinceTime={epochMsStart}&untilTime={epochMsEnd}&pageNumber=0&pageSize=50
```

Both bounds are **epoch milliseconds**. In V2 these were `sinceDate` and `untilDate` in the `POST` body.

### 9.5 Moderate an incoming comment

**Scenario:** an automated policy detects an abusive comment and must hide it on the native channel.

```
PATCH /api/v3/message/action?messageId={messageId}&accountId={accountId}&action=HIDE
```

Action availability is channel-specific. Handle the `400` path explicitly — an invalid action type returns an error.

### 9.6 Signal a WhatsApp read receipt from an external agent desk

**Scenario:** agents work in your own console, not in Sprinklr, and the customer must still see the blue tick.

```
PATCH /api/v3/message/read?messageId={whatsappMessageId}
```

Sprinklr fires Meta's read API when an agent opens the Care console, and this endpoint provides the same signal for integrations whose agents are outside Sprinklr. Verify the error contract first — see the `400` vs `404` note in [§4.2](#42-patch-apiv3messageread--mark-a-message-as-read).

### 9.7 Route and tag a message from an external triage engine

**Scenario:** a classifier assigns a queue, sets sentiment, adds a tag, and clears an obsolete tag — in one call.

```bash
curl --location --request PATCH \
  'https://api3.sprinklr.com/{env}/api/v3/message/workflow?messageId={messageId}' \
  --header 'Authorization: Bearer {{accessToken}}' \
  --header 'Key: {{apiKey}}' \
  --header 'Content-Type: application/json' \
  --data '{
    "addedQueues": [17],
    "sentiment": -1,
    "addedCustomProperties": { "_c_64dcb892e32de6530b5a8dbf": ["billing-dispute"] },
    "removedCustomProperties": { "_c_64dcb892e32de6530b5a8dbf": ["unclassified"] },
    "assignment": { "assigneeId": "4471", "assigneeType": "USER" }
  }'
```

One `PATCH` covers queue, sentiment, tagging, and assignment. No read-modify-write cycle is needed — the `added…` / `removed…` maps are additive and subtractive respectively.

### 9.8 Strip an attachment that should never have been published

**Scenario:** an attachment on an outbound message violates policy and must be removed while the text stays.

```
PATCH /api/v3/message/workflow?messageId={messageId}
{ "clearAttachments": true }
```

### 9.9 Correlate a Sprinklr message with a CRM record

**Scenario:** your CRM creates a ticket for an inbound message and you need the linkage stored on the Sprinklr side.

```
POST /api/v3/message/associated-app-data?messageId={messageId}
```

with `applicationType`, `applicationId`, `applicationMessageId`, and `applicationOrganizationId` in the body ([§4.4](#44-post-apiv3messageassociated-app-data--add-associated-application-data)). This endpoint has no V2 counterpart in the IN-12955 mapping and is documented from the specification only.

### 9.10 Page through a long conversation

```javascript
let pageNumber = 0;
const pageSize = 50;
let all = [];

while (true) {
  const res = await get(
    `/api/v3/conversation?messageId=${messageId}` +
    `&pageNumber=${pageNumber}&pageSize=${pageSize}` +
    `&sortKey=createdTime&sortOrder=ASC`
  );

  if (res.errors && res.errors.length) {
    handleErrors(res.errors);        // V3 returns {id, code, message} objects
    break;
  }

  const batch = res.data || [];
  all = all.concat(batch);

  // ResponseMetadata has no declared properties in the supplied spec —
  // do not depend on metadata.hasMore until confirmed. Stop on a short page.
  if (batch.length < pageSize) break;
  pageNumber += 1;
}
```

## 10. Caveats and best practices

**Method and shape**

- Every V2 write on this surface changes method in V3. `POST`→`PATCH` for action and read, `PUT`→`PATCH` for workflow, `POST`→`GET` for conversations and bulk fetch.
- Parameters move from the body to the query string on all of those.
- `PATCH /message/action`, `/read`, and `/workflow` return a **plain string** on `200`, not an object.


**Identifiers**

- `messageId` is the UMID and is composite: `sourceType_sourceId_channelCreatedTime_channelType_messageType_channelMessageId`.
- URL-encode message IDs containing reserved characters.
- Supply exactly one of `messageId` or `umId` — not both.
- Every ID-bearing query parameter is declared `required: false` in the specification. That is a specification looseness, not permission to omit it; QA confirmed that missing required fields return a validation error.


**Bulk fetch**

- Limit `messageId` lists to **50** comma-separated IDs per call.
- Prefer one bulk call over N single calls.


**Pagination**

- Pages are **0-based**; offset is `pageNumber * pageSize`.
- V2 defaulted `rows` to `21` and `start` to `0`. Do not carry that assumption into V3 — send `pageSize` explicitly.
- `ResponseMetadata` has no declared properties in the supplied specification; do not depend on `metadata.hasMore` until confirmed. Stop paging on a short page.


**Sorting**

- `sortKey` is **case-sensitive** and limited to `associatedCaseNumber`, `channelCreatedTime`, `channelMessageId`, `createdTime`, `modifiedTime`, `parentMessageId`.
- Always send `sortKey` and `sortOrder`; the sources disagree on the default.


**Channel constraints**

- `PATCH /api/v3/message/read` is **WhatsApp only**.
- Message actions are specific to native channel types — a channel that does not support an action will not honour it.
- `msgTypes` takes **integer codes**, not names. The full table is on the [V2 Message reference](https://dev.sprinklr.com/message).


**Content and attachments**

- `content.attachment` is a discriminated union — always set `type`.
- `clearAttachments` and `appendAttachments` are booleans on the workflow `PATCH`, and were the subject of IN-13336. Verify both in your environment.


**Data completeness**

- `apiStatus` on the `Message` object reports when fields were removed under a **resyndication policy** — a message can return `200` with fields stripped. Check `apiStatus` before treating a payload as complete.
- Twitter syndication, Case Compliance, and Listening data require explicit enablement requests to Sprinklr Support. A `403` or an empty payload for those data sets may be an entitlement gap, not a client bug.


**Error handling**

- `errors[]` entries carry `id` (24-character hex ObjectId), `code` (integer), and `message` (dotted key such as `account.not.found`). Match on `message` keys, not on prose.
- Always inspect `errors` even on a `200`.


*All JSON payloads in this guide are illustrative examples. They are not real customer data and are not guaranteed production responses. All credentials are placeholders (`{{accessToken}}`, `{{apiKey}}`) and must never be committed or logged.*