## Send a message to the client

`POST /v1/openlines/crm/messages`

Sends a message to the Open Channel chat linked to a CRM card. The message reaches the client in their messenger on behalf of the specified employee or bot.

**Important:** a real client receives the message. Before calling, check that `chatId` belongs to the right dialog.

## Request fields (body)

| Field | Type | Required | Description |
|------|-----|:-----:|----------|
| `crmEntityType` | string | yes | CRM card type: `lead`, `deal`, `company` or `contact`. Case-insensitive |
| `crmEntityId` | number | yes | CRM card ID. Lists: [`GET /v1/leads`](/docs/entities/leads/list), [`GET /v1/deals`](/docs/entities/deals/list), [`GET /v1/companies`](/docs/entities/companies/list), [`GET /v1/contacts`](/docs/entities/contacts/list) |
| `userId` | number | yes | ID of the employee or bot on whose behalf the message is sent. Employees: [`GET /v1/users`](/docs/entities/users/list), bots: the `data.bots[].id` field in the [`GET /v1/bots`](/docs/bots/management/list) response |
| `chatId` | number | yes | ID of this card's Open Channel chat — a positive integer without the `chat` prefix. List: [`GET /v1/openlines/crm/chats`](/docs/openlines/crm-chats) with `activeOnly=false` |
| `message` | string | yes | Message text. A whitespace-only string is not accepted |

Numeric fields also accept a string of digits.

## Examples

### curl — personal key

```bash
curl -X POST https://vibecode.bitrix24.com/v1/openlines/crm/messages \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "crmEntityType": "deal",
    "crmEntityId": 42,
    "userId": 7,
    "chatId": 123,
    "message": "Hello! Your order is ready for pickup."
  }'
```

### curl — OAuth application

```bash
curl -X POST https://vibecode.bitrix24.com/v1/openlines/crm/messages \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "crmEntityType": "deal",
    "crmEntityId": 42,
    "userId": 7,
    "chatId": 123,
    "message": "Hello! Your order is ready for pickup."
  }'
```

### JavaScript — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/openlines/crm/messages', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    crmEntityType: 'deal',
    crmEntityId: 42,
    userId: 7,
    chatId: 123,
    message: 'Hello! Your order is ready for pickup.',
  }),
})

const { success, data, error } = await res.json()
if (!success) throw new Error(`${error.code}: ${error.message}`)
console.log('Message ID:', data.messageId)
```

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/openlines/crm/messages', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    crmEntityType: 'deal',
    crmEntityId: 42,
    userId: 7,
    chatId: 123,
    message: 'Hello! Your order is ready for pickup.',
  }),
})

const { success, data } = await res.json()
```

## Response fields

| Field | Type | Description |
|------|-----|----------|
| `success` | boolean | `true` when the message is sent |
| `data.chatId` | number | Chat ID from the request |
| `data.messageId` | number | ID of the sent message in the chat |

## Response example

```json
{
  "success": true,
  "data": {
    "chatId": 123,
    "messageId": 456
  }
}
```

## Error response example

404 — the chat does not belong to the specified CRM card:

```json
{
  "success": false,
  "error": {
    "code": "ENTITY_NOT_FOUND",
    "message": "CRM chat not found"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|----------|
| 400 | `INVALID_PARAMS` | `crmEntityType` is missing or outside the list, `crmEntityId` or `userId` is missing or not a positive integer, or the request body is not a JSON object |
| 400 | `INVALID_JSON_BODY` | The request body cannot be parsed as JSON |
| 400 | `INVALID_CHAT_ID` | `chatId` is not a positive integer, including the `chat123` form |
| 400 | `MISSING_REQUIRED_FIELDS` | `chatId` is missing, or `message` is missing, not a string, empty or whitespace-only |
| 404 | `ENTITY_NOT_FOUND` | Message `CRM chat not found`: the CRM card has no chat with that `chatId`, closed ones included. The check runs before sending; the message is not sent |
| 404 | `ENTITY_NOT_FOUND` | Message `Record not found`: Bitrix24 did not find the chat when sending |
| 403 | `BITRIX_ACCESS_DENIED` | The employee making the call has no access to the CRM card or no permission to send the message |
| 422 | `BITRIX_ERROR` | Bitrix24 refused to send; its code is returned in `error.b24Code` |
| 502 | `OPENLINE_OPERATOR_FAILED` | Bitrix24 did not return a message ID. The message may still have been sent |
| 502 | `OPENLINE_OPERATOR_FAILED` | The card's chat list came back in an unexpected shape. The check runs before sending; the message is not sent |
| 403 | `SCOPE_DENIED` | The key lacks the `imopenlines` scope |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | The key is read-only |
| 401 | `MISSING_API_KEY` | The `X-Api-Key` header is missing |
| 401 | `INVALID_API_KEY` | The key is not recognized |
| 401 | `TOKEN_MISSING` | A personal key has no access to the Bitrix24 portal, or an application key was sent without a session token in `Authorization` |
| 429 | `RATE_LIMITED` | The limit of 120 requests per minute per portal was exceeded. The limit is divided across platform replicas, so the code can arrive before the 120th request. The effective value for the key is in the `x-ratelimit-limit` header, the remaining quota is in `x-ratelimit-remaining` |
| 429 | `RATE_LIMITED` | Bitrix24 rejected the call under its request rate limit for the portal. The response carries a `Retry-After` header |
| 502 | `BITRIX_UNAVAILABLE` | Bitrix24 is unavailable or the connection to it dropped before the full response |
| 503 | `BITRIX_TIMEOUT` | Bitrix24 accepted the call but did not respond in time. The message may still have been sent |

Full list of common API errors — [Errors](/docs/errors).

## Known specifics

- **The chat is checked before sending.** Before sending, Vibecode matches `chatId` against all of the card's chats, closed dialogs included.
- **Retry after a failure.** After `502 OPENLINE_OPERATOR_FAILED` or `503 BITRIX_TIMEOUT` the message may have reached the client. A repeated call sends it again, so check the dialog's messages before retrying.

## See also

- [Chats by CRM card](/docs/openlines/crm-chats)
- [CRM chat members](/docs/openlines/crm/members)
- [Create a lead from an inquiry](/docs/openlines/crm/lead)
- [Session history](/docs/openlines/history)
