# Переписка

Сохраненные сообщения переписки аккаунта с лидом: исходящие шаги цепочки, ручные сообщения и ответы.

Базовый адрес: https://api.melioralab.io/v1. Ключ передается в заголовке `Authorization: Bearer mel_live_...`.

Маршруты:

- `GET /v1/leads/{id}/messages`: Переписка с лидом

## Объект Message

Сообщение из сохраненной переписки с лидом. Архив может быть неполным: см. `meta.completeness`.

| Поле | Тип | Описание |
|---|---|---|
| `object` | string | Тип объекта, всегда `message`. |
| `id` | string | Непрозрачный устойчивый идентификатор сообщения. Не разбирайте его формат. |
| `direction` | string | `outbound`: от аккаунта к лиду; `inbound`: от лида. Значения: `outbound`, `inbound`. |
| `author` | string | `campaign`: шаг цепочки; `manager`: ручное сообщение; `lead`: ответ лида; `unknown`: не определено. Значения: `campaign`, `manager`, `lead`, `unknown`. |
| `sent_at` | string (date-time) или null | Когда сообщение отправлено или получено. `null`, если неизвестно. |
| `state` | string | `sent`: отправка подтверждена; `received`: входящее сохранено; `unknown`: исход не подтвержден. Значения: `sent`, `received`, `unknown`. |
| `media_kind` | string | Вид вложения. Файлы и ссылки на них не выдаются. Значения: `none`, `photo`, `video`, `voice`, `audio`, `document`, `sticker`, `animation`, `video_note`, `contact`, `location`, `poll`, `other`, `unknown`. |
| `text` | string или null | Сохраненный текст, до 4000 знаков. `null`, если текст недоступен. |
| `text_truncated` | boolean или null | `true`: текст сокращен; `false`: текст полный; `null`: полнота неизвестна. |
| `text_unavailable` | boolean | `true`, если текст не выдается. Причина в `text_unavailable_reason`. |
| `text_unavailable_reason` | string или null | `not_saved`: текст не сохранен; `ownership_unproven`: принадлежность переписке не доказана; `source_conflict`: источники расходятся; `purged`: текст удален по сроку хранения. Значения: `not_saved`, `ownership_unproven`, `source_conflict`, `purged`. |

Пример:

```json
{
  "object": "message",
  "id": "m_6b2e9f403a7c4d158e921c5a7f3b9d08",
  "direction": "outbound",
  "author": "campaign",
  "sent_at": "2026-10-26T07:30:00.000Z",
  "state": "sent",
  "media_kind": "none",
  "text": "Добрый день, Анна! Пишу из Примера: подскажите, кто у вас отвечает за аутрич?",
  "text_truncated": false,
  "text_unavailable": false,
  "text_unavailable_reason": null
}
```

## Переписка с лидом

`GET /v1/leads/{id}/messages`

Сохраненные сообщения переписки с лидом, от ранних к поздним. Нужны право `messages:read` и включенный у ключа «Текст сообщений». Архив может быть неполным: смотрите `meta.completeness`.

Право: `messages:read` и включенный у ключа «Текст сообщений». Вес запроса: 3.

### Параметры

| Параметр | Где | Тип | Обязательный | Описание |
|---|---|---|---|---|
| `id` | путь | string (uuid) | да | Идентификатор лида. |
| `limit` | строка запроса | integer | нет | Сколько объектов вернуть на странице. От 1 до 100. По умолчанию `50`. |
| `cursor` | строка запроса | string | нет | Значение `next_cursor` из предыдущего ответа. Остальные фильтры должны совпадать с первым запросом. |

### Ответ

Список: `object`, `data`, `has_more`, `next_cursor`, `meta`. Пагинация: https://docs.melioralab.io/md/pagination.md

Поле `meta`:

| Поле | Тип | Описание |
|---|---|---|
| `meta.completeness` | object | Насколько полон сохраненный архив переписки с лидом. Сейчас полнота не вычисляется: `overall` и `inbound` приходят как `partial`, `outbound` как `unknown`. |
| `meta.completeness.overall` | string | Общая полнота: `complete`, `partial` или `unknown`. Значения: `complete`, `partial`, `unknown`. |
| `meta.completeness.inbound` | string | Полнота входящих сообщений. Значения: `complete`, `partial`, `unknown`. |
| `meta.completeness.outbound` | string | Полнота исходящих сообщений. Значения: `complete`, `partial`, `unknown`. |
| `meta.completeness.available_from` | string (date-time) или null | Начало периода, за который есть данные. `null`, если неизвестно. |
| `meta.completeness.available_to` | string (date-time) или null | Конец периода, за который есть данные. `null`, если неизвестно. |
| `meta.completeness.observed_at` | string (date-time) | Время чтения данных сервером. |

### Особые ошибки

- `cursor_mismatch` (400): Курсор поврежден, выдан для другого маршрута или фильтров, либо права и охват ключа изменились.
- `scope_required` (403): Ключ не имеет права на это чтение. Для текстов нужен также включенный «Текст сообщений».
- `not_found` (404): Объект не найден или находится вне охвата ключа. Неизвестный путь тоже дает 404.
- `cursor_expired` (410): Срок курсора истек: 15 минут для списков, 30 суток для ленты событий.

Общие ошибки: https://docs.melioralab.io/md/errors.md

### Пример запроса

```bash
curl https://api.melioralab.io/v1/leads/5a7c3e92-1f4b-4d68-9c2e-7b0a6d4f8e31/messages \
  -H "Authorization: Bearer $MELIORA_API_KEY"
```

### Пример ответа

```json
{
  "object": "list",
  "data": [
    {
      "object": "message",
      "id": "m_6b2e9f403a7c4d158e921c5a7f3b9d08",
      "direction": "outbound",
      "author": "campaign",
      "sent_at": "2026-10-26T07:30:00.000Z",
      "state": "sent",
      "media_kind": "none",
      "text": "Добрый день, Анна! Пишу из Примера: подскажите, кто у вас отвечает за аутрич?",
      "text_truncated": false,
      "text_unavailable": false,
      "text_unavailable_reason": null
    },
    {
      "object": "message",
      "id": "m_c7a1d3e59b2f4a68b4d05e8f2a6c1b97",
      "direction": "inbound",
      "author": "lead",
      "sent_at": "2026-10-26T09:12:00.000Z",
      "state": "received",
      "media_kind": "none",
      "text": "Здравствуйте! Это я, давайте созвонимся завтра.",
      "text_truncated": null,
      "text_unavailable": false,
      "text_unavailable_reason": null
    },
    {
      "object": "message",
      "id": "m_a3f8c6e27d1b4e949c058b2d4f7a6e13",
      "direction": "inbound",
      "author": "lead",
      "sent_at": "2026-10-26T09:13:30.000Z",
      "state": "received",
      "media_kind": "photo",
      "text": null,
      "text_truncated": null,
      "text_unavailable": true,
      "text_unavailable_reason": "not_saved"
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "meta": {
    "completeness": {
      "overall": "partial",
      "inbound": "partial",
      "outbound": "unknown",
      "available_from": "2026-10-26T07:30:00.000Z",
      "available_to": "2026-10-27T09:15:42.000Z",
      "observed_at": "2026-10-27T09:15:42.000Z"
    }
  }
}
```
