# События

Лента событий: первое сообщение лиду и ответ лида. Читайте ее по курсору, чтобы узнавать об изменениях без обхода всех лидов.

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

Маршруты:

- `GET /v1/events`: Лента событий

## Событие lead.first_message_sent

Лиду отправлено первое сообщение. `data.first_message` есть только при `include_text=true`.

| Поле | Тип | Описание |
|---|---|---|
| `object` | string | Тип объекта, всегда `event`. |
| `id` | string | Идентификатор события: `evt_` и 32 шестнадцатеричных знака. Ключ дедупликации. |
| `api_version` | string | Версия формата события. Всегда `"2026-10-18"`. |
| `timestamp` | string (date-time) | Когда произошел факт. |
| `test` | boolean | Всегда `false`. |
| `type` | string | Тип события. Всегда `"lead.first_message_sent"`. |
| `data` | object | Данные события. |
| `data.organization_id` | string (uuid) | Организация, в которой произошло событие. |
| `data.workspace` | object | Пространство на момент события. |
| `data.workspace.id` | string (uuid) | Идентификатор. |
| `data.workspace.name` | string или null | Название. Может быть `null`. |
| `data.campaign` | object или null | Кампания на момент события. Может быть `null`. |
| `data.campaign.id` | string (uuid) | Идентификатор. |
| `data.campaign.name` | string или null | Название. Может быть `null`. |
| `data.lead` | object | Снимок лида. |
| `data.lead.id` | string (uuid) | Идентификатор лида. |
| `data.lead.username` | string или null | Ник Telegram без `@`. Может быть `null`. |
| `data.lead.status` | string | Статус лида на момент события. Значения: `new`, `contacted`, `completed`, `replied`, `blocked`, `failed`, `suspended`, `deleted`, `opted_out`, `unknown`. |
| `data.lead.fields` | object | Поля карточки на момент события. |
| `data.lead.fields.record_id` | string или null | Идентификатор записи во внешней CRM, переданный при загрузке контактов. `null`, если `record_id_status` не `valid`. Может отсутствовать. |
| `data.lead.fields.first_name` | string или null | Имя. Может отсутствовать. |
| `data.lead.fields.last_name` | string или null | Фамилия. Может отсутствовать. |
| `data.lead.fields.company` | string или null | Компания. Может отсутствовать. |
| `data.lead.fields.position` | string или null | Должность. Может отсутствовать. |
| `data.lead.fields.website` | string или null | Сайт. Может отсутствовать. |
| `data.lead.fields.source` | string или null | Источник контакта. Может отсутствовать. |
| `data.lead.fields.utm_source` | string или null | Метка `utm_source`. Может отсутствовать. |
| `data.lead.fields.utm_medium` | string или null | Метка `utm_medium`. Может отсутствовать. |
| `data.lead.fields.utm_campaign` | string или null | Метка `utm_campaign`. Может отсутствовать. |
| `data.lead.fields_truncated` | boolean | `true`, если часть значений сокращена. |
| `data.lead.record_id_status` | string | Надежность `fields.record_id`. В v1 у событий приходит `unknown`, а `fields.record_id` равен `null`: берите `record_id` из `GET /v1/leads/{id}` по `data.lead.id`. Значения: `valid`, `missing`, `conflict`, `unknown`. |
| `data.account` | object или null | Аккаунт, который вел переписку. `null`, если неизвестно. |
| `data.account.id` | string (uuid) | Идентификатор аккаунта. |
| `data.account.username` | string или null | Ник аккаунта без `@`. Может быть `null`. |
| `data.account.name` | string или null | Отображаемое имя аккаунта. Может быть `null`. |
| `data.first_message_sent_at` | string (date-time) или null | Когда отправлено первое сообщение. `null`, если отправка не подтверждена; у события ответа это частый случай. |
| `data.first_message` | object | Текст первого сообщения. Только при `include_text=true`. Может отсутствовать. |
| `data.first_message.text` | string или null | Сохраненный текст, до 4000 знаков. `null`, если текст недоступен. |
| `data.first_message.text_truncated` | boolean или null | `true`: текст сокращен; `false`: текст полный; `null`: полнота неизвестна. |
| `data.first_message.text_unavailable` | boolean | `true`, если текст не выдается. |
| `data.first_message.text_unavailable_reason` | string или null | Причина недоступности текста или `null`. Значения: `not_saved`, `ownership_unproven`, `source_conflict`, `purged`. |

Пример:

```json
{
  "object": "event",
  "id": "evt_3b9e2c7a1f4d4e8a9b6c0d2e5f7a8c91",
  "type": "lead.first_message_sent",
  "api_version": "2026-10-18",
  "timestamp": "2026-10-26T07:30:00.000Z",
  "test": false,
  "data": {
    "organization_id": "2c8f4a61-7d3e-4b9a-8f21-6a0d5e9c3b17",
    "workspace": {
      "id": "3f6a9d21-8b4c-4e7f-a102-5c9e8d7b6a43",
      "name": "Основное пространство"
    },
    "campaign": {
      "id": "4d8b2f73-6e1a-4c95-b7d0-8a3f5e2c9b14",
      "name": "Пример кампании"
    },
    "lead": {
      "id": "5a7c3e92-1f4b-4d68-9c2e-7b0a6d4f8e31",
      "username": "example_user",
      "status": "contacted",
      "fields": {
        "record_id": null,
        "first_name": "Анна",
        "company": "ООО Пример"
      },
      "fields_truncated": false,
      "record_id_status": "unknown"
    },
    "account": {
      "id": "1d9f6b28-4c3e-4a75-8b19-5e7a2c0f6d83",
      "username": "example_sender",
      "name": "Мария из Примера"
    },
    "first_message_sent_at": "2026-10-26T07:30:00.000Z"
  }
}
```

## Событие lead.replied

Лид ответил. Приходит на каждый засчитанный ответ, у одного лида их может быть несколько. Поля текста в `data.reply` есть только при `include_text=true`.

| Поле | Тип | Описание |
|---|---|---|
| `object` | string | Тип объекта, всегда `event`. |
| `id` | string | Идентификатор события: `evt_` и 32 шестнадцатеричных знака. Ключ дедупликации. |
| `api_version` | string | Версия формата события. Всегда `"2026-10-18"`. |
| `timestamp` | string (date-time) | Когда произошел факт. |
| `test` | boolean | Всегда `false`. |
| `type` | string | Тип события. Всегда `"lead.replied"`. |
| `data` | object | Данные события. |
| `data.organization_id` | string (uuid) | Организация, в которой произошло событие. |
| `data.workspace` | object | Пространство на момент события. |
| `data.workspace.id` | string (uuid) | Идентификатор. |
| `data.workspace.name` | string или null | Название. Может быть `null`. |
| `data.campaign` | object или null | Кампания на момент события. Может быть `null`. |
| `data.campaign.id` | string (uuid) | Идентификатор. |
| `data.campaign.name` | string или null | Название. Может быть `null`. |
| `data.lead` | object | Снимок лида. |
| `data.lead.id` | string (uuid) | Идентификатор лида. |
| `data.lead.username` | string или null | Ник Telegram без `@`. Может быть `null`. |
| `data.lead.status` | string | Статус лида на момент события. Значения: `new`, `contacted`, `completed`, `replied`, `blocked`, `failed`, `suspended`, `deleted`, `opted_out`, `unknown`. |
| `data.lead.fields` | object | Поля карточки на момент события. |
| `data.lead.fields.record_id` | string или null | Идентификатор записи во внешней CRM, переданный при загрузке контактов. `null`, если `record_id_status` не `valid`. Может отсутствовать. |
| `data.lead.fields.first_name` | string или null | Имя. Может отсутствовать. |
| `data.lead.fields.last_name` | string или null | Фамилия. Может отсутствовать. |
| `data.lead.fields.company` | string или null | Компания. Может отсутствовать. |
| `data.lead.fields.position` | string или null | Должность. Может отсутствовать. |
| `data.lead.fields.website` | string или null | Сайт. Может отсутствовать. |
| `data.lead.fields.source` | string или null | Источник контакта. Может отсутствовать. |
| `data.lead.fields.utm_source` | string или null | Метка `utm_source`. Может отсутствовать. |
| `data.lead.fields.utm_medium` | string или null | Метка `utm_medium`. Может отсутствовать. |
| `data.lead.fields.utm_campaign` | string или null | Метка `utm_campaign`. Может отсутствовать. |
| `data.lead.fields_truncated` | boolean | `true`, если часть значений сокращена. |
| `data.lead.record_id_status` | string | Надежность `fields.record_id`. В v1 у событий приходит `unknown`, а `fields.record_id` равен `null`: берите `record_id` из `GET /v1/leads/{id}` по `data.lead.id`. Значения: `valid`, `missing`, `conflict`, `unknown`. |
| `data.account` | object или null | Аккаунт, который вел переписку. `null`, если неизвестно. |
| `data.account.id` | string (uuid) | Идентификатор аккаунта. |
| `data.account.username` | string или null | Ник аккаунта без `@`. Может быть `null`. |
| `data.account.name` | string или null | Отображаемое имя аккаунта. Может быть `null`. |
| `data.first_message_sent_at` | string (date-time) или null | Когда отправлено первое сообщение. `null`, если отправка не подтверждена; у события ответа это частый случай. |
| `data.reply` | object | Ответ лида. |
| `data.reply.id` | string (uuid) | Идентификатор ответа. |
| `data.reply.replied_at` | string (date-time) | Когда пришел ответ. |
| `data.reply.automation_stopped` | boolean или null | Остановлена ли цепочка после ответа. `null`, если остановка не подтверждена. Сейчас всегда `null`. |
| `data.reply.is_first_reply` | boolean | Первый ли это ответ лида в кампании. Сейчас не передается. Может отсутствовать. |
| `data.reply.text` | string или null | Текст ответа. Только при `include_text=true`. Может отсутствовать. |
| `data.reply.text_truncated` | boolean или null | Признак сокращения текста. Только при `include_text=true`. Может отсутствовать. |
| `data.reply.text_unavailable` | boolean | Текст недоступен. Только при `include_text=true`. Может отсутствовать. |
| `data.reply.text_unavailable_reason` | string или null | Причина недоступности текста. Только при `include_text=true`. Значения: `not_saved`, `ownership_unproven`, `source_conflict`, `purged`. Может отсутствовать. |

Пример:

```json
{
  "object": "event",
  "id": "evt_8d4f1a6c2e9b4c7d8a3e5f0b1c6d9e27",
  "type": "lead.replied",
  "api_version": "2026-10-18",
  "timestamp": "2026-10-26T09:12:00.000Z",
  "test": false,
  "data": {
    "organization_id": "2c8f4a61-7d3e-4b9a-8f21-6a0d5e9c3b17",
    "workspace": {
      "id": "3f6a9d21-8b4c-4e7f-a102-5c9e8d7b6a43",
      "name": "Основное пространство"
    },
    "campaign": {
      "id": "4d8b2f73-6e1a-4c95-b7d0-8a3f5e2c9b14",
      "name": "Пример кампании"
    },
    "lead": {
      "id": "5a7c3e92-1f4b-4d68-9c2e-7b0a6d4f8e31",
      "username": "example_user",
      "status": "replied",
      "fields": {
        "record_id": null,
        "first_name": "Анна",
        "company": "ООО Пример"
      },
      "fields_truncated": false,
      "record_id_status": "unknown"
    },
    "account": {
      "id": "1d9f6b28-4c3e-4a75-8b19-5e7a2c0f6d83",
      "username": "example_sender",
      "name": "Мария из Примера"
    },
    "first_message_sent_at": null,
    "reply": {
      "id": "e4b7a2d9-6c1f-4e38-9a5b-0d8c3f7e1a62",
      "replied_at": "2026-10-26T09:12:00.000Z",
      "automation_stopped": null
    }
  }
}
```

## Лента событий

`GET /v1/events`

События в порядке появления: первое сообщение лиду и ответ лида. Опрашивайте ленту по курсору. `next_cursor` есть всегда, даже у пустой страницы. С `include_text=true` вес запроса 3.

Право: `events:read`. Вес запроса: 2 (3 с `include_text=true`).

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

| Параметр | Где | Тип | Обязательный | Описание |
|---|---|---|---|---|
| `limit` | строка запроса | integer | нет | Сколько объектов вернуть на странице. От 1 до 200. По умолчанию `50`. |
| `cursor` | строка запроса | string | нет | Значение `next_cursor` из предыдущего ответа. Остальные фильтры должны совпадать с первым запросом. |
| `workspace_id` | строка запроса | string (uuid) | нет | Только объекты этого пространства. |
| `campaign_id` | строка запроса | string (uuid) | нет | Только объекты этой кампании. |
| `type` | строка запроса | string | нет | Только события этого типа. Значения: `lead.first_message_sent`, `lead.replied`. |
| `include_text` | строка запроса | string | нет | Добавить тексты сообщений в события. Нужны право `messages:read` и включенный «Текст сообщений» у ключа. Значения: `true`, `false`. По умолчанию `false`. |
| `start` | строка запроса | string | нет | `now`: начать ленту с текущего момента. Возвращает пустую страницу и курсор. Нельзя вместе с `cursor`. Значения: `now`. |

### Ответ

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

Поле `meta`:

| Поле | Тип | Описание |
|---|---|---|
| `meta.available_from` | string (date-time) или null | С какого момента в ленте есть события. `null`, если неизвестно. |
| `meta.retention_days` | string | Сколько суток лента хранит события. Всегда `30`. |
| `meta.collection` | object | Состояние сбора сохраненной переписки для API. |
| `meta.collection.state` | string | `running`: сбор идет; `paused`: приостановлен в кабинете; `preparing`: готовится после включения; `degraded`: данные могут быть неполными. Значения: `running`, `paused`, `preparing`, `degraded`. |
| `meta.collection.enabled_at` | string (date-time) или null | Когда сбор был включен. `null`, если еще не включен. |
| `meta.collection.pause_pending` | boolean | Пауза или возобновление запрошены в кабинете, но еще не применены. |
| `meta.coverage` | object | Полнота ленты событий. |
| `meta.coverage.status` | string | `complete`, `partial` или `unknown`. Значения: `complete`, `partial`, `unknown`. |
| `meta.coverage.gaps` | array of object | До 50 интервалов возможной неполноты. |
| `meta.coverage.gaps[].id` | string (uuid) | Идентификатор интервала. |
| `meta.coverage.gaps[].from` | string (date-time) | Начало интервала. |
| `meta.coverage.gaps[].to` | string (date-time) или null | Конец интервала. `null`, если интервал еще не закрыт. |
| `meta.coverage.gaps[].state` | string | `open`, `recovering`, `recovered`, `unrecoverable` или `unknown`. Значения: `open`, `recovering`, `recovered`, `unrecoverable`, `unknown`. |
| `meta.coverage.gaps_truncated` | boolean | `true`, если интервалов больше 50. |
| `meta.coverage.incomplete_from` | string (date-time) или null | Самая ранняя граница скрытых неполных интервалов. `null`, если таких нет. |
| `meta.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 -G https://api.melioralab.io/v1/events \
  -H "Authorization: Bearer $MELIORA_API_KEY" \
  -d limit=100
```

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

```json
{
  "object": "list",
  "data": [
    {
      "object": "event",
      "id": "evt_3b9e2c7a1f4d4e8a9b6c0d2e5f7a8c91",
      "type": "lead.first_message_sent",
      "api_version": "2026-10-18",
      "timestamp": "2026-10-26T07:30:00.000Z",
      "test": false,
      "data": {
        "organization_id": "2c8f4a61-7d3e-4b9a-8f21-6a0d5e9c3b17",
        "workspace": {
          "id": "3f6a9d21-8b4c-4e7f-a102-5c9e8d7b6a43",
          "name": "Основное пространство"
        },
        "campaign": {
          "id": "4d8b2f73-6e1a-4c95-b7d0-8a3f5e2c9b14",
          "name": "Пример кампании"
        },
        "lead": {
          "id": "5a7c3e92-1f4b-4d68-9c2e-7b0a6d4f8e31",
          "username": "example_user",
          "status": "contacted",
          "fields": {
            "record_id": null,
            "first_name": "Анна",
            "company": "ООО Пример"
          },
          "fields_truncated": false,
          "record_id_status": "unknown"
        },
        "account": {
          "id": "1d9f6b28-4c3e-4a75-8b19-5e7a2c0f6d83",
          "username": "example_sender",
          "name": "Мария из Примера"
        },
        "first_message_sent_at": "2026-10-26T07:30:00.000Z"
      }
    },
    {
      "object": "event",
      "id": "evt_8d4f1a6c2e9b4c7d8a3e5f0b1c6d9e27",
      "type": "lead.replied",
      "api_version": "2026-10-18",
      "timestamp": "2026-10-26T09:12:00.000Z",
      "test": false,
      "data": {
        "organization_id": "2c8f4a61-7d3e-4b9a-8f21-6a0d5e9c3b17",
        "workspace": {
          "id": "3f6a9d21-8b4c-4e7f-a102-5c9e8d7b6a43",
          "name": "Основное пространство"
        },
        "campaign": {
          "id": "4d8b2f73-6e1a-4c95-b7d0-8a3f5e2c9b14",
          "name": "Пример кампании"
        },
        "lead": {
          "id": "5a7c3e92-1f4b-4d68-9c2e-7b0a6d4f8e31",
          "username": "example_user",
          "status": "replied",
          "fields": {
            "record_id": null,
            "first_name": "Анна",
            "company": "ООО Пример"
          },
          "fields_truncated": false,
          "record_id_status": "unknown"
        },
        "account": {
          "id": "1d9f6b28-4c3e-4a75-8b19-5e7a2c0f6d83",
          "username": "example_sender",
          "name": "Мария из Примера"
        },
        "first_message_sent_at": null,
        "reply": {
          "id": "e4b7a2d9-6c1f-4e38-9a5b-0d8c3f7e1a62",
          "replied_at": "2026-10-26T09:12:00.000Z",
          "automation_stopped": null
        }
      }
    }
  ],
  "has_more": false,
  "next_cursor": "v1.k1.Qm9hcmRfMWZ4.c3RyaWN0bHlfb3BhcXVl.dGFnX2V4YW1wbGU.bWFjX2V4YW1wbGVfdmFsdWU",
  "meta": {
    "available_from": "2026-10-20T08:00:00.000Z",
    "retention_days": 30,
    "collection": {
      "state": "running",
      "enabled_at": "2026-10-20T08:00:00.000Z",
      "pause_pending": false
    },
    "coverage": {
      "status": "unknown",
      "gaps": [],
      "gaps_truncated": false,
      "incomplete_from": null
    },
    "observed_at": "2026-10-27T09:15:42.000Z"
  }
}
```

### Пример ответа: С include_text=true

```json
{
  "object": "list",
  "data": [
    {
      "object": "event",
      "id": "evt_3b9e2c7a1f4d4e8a9b6c0d2e5f7a8c91",
      "type": "lead.first_message_sent",
      "api_version": "2026-10-18",
      "timestamp": "2026-10-26T07:30:00.000Z",
      "test": false,
      "data": {
        "organization_id": "2c8f4a61-7d3e-4b9a-8f21-6a0d5e9c3b17",
        "workspace": {
          "id": "3f6a9d21-8b4c-4e7f-a102-5c9e8d7b6a43",
          "name": "Основное пространство"
        },
        "campaign": {
          "id": "4d8b2f73-6e1a-4c95-b7d0-8a3f5e2c9b14",
          "name": "Пример кампании"
        },
        "lead": {
          "id": "5a7c3e92-1f4b-4d68-9c2e-7b0a6d4f8e31",
          "username": "example_user",
          "status": "contacted",
          "fields": {
            "record_id": null,
            "first_name": "Анна",
            "company": "ООО Пример"
          },
          "fields_truncated": false,
          "record_id_status": "unknown"
        },
        "account": {
          "id": "1d9f6b28-4c3e-4a75-8b19-5e7a2c0f6d83",
          "username": "example_sender",
          "name": "Мария из Примера"
        },
        "first_message_sent_at": "2026-10-26T07:30:00.000Z",
        "first_message": {
          "text": "Добрый день, Анна! Пишу из Примера: подскажите, кто у вас отвечает за аутрич?",
          "text_truncated": false,
          "text_unavailable": false,
          "text_unavailable_reason": null
        }
      }
    },
    {
      "object": "event",
      "id": "evt_8d4f1a6c2e9b4c7d8a3e5f0b1c6d9e27",
      "type": "lead.replied",
      "api_version": "2026-10-18",
      "timestamp": "2026-10-26T09:12:00.000Z",
      "test": false,
      "data": {
        "organization_id": "2c8f4a61-7d3e-4b9a-8f21-6a0d5e9c3b17",
        "workspace": {
          "id": "3f6a9d21-8b4c-4e7f-a102-5c9e8d7b6a43",
          "name": "Основное пространство"
        },
        "campaign": {
          "id": "4d8b2f73-6e1a-4c95-b7d0-8a3f5e2c9b14",
          "name": "Пример кампании"
        },
        "lead": {
          "id": "5a7c3e92-1f4b-4d68-9c2e-7b0a6d4f8e31",
          "username": "example_user",
          "status": "replied",
          "fields": {
            "record_id": null,
            "first_name": "Анна",
            "company": "ООО Пример"
          },
          "fields_truncated": false,
          "record_id_status": "unknown"
        },
        "account": {
          "id": "1d9f6b28-4c3e-4a75-8b19-5e7a2c0f6d83",
          "username": "example_sender",
          "name": "Мария из Примера"
        },
        "first_message_sent_at": null,
        "reply": {
          "id": "e4b7a2d9-6c1f-4e38-9a5b-0d8c3f7e1a62",
          "replied_at": "2026-10-26T09:12:00.000Z",
          "automation_stopped": null,
          "text": "Здравствуйте! Это я, давайте созвонимся завтра.",
          "text_truncated": null,
          "text_unavailable": false,
          "text_unavailable_reason": null
        }
      }
    }
  ],
  "has_more": false,
  "next_cursor": "v1.k1.Qm9hcmRfMWZ4.c3RyaWN0bHlfb3BhcXVl.dGFnX2V4YW1wbGU.bWFjX2V4YW1wbGVfdmFsdWU",
  "meta": {
    "available_from": "2026-10-20T08:00:00.000Z",
    "retention_days": 30,
    "collection": {
      "state": "running",
      "enabled_at": "2026-10-20T08:00:00.000Z",
      "pause_pending": false
    },
    "coverage": {
      "status": "unknown",
      "gaps": [],
      "gaps_truncated": false,
      "incomplete_from": null
    },
    "observed_at": "2026-10-27T09:15:42.000Z"
  }
}
```

### Пример ответа: С start=now

```json
{
  "object": "list",
  "data": [],
  "has_more": false,
  "next_cursor": "v1.k1.Qm9hcmRfMWZ4.c3RyaWN0bHlfb3BhcXVl.dGFnX2V4YW1wbGU.bWFjX2V4YW1wbGVfdmFsdWU",
  "meta": {
    "available_from": "2026-10-20T08:00:00.000Z",
    "retention_days": 30,
    "collection": {
      "state": "running",
      "enabled_at": "2026-10-20T08:00:00.000Z",
      "pause_pending": false
    },
    "coverage": {
      "status": "unknown",
      "gaps": [],
      "gaps_truncated": false,
      "incomplete_from": null
    },
    "observed_at": "2026-10-27T09:15:42.000Z"
  }
}
```
