> ## Documentation Index
> Fetch the complete documentation index at: https://docs.notifique.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Send messages

> Chat PIV1 contract: from, to, type, and payload. Text, image, audio, and file.

<Tip>
  Chat send uses the **same contract** as other channels: `from`, `to`, `type`, and `payload`. The difference: `to` is the **conversation id**, not a phone number.
</Tip>

## Endpoint

```http theme={null}
POST /v1/chat/messages
Content-Type: application/json
Authorization: Bearer sk_live_xxxxx
```

Scope: **`chat:messages:send`**. Each send costs **1 credit** (`CHAT_MESSAGE`).

`POST /v1/chat/conversations/{id}/messages` still works: conversation id is in the URL and the body is the same (`type` + `payload`), without `to`.

## Fields

| Field                  | Required             | Meaning                                                  |
| ---------------------- | -------------------- | -------------------------------------------------------- |
| `from`                 | No                   | Chat App id. If set, it must match the conversation      |
| `to`                   | Yes on this endpoint | Conversation id: string or array                         |
| `type`                 | Yes                  | `text`, `image`, `audio`, or `file`                      |
| `payload.message`      | Yes if `type=text`   | Text. For media, used as caption (`caption` also works)  |
| `payload.mediaUrl`     | Yes if not text      | **HTTPS** media URL                                      |
| `senderExternalUserId` | Yes with API Key     | Who is speaking: `externalUserId` of a `USER` or `AGENT` |
| `clientMessageId`      | No                   | Idempotency: the same id returns the same message        |
| `replyToId`            | No                   | Quoted message id                                        |

With a **member JWT**, do not send `senderExternalUserId`: the sender is the token.

The response uses uppercase `type` (`TEXT`, `IMAGE`, `AUDIO`, `FILE`).

***

## Text

```json theme={null}
{
  "from": "clxxapp...",
  "to": ["cnv_xxxxx"],
  "type": "text",
  "payload": {
    "message": "Hi Alice, your order is out for delivery."
  },
  "senderExternalUserId": "agent:clxxuser...",
  "clientMessageId": "msg_text_001"
}
```

**Response (200)**

```json theme={null}
{
  "success": true,
  "idempotent": false,
  "data": {
    "id": "clxxmsg...",
    "conversationId": "cnv_xxxxx",
    "type": "TEXT",
    "body": "Hi Alice, your order is out for delivery.",
    "mediaUrl": null,
    "createdAt": "2026-09-06T03:10:00.000Z"
  }
}
```

***

## Image

```json theme={null}
{
  "from": "clxxapp...",
  "to": ["cnv_xxxxx"],
  "type": "image",
  "payload": {
    "mediaUrl": "https://cdn.yoursite.com/receipt.png",
    "message": "Delivery receipt"
  },
  "senderExternalUserId": "user_alice"
}
```

`payload.message` (or `payload.caption`) is the caption. Missing `payload.mediaUrl` → **400**.

***

## Audio

```json theme={null}
{
  "from": "clxxapp...",
  "to": ["cnv_xxxxx"],
  "type": "audio",
  "payload": {
    "mediaUrl": "https://cdn.yoursite.com/note.ogg"
  },
  "senderExternalUserId": "user_bob"
}
```

***

## File

```json theme={null}
{
  "from": "clxxapp...",
  "to": ["cnv_xxxxx"],
  "type": "file",
  "payload": {
    "mediaUrl": "https://cdn.yoursite.com/contract.pdf",
    "message": "Signed contract"
  },
  "senderExternalUserId": "agent:clxxuser..."
}
```

***

## Reply to a message

```json theme={null}
{
  "from": "clxxapp...",
  "to": ["cnv_xxxxx"],
  "type": "text",
  "payload": { "message": "Sounds good, thanks" },
  "senderExternalUserId": "agent:clxxuser...",
  "replyToId": "clxxmsg..."
}
```

`replyToId` can also go in `payload.replyToId`.

***

## Member JWT

The app authenticated with the JWT does **not** send an API Key.

```http theme={null}
POST /v1/chat/messages
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
```

```json theme={null}
{
  "to": ["cnv_xxxxx"],
  "type": "text",
  "payload": { "message": "I'm here" }
}
```

***

## Several conversations in one request

`to` accepts a list. Each id is a conversation in the same workspace.

```json theme={null}
{
  "from": "clxxapp...",
  "to": ["cnv_aaaa", "cnv_bbbb"],
  "type": "text",
  "payload": { "message": "Maintenance at 10pm" },
  "senderExternalUserId": "agent:clxxuser..."
}
```

One conversation → `data` is the message object. Several → `data` is an array.

***

## WebSocket (user app)

The socket does **not** use the PIV1 body. After `auth`:

```json theme={null}
{ "type": "message.send", "conversationId": "cnv_xxxxx", "body": "Hi", "clientMessageId": "msg_ws_1" }
```

Media on the socket: `messageType` (`image` | `audio` | `file`) and HTTPS `mediaUrl`, or `payload.type` / `payload.mediaUrl`. The frame `type` stays `message.send`.

REST (backend) → `POST /v1/chat/messages`. Realtime in the app → WebSocket.

Type outside the app ceiling or the room switch → **403** `CHAT_CAPABILITY_DISABLED`. DIRECT with a user block → **403** `CHAT_USER_BLOCKED`. Details: [Capabilities and blocking](/en/chat-api/como-funciona/capacidades-e-bloqueio).

On WebSocket, `message.send` also accepts `type` (`text` | `image` | `audio` | `file`) and `mediaUrl` (HTTPS). `recording.start` only works if the room has `audio` on.

## Next steps

* [User and agent](/en/chat-api/como-funciona/usuario-e-agente): who shows up as sender
* [Webhook events](/en/chat-api/como-funciona/eventos-do-webhooks)
* [API Key scopes](/en/chat-api/como-funciona/escopos-da-api-key)
