## Edit a message

`PATCH /v1/chats/:dialogId/messages/:messageId`

Updates the text of a previously sent message. The API cannot restore the original text after an edit — send a new message if needed.

## Parameters

| Parameter | Type | Required | Description |
|----------|-----|:-----:|---------|
| `dialogId` (path) | string | yes | Dialog identifier: numeric user ID or `chatXXX` for group chats. `chatXXX` is the `id` field of a [recent dialogs](/docs/chats/discovery/recent) row; the user ID comes from the [user list](/docs/entities/users/list) |
| `messageId` (path) | number | yes | Message ID — from the [send](/docs/chats/messages/send) response or the `id` field of a message in the [chat feed](/docs/chats/messages/list) |
| `format` (query) | string | no | `v2` turns on the v2 mode — see "The v2 mode" below. Any other value is ignored and the response is unchanged. A repeated `format` is not rejected: with `format=v1&format=v2` the last value wins, so the v2 mode is on, while `format=v2&format=v1` returns the legacy response. The bracket form `format[]=v2` turns on the v2 mode when all its values are `v2` |

**The v2 mode.** `format=v2` edits the message: besides the text it changes the `attach`, `keyboard`, `menu` and `block` blocks — with or without the text — and `urlPreview` turns the link preview on and off. `data` carries `{ "result": true }` instead of `true`. Without `message` the text stays as it was. An edit that could turn into a deletion is refused before Bitrix24 is called, as in the regular mode: text that is empty by the Bitrix24 rule gets `400 MESSAGE_REQUIRED`. Removing a block (`[]`, `{}` or `""`) without new text in the same request gets `400 INVALID_PARAMS`, because a message left with no text and no blocks would be deleted, and the platform cannot see whether the message has text. At least one of `message`, `attach`, `keyboard`, `menu`, `block` is required. In the v2 mode `message` must be a string. Any other body field and any query parameter other than `format` are rejected with `400 INVALID_PARAMS`, and `botId` is rejected with a pointer to [editing as a bot](/docs/bots). The message must belong to the dialog in the path: after the input checks, the platform reads that dialog without joining the chat and, if the message is not there, returns `404 ENTITY_NOT_FOUND` and changes nothing. The `me` alias is resolved before this check. The regular mode does not check `dialogId`.

## Request body fields

| Field | Type | Required | Description |
|------|-----|:-----:|---------|
| `message` | string | yes | New message text — up to 20000 Unicode code points. Optional in the v2 mode when a block is passed |
| `attach` | array\|object\|string | no | The v2 mode only: new attachments — an object, an array or a string with the attachment JSON. `[]`, `{}` or `""` removes them — only together with `message` |
| `keyboard` | array\|object\|string | no | The v2 mode only: a new keyboard — an object or an array. An empty value (`[]`, `{}` or `""` — a string is accepted only when empty) removes it — only together with `message` |
| `menu` | array\|object\|string | no | The v2 mode only: a new context menu — an object or an array. An empty value (`[]`, `{}` or `""` — a string is accepted only when empty) removes it — only together with `message` |
| `block` | array\|object\|string | no | The v2 mode only: new message blocks in the Bitrix24 format — an object with `elements` (each element with its own `type`) and an optional `config`. An empty value (`[]`, `{}` or `""` — a string is accepted only when empty) removes them — only together with `message` |
| `urlPreview` | boolean | no | The v2 mode only. On by default: every edit builds the link preview anew from the text — the new one, or the current one when none is given — including after [removing the preview](/docs/chats/messages/url-preview). `false` leaves the message without a preview. `true`/`false` or `"Y"`/`"N"` |

## Message layout

- [Text formatting](/docs/chats/messages/formatting) — BB codes in the message text

For a non-empty update, the `message` text is limited to 20000 Unicode code points. The complete outgoing string is counted, including markup, URLs and mentions, without truncation or trimming. For example, `😀` is one code point, while a compound emoji may occupy several. Text that is empty by Bitrix24's rule is rejected with `MESSAGE_REQUIRED` before this length check, even when it contains more than 20000 code points. The request is rejected before any call to Bitrix24.

Without `format=v2`, matching text aliases `message`, `MESSAGE`, `Message` and `fields.message` are accepted, including other case variants. Their values must agree. A conflict returns `400 INVALID_PARAMS`. An object is never converted to text. The v2 mode keeps its flat body whitelist: case variants and `fields` are not accepted.

## Examples

### curl — personal key

```bash
curl -X PATCH https://vibecode.bitrix24.com/v1/chats/42/messages/36889 \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Invoice confirmed. Thank you."
  }'
```

### curl — OAuth application

```bash
curl -X PATCH https://vibecode.bitrix24.com/v1/chats/42/messages/36889 \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Invoice confirmed. Thank you."
  }'
```

### JavaScript — personal key

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/chats/42/messages/36889', {
  method: 'PATCH',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    message: 'Invoice confirmed. Thank you.',
  }),
})

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

### JavaScript — OAuth application

```javascript
const res = await fetch('https://vibecode.bitrix24.com/v1/chats/42/messages/36889', {
  method: 'PATCH',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    message: 'Invoice confirmed. Thank you.',
  }),
})

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

### curl — the v2 mode

```bash
curl -X PATCH "https://vibecode.bitrix24.com/v1/chats/42/messages/36889?format=v2" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "keyboard": [{ "TEXT": "Open the invoice", "LINK": "https://example.com/invoice" }]
  }'
```

## Response fields

| Field | Type | Description |
|------|-----|---------|
| `success` | boolean | Always `true` on success |
| `data` | boolean | `true` on successful update |
| `data.result` | boolean | The v2 mode: `true` on successful update |

## Response example

```json
{
  "success": true,
  "data": true
}
```

## Error response example

422 — the message was not found, the edit time has expired, or there is no access:

```json
{
  "success": false,
  "error": {
    "code": "BITRIX_ERROR",
    "message": "Time has expired for modification or you don't have access",
    "b24Code": "CANT_EDIT_MESSAGE"
  }
}
```

## Errors

| HTTP | Code | Description |
|------|-----|---------|
| 400 | `MESSAGE_TOO_LONG` | After the empty-text check, more than 20000 Unicode code points in the outgoing text. Text that is empty by Bitrix24's rule returns `MESSAGE_REQUIRED` instead. `error.details` contains only `field: "message"`, `used`, `limit: 20000`, `unit: "unicode-code-points"`. No Bitrix24 call is made |
| 400 | `INVALID_PARAMS` | `messageId` is not a positive integer — checked before the Bitrix24 call |
| 400 | `MESSAGE_REQUIRED` | The `message` field is absent, not text, or empty by Bitrix24's own rule — whitespace only, `[BR]`/`[br]` line-break tags only, or the null character (NUL) (including text sent under another field name — the error `message` names that field). Nothing was sent to Bitrix24 |
| 422 | `BITRIX_ERROR` | Bitrix24 returned an error — message not found or unavailable (details in `message`). The Bitrix24 code is in `error.b24Code` |
| 400 | `INVALID_PARAMS` | Conflicting legacy text aliases. The v2 mode only: nothing to update, a block removal without `message`, a body field outside the list, a query parameter other than `format` or an invalid value |
| 404 | `ENTITY_NOT_FOUND` | The v2 mode only: the message is not in the dialog in the path (it belongs to another dialog or does not exist) — nothing is changed |
| 403 | `WRITE_BLOCKED_READONLY_KEY` | The key is read-only — see [access rights](/docs/access-rights) |
| 403 | `SCOPE_DENIED` | The key does not have the `im` scope |
| 401 | `TOKEN_MISSING` | The key has no configured tokens |
| 502 | `ME_ALIAS_RESOLUTION_FAILED` | The v2 mode only: failed to resolve the user for the `me` alias |

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

## Known specifics

**Empty text is not an update.** Text that is empty by the Bitrix24 rule would delete the message, so such a request is refused before Bitrix24 is called — see `MESSAGE_REQUIRED` in the errors table. To delete a message, use [`DELETE /v1/chats/:dialogId/messages/:messageId`](/docs/chats/messages/delete). A number in `message` is accepted and stored as text.

**Only `message` is sent to Bitrix24.** In the regular mode other fields of the request body are not forwarded to Bitrix24 — including Bitrix24's own upper-case keys. In the v2 mode the fields from the table are forwarded, and the rest are rejected. Bitrix24 compares parameter names case-insensitively and the last key takes effect: an alias such as `Message: ""` beside `MESSAGE` would delete the message.

**System messages cannot be edited.** Messages sent with `system: true` have `authorId = 0`. They cannot be changed — the API returns an error.

**An edit without changes.** In the v2 mode even an edit that changes nothing may mark the message as edited if other members have already seen it.

## See also

- [Messages](/docs/chats/messages)
- [Send a message](/docs/chats/messages/send)
- [Delete a message](/docs/chats/messages/delete)
- [Read messages](/docs/chats/messages/list)
