## Список Follow-up

> **Метод выходит в обновлении `call 26.600.0` и доступен пока не на всех порталах Битрикс24.** Если обновление на ваш портал ещё не пришло, API вернёт `422 METHOD_NOT_YET_AVAILABLE` — это признак того, что метод на портале ещё не выпущен, а не ошибка интеграции.

`POST /v1/calls/followups/list`

Возвращает список AI Follow-up завершённых звонков за указанный период. Навигация по выдаче — курсорная.

## Поля запроса (body)

| Поле | Тип | Обяз. | Описание |
|------|-----|:-----:|---------|
| `filter` | object | да | Условия выборки. Без этого объекта запрос отклоняется |
| `filter.startDate.from` | string | да | Начало периода, ISO 8601 — `2026-01-01T00:00:00Z` |
| `filter.startDate.to` | string | да | Конец периода, ISO 8601. Значение раньше `from` отклоняется |
| `filter.participantId` | integer | нет | Выборка по одному участнику. Только для администратора портала: рядовому сотруднику фильтр по другому участнику недоступен. Список сотрудников — `GET /v1/users` |
| `select` | string[] | нет | Какие поля вернуть. Словарь значений — в разделе «Поле select» |
| `order` | object | нет | Сортировка по дате начала: `{ "startDate": "asc" }` или `{ "startDate": "desc" }`. По умолчанию — `desc` |
| `pagination` | object | нет | Размер страницы и курсор. Правила — в разделе «Навигация» |
| `pagination.limit` | integer | нет | Записей на страницу. По умолчанию 50, максимум 200. При выборе полей с большим объёмом данных максимум снижается до 20 |
| `pagination.afterCursor` | object | нет | Курсор следующей страницы. Копируется целиком из `data.afterCursor` предыдущего ответа. В первом запросе не передаётся |
| `mentionFormat` | string | нет | Формат `@`-упоминаний в текстовых полях: `bb`, `html` или `none`. По умолчанию — `bb` |

## Поле select

Без `select` возвращаются только метаданные звонка. Остальные поля запрашиваются явно. Неизвестное значение отклоняется с `422 BITRIX_ERROR` и сообщением, которое называет отклонённое поле.

### Метаданные

| Значение | Что вернётся |
|---|---|
| `callId`, `callType`, `initiatorId`, `startDate`, `endDate`, `durationSeconds` | Базовые поля. В списке возвращаются всегда |
| `uuid` | Идентификатор сессии звонка |
| `participants` | Список участников |
| `tracks` | Записи звонка со ссылками на скачивание |
| `outcomes` | Перечень готовых AI-блоков |
| `language` | Язык транскрипции |
| `version` | Версия схемы AI-данных |
| `createdAt` | Время последней AI-записи |

### AI-блоки

Блок запрашивается целиком или отдельным подполем через точку.

| Блок | Подполя |
|---|---|
| `transcription` | `transcription.language`, `transcription.segments` |
| `overview` | `overview.topic`, `overview.detailedTakeaways`, `overview.meetingType`, `overview.agenda`, `overview.agreements`, `overview.actionItems`, `overview.meetings` |
| `summary` | Запрашивается только целиком |
| `insights` | `insights.speakerEvaluationAvailable`, `insights.speakerAnalysis`, `insights.meetingStrengths`, `insights.meetingWeaknesses`, `insights.speechStyleInfluence`, `insights.engagementLevel`, `insights.areasOfResponsibility`, `insights.finalRecommendations` |
| `evaluation` | `evaluation.efficiencyValue`, `evaluation.calendar`, `evaluation.criteria` |

Правила выбора:

- Запрос блока целиком (`["overview"]`) возвращает все его подполя.
- Запрос подполя (`["overview.topic"]`) возвращает только это подполе, остальные опускаются.
- `transcription`, `transcription.segments`, `overview` и `insights` содержат много данных. Их выбор снижает максимальный размер страницы до 20 записей.

## Навигация

1. Первый запрос отправляется без `pagination.afterCursor`.
2. Если в ответе `hasMore` равно `true`, скопируйте `data.afterCursor` целиком в `pagination.afterCursor` следующего запроса.
3. Повторяйте, пока `hasMore` не станет `false`.

`pagination.limit` — по умолчанию 50, максимум 200. При выборе полей с большим объёмом данных максимум снижается до 20.

```jsonc
{ "pagination": { "limit": 50, "afterCursor": { "startDate": "2026-01-12T14:30:00.000000+00:00", "id": 12330 } } }
```

## Формат упоминаний

`mentionFormat` управляет тем, как выглядят `@`-упоминания сотрудников во всех текстовых AI-полях — `transcription.segments[].text`, `overview.*`, `insights.*`, `evaluation.criteria.*.thoughts`.

| Значение | Текст в ответе | Когда выбирать |
|---|---|---|
| `bb` | `[USER=7]Иван Петров[/USER]` | Вывод в интерфейсе, который понимает BB-код |
| `html` | `<span class="bx-call-mention" …>Иван Петров</span>` | Вывод в вебе |
| `none` | `Иван Петров` | Передача текста в нейросеть, поиск, экспорт |

## Примеры

Разбор встреч одного сотрудника за январь: берём метаданные звонка, тему, договорённости и задачи из обзора, разбор участников и общую оценку. Сортировка — от новых к старым, упоминания приходят чистым текстом. Это первый запрос, поэтому `afterCursor` в нём нет — курсор для следующей страницы придёт в ответе.

### curl — личный ключ

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/calls/followups/list \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {
      "startDate": { "from": "2026-01-01T00:00:00Z", "to": "2026-01-31T23:59:59Z" },
      "participantId": 42
    },
    "select": [
      "callId", "startDate", "durationSeconds", "participants", "outcomes",
      "overview.topic", "overview.agreements", "overview.actionItems",
      "insights.speakerAnalysis", "evaluation.efficiencyValue"
    ],
    "order": { "startDate": "desc" },
    "pagination": { "limit": 20 },
    "mentionFormat": "none"
  }'
```

### curl — OAuth-приложение

```bash
curl -X POST https://vibecode.bitrix24.tech/v1/calls/followups/list \
  -H "X-Api-Key: YOUR_APP_KEY" \
  -H "Authorization: Bearer USER_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {
      "startDate": { "from": "2026-01-01T00:00:00Z", "to": "2026-01-31T23:59:59Z" },
      "participantId": 42
    },
    "select": [
      "callId", "startDate", "durationSeconds", "participants", "outcomes",
      "overview.topic", "overview.agreements", "overview.actionItems",
      "insights.speakerAnalysis", "evaluation.efficiencyValue"
    ],
    "order": { "startDate": "desc" },
    "pagination": { "limit": 20 },
    "mentionFormat": "none"
  }'
```

### JavaScript — личный ключ

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/calls/followups/list', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_API_KEY',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    filter: {
      startDate: { from: '2026-01-01T00:00:00Z', to: '2026-01-31T23:59:59Z' },
      participantId: 42,
    },
    select: [
      'callId', 'startDate', 'durationSeconds', 'participants', 'outcomes',
      'overview.topic', 'overview.agreements', 'overview.actionItems',
      'insights.speakerAnalysis', 'evaluation.efficiencyValue',
    ],
    order: { startDate: 'desc' },
    pagination: { limit: 20 },
    mentionFormat: 'none',
  }),
})

const body = await res.json()

if (!body.success) {
  // Ошибку разбираем явно: иначе на 403 или 422 код упадёт на обращении к data
  throw new Error(`${body.error.code}: ${body.error.message}`)
}

const { data } = body

for (const call of data.items) {
  // AI-блоки готовы не у каждого звонка: у части из них приходит null
  const topic = call.overview?.topic ?? 'тема не определена'
  const score = call.evaluation?.efficiencyValue ?? '—'
  console.log(call.startDate, topic, `оценка: ${score}`)

  for (const item of call.overview?.actionItems ?? []) {
    console.log('  задача:', item.actionItem)
  }
}

// Следующая страница: курсор из ответа кладём в pagination.afterCursor нового запроса
console.log(data.hasMore, data.afterCursor)
```

### JavaScript — OAuth-приложение

```javascript
const res = await fetch('https://vibecode.bitrix24.tech/v1/calls/followups/list', {
  method: 'POST',
  headers: {
    'X-Api-Key': 'YOUR_APP_KEY',
    'Authorization': 'Bearer USER_SESSION_TOKEN',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    filter: {
      startDate: { from: '2026-01-01T00:00:00Z', to: '2026-01-31T23:59:59Z' },
      participantId: 42,
    },
    select: [
      'callId', 'startDate', 'durationSeconds', 'participants', 'outcomes',
      'overview.topic', 'overview.agreements', 'overview.actionItems',
      'insights.speakerAnalysis', 'evaluation.efficiencyValue',
    ],
    order: { startDate: 'desc' },
    pagination: { limit: 20 },
    mentionFormat: 'none',
  }),
})

const body = await res.json()

if (!body.success) {
  // Ошибку разбираем явно: иначе на 403 или 422 код упадёт на обращении к data
  throw new Error(`${body.error.code}: ${body.error.message}`)
}

const { data } = body
console.log(data.items, data.hasMore, data.afterCursor)
```

## Поля ответа

| Поле | Тип | Описание |
|------|-----|---------|
| `success` | boolean | Всегда `true` при успехе |
| `data.items` | object[] | Массив Follow-up. Поля элемента — в таблице ниже |
| `data.hasMore` | boolean | Есть ли записи за пределами текущей страницы |
| `data.afterCursor` | object \| null | Курсор следующей страницы. `null` — записей больше нет |

Поля элемента массива:

| Поле | Тип | Описание |
|------|-----|---------|
| `items[].callId` | number | Идентификатор звонка. Передаётся в [`GET /v1/calls/followups/:callId`](./get.md) |
| `items[].callType` | number | Вид звонка: `1` — мгновенный, `2` — конференция, `3` — большая комната |
| `items[].initiatorId` | number | Инициатор звонка. Карточка сотрудника — `GET /v1/users/:id` |
| `items[].startDate` | string | Начало звонка, ISO 8601 в UTC |
| `items[].endDate` | string \| null | Окончание звонка, ISO 8601 в UTC |
| `items[].durationSeconds` | number | Длительность звонка в секундах |
| `items[].uuid` | string | Идентификатор сессии звонка |
| `items[].language` | string | Код языка транскрипции |
| `items[].version` | number | Версия схемы AI-данных |
| `items[].participants` | object[] | Участники звонка |
| `items[].outcomes` | string[] | Готовые AI-блоки этого звонка |
| `items[].createdAt` | string | Время последней AI-записи |
| `items[].tracks` | object[] | Записи звонка |
| `items[].transcription` | object | Транскрипция разговора |
| `items[].overview` | object | Обзор встречи |
| `items[].summary` | object | Краткое изложение по фрагментам |
| `items[].evaluation` | object | Оценка эффективности встречи |
| `items[].insights` | object | Аналитические выводы и разбор участников |

### Участники — `participants[]`

| Поле | Тип | Описание |
|------|-----|---------|
| `userId` | number | Идентификатор сотрудника |
| `name` | string | Имя сотрудника |
| `avatar` | string | Ссылка на аватар |
| `workPosition` | string | Должность |
| `talkedSeconds` | number | Сколько секунд участник говорил |

### Записи звонка — `tracks[]`

| Поле | Тип | Описание |
|------|-----|---------|
| `trackId` | number | Идентификатор записи |
| `type` | string | Вид дорожки, например `mixed_audio` |
| `duration` | number | Длительность записи в секундах |
| `fileName` | string | Имя файла |
| `mimeType` | string | MIME-тип файла |
| `url` | string | Ссылка на скачивание |
| `dateCreate` | string | Когда запись создана, ISO 8601 |

### Транскрипция — `transcription`

| Поле | Тип | Описание |
|------|-----|---------|
| `language` | string | Код языка расшифровки |
| `segments[]` | object[] | Реплики в хронологическом порядке |
| `segments[].userId` | number | Кто говорит |
| `segments[].userName` | string | Имя говорящего |
| `segments[].start` | number | Начало реплики, секунды от начала звонка |
| `segments[].end` | number | Конец реплики, секунды от начала звонка |
| `segments[].text` | string | Текст реплики |

### Обзор встречи — `overview`

| Поле | Тип | Описание |
|------|-----|---------|
| `topic` | string | Тема встречи |
| `detailedTakeaways` | string | Развёрнутые выводы |
| `meetingType` | object | Тип встречи: `typeTag` — код, `title` — название, `explanation` — обоснование |
| `agenda` | object | Повестка: `explanation` — формулировка, `quote` — цитата из разговора |
| `agreements[]` | object[] | Договорённости: `agreement` — формулировка, `quote` — цитата |
| `actionItems[]` | object[] | Задачи: `actionItem` — формулировка, `quote` — цитата |
| `meetings[]` | object[] | Назначенные встречи: `meeting` — формулировка, `quote` — цитата |

### Краткое изложение — `summary`

| Поле | Тип | Описание |
|------|-----|---------|
| `segments[]` | object[] | Фрагменты разговора |
| `segments[].start` | number | Начало фрагмента, секунды |
| `segments[].end` | number | Конец фрагмента, секунды |
| `segments[].title` | string | Заголовок фрагмента |
| `segments[].summary` | string | Изложение фрагмента |

### Аналитические выводы — `insights`

| Поле | Тип | Описание |
|------|-----|---------|
| `speakerEvaluationAvailable` | boolean | Доступен ли разбор участников на портале |
| `speakerAnalysis[]` | object[] | Разбор участников, по убыванию `talkPercentage` |
| `speakerAnalysis[].userId` | number | Идентификатор сотрудника. Карточка — `GET /v1/users/:id` |
| `speakerAnalysis[].detailedInsight` | string | Вывод по участнику |
| `speakerAnalysis[].efficiencyValue` | number | Оценка эффективности участника, от 0 до 100 |
| `speakerAnalysis[].evaluationCriteria` | string | Критерий, по которому дана оценка |
| `speakerAnalysis[].talkPercentage` | number | Доля времени, которую участник говорил, в процентах |
| `speakerAnalysis[].duration` | number | Сколько секунд участник говорил |
| `speakerAnalysis[].durationFormat` | string | То же время в виде `ММ:СС` |
| `meetingStrengths[]` | object[] | Сильные стороны: `strengthTitle` и `strengthExplanation` |
| `meetingWeaknesses[]` | object[] | Слабые стороны: `weaknessTitle` и `weaknessExplanation` |
| `speechStyleInfluence` | string | Как манера речи повлияла на встречу |
| `engagementLevel` | string | Вовлечённость участников |
| `areasOfResponsibility` | string | Кто за что отвечает по итогам |
| `finalRecommendations` | string | Рекомендации к следующей встрече |

### Оценка эффективности — `evaluation`

| Поле | Тип | Описание |
|------|-----|---------|
| `efficiencyValue` | number | Общая эффективность встречи, от 0 до 100 |
| `calendar.overhead` | boolean | Была ли встреча избыточной по времени |
| `criteria` | object | Карта критериев. Ключ — код критерия, значение — объект с полями ниже |
| `criteria.<код>.value` | boolean | Выполнен ли критерий |
| `criteria.<код>.title` | string | Название критерия |
| `criteria.<код>.criteria` | string | Формулировка критерия |
| `criteria.<код>.thoughts` | string | Обоснование оценки |

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

Ответ на запрос из примеров выше. Базовые поля звонка приходят всегда, а из AI-блоков вернулось ровно то, что перечислено в `select`: внутри `overview` — только тема, договорённости и задачи, внутри `insights` — только разбор участников, внутри `evaluation` — только общая оценка. Упоминания сотрудников идут чистым текстом, потому что запрошен `mentionFormat: "none"`.

```json
{
  "success": true,
  "data": {
    "items": [
      {
        "callId": 12345,
        "callType": 1,
        "initiatorId": 7,
        "startDate": "2026-01-15T10:00:00+00:00",
        "endDate": "2026-01-15T10:42:00+00:00",
        "durationSeconds": 2520,
        "participants": [
          { "userId": 7, "name": "Иван Петров", "avatar": "https://example.bitrix24.ru/upload/avatar.png", "workPosition": "Руководитель проекта", "talkedSeconds": 600 },
          { "userId": 42, "name": "Мария Иванова", "avatar": "https://example.bitrix24.ru/upload/avatar-2.png", "workPosition": "Разработчик", "talkedSeconds": 1200 }
        ],
        "outcomes": ["transcription", "overview", "summary", "insights", "evaluation"],
        "overview": {
          "topic": "Планирование спринта",
          "agreements": [
            { "agreement": "В спринт берём только импорт каталога", "quote": "Предлагаю оставить только импорт каталога, остальное не успеем." }
          ],
          "actionItems": [
            { "actionItem": "Подготовить прототип к пятнице", "quote": "Тогда прототип показываем в пятницу." }
          ]
        },
        "insights": {
          "speakerAnalysis": [
            { "userId": 42, "detailedInsight": "Участник предложил сократить объём и обосновал это оценкой сроков.", "efficiencyValue": 82, "evaluationCriteria": "Конструктивность предложений", "talkPercentage": 66, "duration": 1200, "durationFormat": "20:00" },
            { "userId": 7, "detailedInsight": "Участник вёл встречу и зафиксировал договорённости.", "efficiencyValue": 74, "evaluationCriteria": "Управление обсуждением", "talkPercentage": 34, "duration": 600, "durationFormat": "10:00" }
          ]
        },
        "evaluation": {
          "efficiencyValue": 75
        }
      },
      {
        "callId": 12318,
        "callType": 2,
        "initiatorId": 42,
        "startDate": "2026-01-12T09:00:00+00:00",
        "endDate": "2026-01-12T09:25:00+00:00",
        "durationSeconds": 1500,
        "participants": [
          { "userId": 42, "name": "Мария Иванова", "avatar": "https://example.bitrix24.ru/upload/avatar-2.png", "workPosition": "Разработчик", "talkedSeconds": 900 }
        ],
        "outcomes": ["transcription", "overview"],
        "overview": {
          "topic": "Разбор обращений за неделю",
          "agreements": [],
          "actionItems": [
            { "actionItem": "Собрать частые вопросы в базу знаний", "quote": "Давай соберём повторяющиеся вопросы в одну статью." }
          ]
        },
        "insights": null,
        "evaluation": null
      }
    ],
    "hasMore": true,
    "afterCursor": { "startDate": "2026-01-12T09:00:00.000000+00:00", "id": 12318 }
  }
}
```

Второй звонок показывает частый случай: у него готовы не все AI-блоки. В `outcomes` перечислено то, что сформировано, а запрошенные, но отсутствующие блоки приходят как `null` — проверяйте их перед обращением к полям.

Как выглядит звонок со всеми заполненными блоками, показано на странице [Follow-up по одному звонку](./get.md).

## Пример ответа при ошибке

400 — в теле запроса нет объекта `filter`:

```json
{
  "success": false,
  "error": {
    "code": "MISSING_PARAMS",
    "message": "Required: filter.startDate.from/to (ISO 8601)"
  }
}
```

## Ошибки

| HTTP | Код | Описание |
|------|-----|---------|
| 400 | `MISSING_PARAMS` | В теле запроса нет объекта `filter` |
| 400 | `INVALID_PARAMS` | Значение поля запроса вне списка допустимых — например `mentionFormat`. Если Битрикс24 отклонил значение по полю, ответ дополнительно содержит массив `error.validation` с именем поля |
| 401 | `MISSING_API_KEY` | Не передан заголовок `X-Api-Key` |
| 401 | `TOKEN_MISSING` | У ключа нет токенов портала. Ключ OAuth-приложения требует заголовок `Authorization: Bearer` |
| 403 | `SCOPE_DENIED` | У ключа нет скоупа `call` |
| 403 | `BITRIX_ACCESS_DENIED` | Битрикс24 отказал в доступе. Частый случай — набор скоупов, с которым ключ обращается к порталу, не содержит `call`. Полный разбор причин и что делать по типу ключа — [Ошибки](/docs/errors#bitrix_access_denied-403) |
| 422 | `METHOD_NOT_YET_AVAILABLE` | Обновление `call 26.600.0` на портале ещё не выпущено. Ответ содержит поле `error.release` со значением `call 26.600.0` |
| 422 | `BITRIX_ERROR` | Запрос отклонён: некорректный диапазон дат, недопустимое поле в `select`, некорректные `pagination` или `order`, нет доступа к данным. Причина — в `error.message` |
| 429 | `RATE_LIMITED` | Превышена частота запросов на стороне Битрикс24 |
| 429 | `QUEUE_OVERFLOW`, `QUEUE_TIMEOUT` | Очередь запросов портала переполнена или запрос не дождался очереди. Заголовок `Retry-After` подсказывает задержку перед повтором |
| 503 | `BITRIX_TIMEOUT` | Битрикс24 принял запрос, но не ответил за 15 секунд. Для чтения повтор безопасен |
| 502 | `BITRIX_UNAVAILABLE` | Битрикс24 недоступен |

Полный список общих ошибок API — [Ошибки](/docs/errors).

## Известные особенности

**Звонки без AI-обработки в список не попадают.** В выдаче только те звонки, по которым Follow-up уже сформирован. Пустой массив `items` означает, что за период таких звонков нет.

**Неполный период отклоняется кодом `422`, а не `400`.** Код `400 MISSING_PARAMS` приходит только тогда, когда объекта `filter` нет в теле запроса вовсе. Пропущенный `to` или период, где `from` позже `to`, возвращают `422 BITRIX_ERROR` с описанием причины.

**Разбор участников доступен не на каждом портале.** Если на портале он недоступен, блок `insights` приходит с `speakerEvaluationAvailable: false` и пустым разбором участников. Это не ошибка запроса — структура ответа не меняется.

**Выдача ограничена правами сотрудника.** Ключ возвращает Follow-up тех звонков, в которых владелец ключа участвовал или состоит в связанном чате. Администратор портала видит все звонки портала. Поэтому один и тот же период у разных сотрудников даёт разные списки.

## Смотрите также

- [Follow-up по одному звонку](/docs/calls/followup/get)
- [Follow-up звонков](/docs/calls/followup)
- [Звонки](/docs/calls)
- [Ошибки](/docs/errors)
