# Инструменты MCP

## Инструменты: формат ответа, права и тарифы {#tools-overview}

### Инструменты (Tools)

Tools — действия, которые выполняются на стороне WebAsk по запросу ИИ-модели.

### Формат ответа инструментов

Каждый инструмент возвращает объект с полями:

| Поле | Тип | Описание |
|------|-----|----------|
| `content` | `array` | Массив блоков для отображения: `[{"type": "text", "text": "сообщение"}]`. Текст сообщения об успехе или об ошибке. |
| `isError` | `boolean` | `false` — успех, `true` — ошибка (валидация, доступ, сохранение и т.д.). |
| *(доп. ключи)* | — | При успехе добавляются поля с данными (например `quiz`, `quiz_id`, `url`, `updated`). При ошибке структуры виджетов может быть массив `errors`. |

В примерах ниже приведён **полный** успешный ответ (включая `content` и `isError` и все возвращаемые данные).

**Пример ответа с ошибкой:**
```json
{
  "content": [{"type": "text", "text": "Ошибка валидации: Поле quiz_id обязательно для заполнения."}],
  "isError": true
}
```

---

### Права и тарифы

Инструменты записи проверяют то же, что и интерфейс конструктора.

**Права участника воркспейса.** Владелец воркспейса может выдать участнику урезанные права. Если права на действие нет, инструмент отвечает отказом, из которого видно, что делать дальше:

- имя права и его код — `Недостаточно прав: для этого действия нужно право «Удаление опросов» (delete_quiz)`;
- значение у роли: `allowed` (разрешено, но объект в закрытой папке), `partially` — только свои объекты, причём над чужим объектом это названо прямо, `forbidden` — запрещено, не задано — считается запрещённым;
- отличие по папке, если для папки объекта у роли стоит своё значение;
- причина, из-за которой права не действуют вовсе: приглашение не принято, почта не подтверждена, не участник;
- кто может право выдать: владелец или участник с `member_crud`, инструменты `save_workspace_role` и `set_workspace_member_role`.

Свои права по всем пространствам ассистент читает заранее из `user://me` (`workspaces[].access`), права другого участника — `get_workspace_member_access`. Расчёт там тот же, что у проверки при вызове.

**Тарифный отказ** называет закрытую возможность и дописывает, где посмотреть текущий тариф (`get_workspace_tariff` — лимиты и возможности) и что дают другие (`get_tariff_list`); сменить тариф может владелец или участник с правом `payment`.

Таблица прав по инструментам:

| Действие | Требуемое право |
|---|---|
| `create_quiz` | `create_quiz` |
| `duplicate_quiz` | `copy_quiz` |
| `delete_quiz` | `delete_quiz` |
| `rename_quiz` | `rename_quiz` |
| `archive_quiz` | `quiz_archive` |
| `move_quiz` | `move_in_folder` |
| `update_quiz_settings` | `settings` |
| `make_quiz_template` | `template` |
| `create_folder` | `create_folder` |
| `manage_folder_access` (add, remove) | `invite_members` |
| `set_answer_note` | `settings` |
| `manage_workspace_folder` (rename) | `rename_folder` |
| `manage_workspace_folder` (delete) | `delete_folder` |
| `get_quiz_versions`, `restore_quiz_version` | `settings` |
| `manage_quiz_passwords` | `settings` |
| `manage_promocode_group`, `get_promocode_list_quizzes` | `settings` |
| `get_answer_extra_field_values` | `view_results` |
| `generate_ai_report`, `get_ai_report`, `share_ai_report` | `view_results` |
| `get_quiz_integrations`, `get_integration_logs`, `resend_integration_logs`, `manage_quiz_messenger`, `manage_quiz_crm`, `get_quiz_crm_fields`, `manage_quiz_crm_mapping`, `manage_quiz_amocrm_mapping`, `manage_quiz_google_sheets` | `integrations` |
| `manage_quiz_analytics`, `manage_quiz_zapier` | `integrations` |
| `manage_workspace_smtp` | `account_smtp` |
| `manage_workspace_domain` | `domain` |
| `export_quiz_print`, `manage_quiz_qr_code`, `manage_quiz_payment` | `settings` |
| `manage_workspace_files` (чтение) | `files_view` |
| `manage_workspace_files` (удаление) | `files_delete` |
| `get_respondent_campaigns` | только доступ к аккаунту |
| `get_mailing_state` | `mailing_view` |
| `update_user_profile`, `manage_user_sessions` | своё, права аккаунта не спрашиваются |
| `create_quiz_from_template` | `create_quiz` |
| `restore_quiz` | `delete_quiz` |
| `manage_theme` (copy, restore) | `theme` |
| `manage_theme` (delete) | `theme_delete` |
| `duplicate_quiz` (с `folder_id`) | `copy_in_folder` |
| `export_answers_spss` | `view_results` |
| `create_theme`, `update_theme`, `apply_quiz_theme` | `theme` |
| `upload_quiz_media`, `upload_quiz_widget_image` | `files_upload` |
| `update_workspace_branding`, `upload_workspace_logo`, `delete_workspace_logo` | `account_quiz_settings` |
| `update_workspace_report_palette` | `account_quiz_settings` |
| промокоды: `create_promocode_group`, `add_promocodes`, `get_promocode_list`, `get_promocode_codes` | `settings` |
| результаты: чтение ответов и отчётов, выгрузки, сводка, публичные ссылки, `tag_answer`, `toggle_answer_visibility`, `save_quiz_report_filters` | `view_results` |
| запись на встречи: правка расписаний, записей и блокировок | `settings` |
| запись на встречи: чтение расписаний и журнала | `view_results` |
| результаты: чтение ответов и отчётов, выгрузки, сводка, публичные ссылки, `tag_answer`, `toggle_answer_visibility`, `save_quiz_report_filters`, `set_answers_order_mode` | `view_results` |

Правка виджетов, логики и текстов опроса отдельного права не требует — как и в конструкторе.

**Тарифные ограничения.** Проверяются до выполнения действия:

| Действие | Ограничение |
|---|---|
| `create_quiz`, `duplicate_quiz` | лимит числа опросов по тарифу; ошибка называет лимит и текущее значение |
| `create_theme`, `update_theme` | своя тема недоступна на бесплатном тарифе |
| `upload_quiz_media`, `upload_quiz_widget_image` | загрузка файлов и дисковая квота |
| `upload_workspace_logo`, `delete_workspace_logo` | свой логотип доступен не на всех тарифах |
| `update_workspace_branding` | скрытие копирайта доступно не на всех тарифах |
| экспорт ответов и отчётов | выгрузка результатов |
| промокоды: создание группы, добавление кодов, чтение списков | промокоды доступны не на всех тарифах |

---


## Опросы: создание, публикация, версии {#tools-quiz-lifecycle}

### Управление опросами

### `create_quiz`
Создаёт новый опрос.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Опрос успешно создан (ID: 1)."}],
  "isError": false,
  "quiz": {
    "id": 1,
    "workspace_id": 5,
    "answer_count": 0,
    "visits": {"visit": 0, "u_visit": 0},
    "ssid": "...",
    "shared": false,
    "isPublished": false,
    "is_archive": false,
    "is_template": false,
    "name": "Новый опрос",
    "virtual_id": null,
    "url_preview": "https://...",
    "url_shared": "https://...",
    "project": null,
    "quiz_data": {"ids": [], "entities": {}},
    "conditions": null,
    "settings": {"lang": "ru", "show_progressbar": true, "show_navigate": true, "numeration": false, "theme_id": 1, "..." : "..."},
    "info": {"info_title": "", "description": "", "og_image": null, "favicon": null},
    "integrations": {}
  }
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `folder_id` | `integer` | (Обязательный) ID папки, в которой будет создан опрос. |

### `rename_quiz`
Переименовывает существующий опрос.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Опрос #1 переименован в \"Новое название опроса\"."}],
  "isError": false,
  "quiz_id": 1,
  "name": "Новое название опроса"
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `name` | `string` | (Обязательный) Новое название опроса (макс. 80 символов). |

### `duplicate_quiz`
Создаёт копию опроса со всеми данными и версиями.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Опрос #1 успешно дублирован (новый ID: 2)."}],
  "isError": false,
  "quiz": {
    "id": 2,
    "name": "Копия опроса",
    "url_shared": "https://...",
    "is_published": false,
    "created_at": "2024-01-15T12:00:00.000000Z",
    "updated_at": "2024-01-15T12:00:00.000000Z"
  }
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса для дублирования. |
| `name` | `string` | (Опционально) Новое название опроса (макс. 80 символов). |
| `folder_id` | `integer` | (Опционально) Папка для копии. По умолчанию копия ложится рядом с оригиналом; копирование в другую папку требует права `copy_in_folder`. |

### `archive_quiz`
Архивирует или разархивирует опрос.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Опрос архивирован."}],
  "isError": false,
  "quiz_id": 1,
  "archive_at": "2024-01-15 12:00:00",
  "is_archived": true
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `archive` | `boolean` | (Обязательный) `true` — архивировать, `false` — разархивировать. |

### `delete_quiz`
Удаляет опрос (мягкое удаление, с возможностью восстановления).
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Опрос #1 удален."}],
  "isError": false,
  "quiz_id": 1
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |

### `restore_quiz`
Возвращает удалённый опрос из корзины — с вопросами, логикой и ответами.

Опрос снова занимает место в тарифном лимите, поэтому при исчерпанном лимите возврат не пройдёт. Живой и чужой опрос в возврат не попадают, о пропущенных приходит `skipped_quiz_ids`. Архив — другое: его снимает `archive_quiz`.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `workspace_id` | `integer` | (Обязательный) ID workspace. |
| `quiz_ids` | `array` | (Обязательный) ID опросов, до 100 за раз. |

### `publish_quiz`
Публикует опрос: текущая локальная версия становится публичной.

Опрос под оплаченной кампанией за респондентов не публикуется и не откатывается к
прежней версии — инструмент отказывает и называет причину. Подробности — в
[tools-quiz-content.md](/dev/api/mcp-tools#tools-quiz-content).
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Опрос #1 успешно опубликован."}],
  "isError": false,
  "quiz": {
    "id": 1,
    "workspace_id": 5,
    "answer_count": 0,
    "name": "Мой опрос",
    "url_shared": "https://...",
    "isPublished": true,
    "quiz_data": {"ids": ["uuid-1"], "entities": {"uuid-1": {"type": "question", "title": "Вопрос"}}},
    "conditions": {},
    "settings": {"lang": "ru", "theme_id": 1, "..." : "..."},
    "info": {},
    "integrations": {}
  }
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |

### `move_quiz`
Перемещает опрос в другую папку пользователя.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Опрос перемещен в папку #5."}],
  "isError": false,
  "quiz_id": 1,
  "folder_id": 5
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `folder_id` | `integer` | (Обязательный) ID целевой папки (`folder://list`). |

### `make_quiz_template`
Создаёт шаблон на основе текущего опроса.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Шаблон успешно создан."}],
  "isError": false,
  "quiz_id": 1
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |

### `get_quiz_versions`
Показывает историю публикаций опроса: каждая публикация оставляет снимок.

Черновик в список не попадает — это текущее состояние, а не история. Снимок с `is_live` — тот, который сейчас видят респонденты.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `limit` | `integer` | Сколько снимков вернуть, 1–100. По умолчанию 20. |
| `offset` | `integer` | Сколько снимков пропустить. Список от новых к старым. |

### `restore_quiz_version`
Возвращает содержимое прежнего снимка в черновик — откат неудачной правки.

Респондентов откат сразу не касается: живая версия остаётся прежней, пока опрос не опубликуют заново (`publish_quiz`). В ответе приходит `needs_publish: true`.

**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Версия восстановлена в черновик. Чтобы её увидели респонденты, опубликуйте опрос."}],
  "isError": false,
  "quiz_id": 42,
  "restored_from_version_id": 128,
  "live_version_id": 128,
  "needs_publish": true
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `version_id` | `integer` | (Обязательный) ID снимка из `get_quiz_versions`. |

### `generate_ai_quiz`
Запускает AI-генерацию опроса по пользовательскому описанию. Использует тот же backend flow, что и встроенное создание AI-опроса в приложении: модель сама собирает структуру, виджеты и (опционально) логику переходов.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "AI-опрос успешно создан."}],
  "isError": false
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `folder_id` | `integer` | (Обязательный) ID папки, в которой будет создан опрос (`folder://list`). |
| `prompt` | `string` | (Обязательный) Свободное описание того, какой опрос нужно сгенерировать. |
| `ai_type` | `string` | Тип AI-опроса: `quiz` (по умолчанию) или `test`. |
| `with_logic` | `boolean` | Просить ли AI построить логику переходов между шагами. |
| `project` | `string` | Произвольная метка проекта (опционально). |

---

### `get_workspace_templates`
Собственные шаблоны аккаунта — те опросы, что сделали шаблонами через `make_quiz_template`.

Это не общий каталог сервиса, его ищет `search_quiz_templates`. Возвращённый id принимает `create_quiz_from_template` наравне с каталожным.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `workspace_id` | `integer` | (Обязательный) ID workspace. |
| `limit` | `integer` | Сколько вернуть, 1–100. По умолчанию 20. |
| `offset` | `integer` | Сколько пропустить. |

### `search_quiz_templates`
Ищет опрос в каталоге готовых. Каталог собран на `ru` и `en` — других языков в нём нет.

Без `query` возвращает каталог целиком. Отдаёт `template_id`, название и метки — без оформления, чтобы ответ оставался читаемым.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `lang` | `string` | (Обязательный) `ru` или `en`. |
| `query` | `string` | Поиск по названию, описанию и меткам. |
| `limit` | `integer` | Сколько вернуть, 1–100. По умолчанию 30. |

### `create_quiz_from_template`
Создаёт свой опрос из шаблона каталога — с вопросами, логикой и оформлением.

Личная тема автора шаблона копируется в аккаунт, чтобы опрос не ссылался на чужую запись. Опрос создаётся черновиком (`needs_publish: true`). Скопировать по `template_id` произвольный чужой опрос нельзя: берутся только те, что лежат в каталоге.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `template_id` | `integer` | (Обязательный) ID шаблона из `search_quiz_templates`. |
| `folder_id` | `integer` | (Обязательный) Папка для нового опроса (`folder://list`). |
| `name` | `string` | Своё название. По умолчанию — название шаблона. |


## Содержимое и структура опроса {#tools-quiz-content}

### Содержимое и структура опроса

**Заморозка опроса кампанией.** Пока по опросу идёт оплаченная кампания за респондентов,
его состав менять нельзя: респонденты проходят то, что оплачено и промодерировано.
`update_quiz_widgets`, `update_quiz_texts`, `update_quiz_logic`, а также `publish_quiz` и
`restore_quiz_version` из [tools-quiz-lifecycle.md](/dev/api/mcp-tools#tools-quiz-lifecycle) в этом случае
отказывают с пояснением про кампанию. `update_quiz_settings`, интеграции, темы и работа с
ответами остаются доступны — так же, как в конструкторе. До оплаты кампании правки открыты:
сумма пересчитывается перед платежом. Замок снимается сам по завершении кампании.

### `update_quiz_widgets`
Сохраняет структуру виджетов опроса. Используется для изменения/добавления любых элементов формы.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Структура виджетов опроса успешно обновлена."}],
  "isError": false,
  "quiz": {
    "id": 1,
    "workspace_id": 5,
    "answer_count": 0,
    "name": "Мой опрос",
    "url_shared": "https://...",
    "isPublished": false,
    "quiz_data": {
      "ids": ["a1b2c3d4-..."],
      "entities": {
        "a1b2c3d4-...": {"type": "question", "title": "Текст вопроса", "..." : "..."}
      }
    },
    "conditions": {},
    "settings": {"lang": "ru", "theme_id": 1, "show_progressbar": true, "..." : "..."},
    "info": {},
    "integrations": {}
  }
}
```
При ошибке структуры виджетов дополнительно возвращается массив `errors` (список строк с описанием несоответствий).
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `ids` | `array` | (Обязательный) Массив UUID виджетов (порядок на странице). |
| `entities` | `object` | (Обязательный) Ключ-значение: `{"uuid": {настройки виджета}}`. |

**Запись идёт всей структурой целиком.** Внутри виджета можно передать только изменённые поля — остальные не затрутся. Но состав виджетов передаётся полностью: каждый id из `ids` должен быть в `entities`, а виджет, которого нет в `ids`, из опроса удаляется. Поэтому правка одного поля — это три шага:

1. `get_quiz_structure(quiz_id)` — получить `ids` и `entities`;
2. поправить нужное поле в нужной сущности;
3. `update_quiz_widgets(quiz_id, ids, entities)` — вернуть структуру обратно.

Структуру, полученную чтением, можно возвращать записью как есть: ничего вычищать из неё не нужно.

#### Экраны опроса

| Тип | Что это | Как добавить |
|-----|---------|--------------|
| `welcome` | Экран приветствия | Служебный id `welcome`, всегда первый в `ids` |
| `submit` | Экран отправки с кнопкой «Отправить» | Служебный id `submit`, всегда последний в `ids` |
| `finish` | **Экран благодарности** («спасибо», thank you) | Новый UUID в `ids` перед `submit` + сущность с `type: "finish"` |
| `screenout` | Экран отсева | Новый UUID в `ids` + сущность с `type: "screenout"`, переход задаётся `update_quiz_logic` |

Экрана благодарности в опросе по умолчанию нет. Правка полей `submit` его не создаёт — нужен отдельный виджет `finish`.

#### Спецответы вместо обычных вариантов

«Другое / Свой вариант», «Затрудняюсь ответить» и «Ничего из перечисленного» — это настройки виджета, а не пункты списка вариантов:

| Спецответ | Настройка | Типы |
|-----------|-----------|------|
| Другое / Свой вариант | `choiceTextOther` | `choice` с текстовыми вариантами |
| Другое | `dropdownOther` | `dropdown` |
| Затрудняюсь ответить | `hardToAnswer` + `hardToAnswerLabel` | все, кроме экранов, `html`, `message`, `inline_group` |
| Ничего из перечисленного | `noneOfAbove` + `noneOfAboveLabel` | `choice`, `dropdown` |

Такой пункт, добавленный в `choiceTextEntities` / `dropdownEntities` руками, не даёт поля свободного ввода, не снимает остальные отметки, не попадает в отдельные показатели отчёта (`hardToAnswerCount`, `hardToAnswerPercent`) и перемешивается вместе с обычными вариантами. Если настройка при этом включена — респондент видит два одинаковых пункта.

Инструмент такие подписи не запрещает (бывают и осознанные варианты ответа), но возвращает предупреждение в массиве `warnings`.

#### Проверка результата

Ответ содержит ключ `saved` с фактическим составом опроса после записи — по нему сверяется, что изменение применилось, без повторного чтения структуры:

```json
{
  "content": [{"type": "text", "text": "Структура виджетов опроса успешно обновлена. Сохранено виджетов: 5. Состав: welcome x1, choice x2, finish x1, submit x1."}],
  "isError": false,
  "saved": {"widgets": 5, "types": {"welcome": 1, "choice": 2, "finish": 1, "submit": 1}},
  "warnings": ["Виджет ...: вариант «Другое» повторяет включённый спецответ (choiceTextOther) ..."],
  "quiz": {"...": "..."}
}
```

### `update_quiz_texts`
Сохраняет пользовательские тексты кнопок и интерфейсных элементов опроса. (**Требуется платный тариф**).
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Пользовательские тексты опроса успешно обновлены."}],
  "isError": false,
  "message": "OK"
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `texts` | `array` | (Обязательный) Массив объектов `[{"code": "string", "text": "string"}]`. |

### `update_quiz_settings`
Обновляет настройки опроса (название, язык, отображение, навигация, ограничения по времени и числу ответов, капча, тема, скрипты, QR-код, таймер заполнения, IP/устройства и др.). Передаются только те поля, которые нужно изменить.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Настройки опроса обновлены."}],
  "isError": false,
  "updated": ["name", "lang"]
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `name` | `string` | Название опроса (макс. 255 символов). |
| `lang` | `string` | Язык интерфейса опроса: `ru`, `en`, `es` или `pt`. |
| `show_progressbar` | `boolean` | Показывать прогресс-бар прохождения опроса. |
| `show_navigate` | `boolean` | Показывать навигацию (кнопки назад/вперёд). |
| `numeration` | `boolean` | Включить нумерацию вопросов. |
| `auto_move_by_enter` | `boolean` | Переход к следующему вопросу по нажатию Enter. |
| `show_to_robots` | `boolean` | Доступность страницы опроса для поисковых роботов. |
| `show_by_one` | `boolean` | Показывать по одному вопросу на экране. |
| `allow_multiple_answer`| `boolean`| Разрешить повторную отправку ответа (несколько прохождений). |
| `scripts_in_head` | `string` | Код скриптов в `<head>`. |
| `scripts_in_body` | `string` | Код скриптов в `<body>`. |
| `scripts_in_head_enabled` | `boolean` | Включить выполнение скриптов в head. |
| `scripts_in_body_enabled` | `boolean` | Включить выполнение скриптов в body. |
| `closed_quiz_message_enabled`| `boolean`| Включить сообщение о закрытии опроса. |
| `close_quiz_enabled` | `boolean` | Опрос закрыт (приём ответов отключён). |
| `limit_time_enabled`| `boolean` | Включить ограничение по времени доступности опроса. |
| `limit_time_value` | `string` | Дата/время окончания доступности. |
| `limit_time_value_tz`| `string` | Значение времени с таймзоной. |
| `limit_time_timezone`| `string` | Таймзона для ограничения по времени. |
| `limit_answer_count_enabled`| `boolean`| Включить ограничение максимального числа ответов. |
| `limit_answer_count_value` | `integer`| Максимальное число ответов. |
| `limit_message_title`| `string`| Заголовок сообщения при достижении лимита ответов. |
| `limit_message_body`| `string` | Текст сообщения при достижении лимита ответов. |
| `show_captcha` | `boolean` | Показывать капчу при отправке ответа. |
| `captcha_public_key` | `string` | Публичный ключ капчи. |
| `captcha_secret_key` | `string` | Секретный ключ капчи. |
| `multiple_ip` | `boolean` | Разрешить прохождение с нескольких IP. |
| `use_password` | `boolean` | Требовать пароль для доступа к опросу. |
| `session_check_method`| `array` | Методы проверки сессии: `ip`, `cookie`, `extra_params`. |
| `theme_id` | `integer` | ID темы оформления воркспейса. |
| `extra_fields` | `array` | Список скрытых переменных (extra fields). |
| `scoring_switcher` | `boolean` | Включить переключатель подсчёта баллов в опросе. |
| `block_jump_to_prev_widget`| `boolean`| Запретить переход к предыдущему вопросу (виджету). |
| `question_required_mode` | `string`| Режим обязательности вопросов: `individual`, `all_required`, `all_optional`. |
| `question_min_view_mode` | `string`| Минимальное время просмотра вопроса: `individual`, `all_enabled`, `all_disabled`. |
| `question_min_view_seconds` | `integer`| Минимальное время просмотра в секундах (при `all_enabled`). |
| `question_random_order`| `boolean`| Показывать вопросы в случайном порядке. |
| `disable_auto_redirect`| `boolean`| Отключить автоматический переход после ответа. |
| `auto_save_progress` | `boolean` | Автосохранение прогресса: респондент может вернуться и продолжить с того же места. |
| `allow_restart_progress`| `boolean`| Разрешить вернувшемуся респонденту начать прохождение заново вместо продолжения незавершённого. Работает только вместе с `auto_save_progress`. |
| `filling_time_enabled`| `boolean`| Включить таймер времени на заполнение опроса. |
| `filling_time_seconds`| `integer`| Время на заполнение в секундах. |
| `ip_list_enabled` | `boolean` | Включить фильтрацию по спискам IP (белый/чёрный список). |
| `ip_whitelist` | `string` | Белый список IP. |
| `ip_blacklist` | `string` | Чёрный список IP. |
| `available_devices` | `array` | Доступные устройства: `desktop`, `mobile`, `tablet`. |
| `info_title` | `string` | Заголовок (мета). |
| `description` | `string` | Описание опроса (мета). |
| `qr_code_format` | `string` | Формат QR-кода. |
| `qr_code_size` | `integer` | Размер QR-кода в пикселях. |
| `qr_code_color` | `string` | Цвет QR-кода. |
| `qr_code_background_color`|`string`| Цвет фона QR-кода. |
| `qr_code_margin` | `integer` | Отступ QR-кода. |
| `is_change_theme` | `boolean` | Менять тему оформления по данным из QR-кода. |
| `report_merge_versions` | `boolean` | Склеивать версии вопросов в отчёте: ответы, собранные разными версиями опроса, показываются одним блоком. |
| `multilogic_sequential_mode` | `boolean` | Последовательная мульти-логика: несколько групп условий одного вопроса применяются по порядку, а не только первая сработавшая. |
| `matrix_transpose` | `boolean` | Транспонировать матричные вопросы в отчёте: строки и столбцы меняются местами. |
| `question_random_mode` | `string` | Что перемешивать при случайном порядке: all — всю анкету, groups — только вопросы, отмеченные полем randomGroupId (каждый набор перемешивается по своим позициям). По умолчанию all. |
| `results_appearance` | `array` | Оформление отчёта этого опроса: palette_id, custom_colors, bar_color_mode, font_family, show_value_labels, show_grid_lines. null возвращает стандартный вид. Передача этого объекта заодно выключает results_appearance_use_shared — иначе цвета не применились бы, потому что общая палитра аккаунта имеет приоритет. Допустимые значения и текущая палитра аккаунта: get_workspace_report_appearance. |
| `results_appearance_use_shared` | `boolean` | Следовать общей палитре отчётов аккаунта (update_workspace_report_palette). true — опрос рисует отчёты палитрой аккаунта; свои цвета опроса остаются на месте и вернутся, если выключить. |
| `report_row_options` | `array` | Настройки строк отчёта: sort_mode (default, asc, desc) и value_format (both, count, percent). null возвращает стандартное поведение. |
| `schedule_enabled` | `boolean` | Включить доступ к опросу по расписанию (дни недели + интервал времени). |
| `schedule_days` | `array` | Дни недели, в которые опрос доступен: массив чисел 1–7 по ISO (1 — понедельник, 7 — воскресенье). |
| `schedule_time_from` | `string` | Начало интервала доступности в формате H:i (например "09:00"). Трактуется в таймзоне schedule_timezone. |
| `schedule_time_to` | `string` | Конец интервала доступности в формате H:i (например "18:00"). Трактуется в таймзоне schedule_timezone. |
| `schedule_timezone` | `string` | Таймзона расписания, идентификатор IANA (например "Europe/Moscow"). |
| `yandex_analytics_code` | `string` | Номер счётчика Яндекс.Метрики (макс. 100 символов). |
| `yandex_analytics_is_webvisor` | `boolean` | Включить Вебвизор в Яндекс.Метрике. |
| `yandex_analytics_is_active` | `boolean` | Включить отправку событий в Яндекс.Метрику. |
| `google_analytics_code` | `string` | Идентификатор Google Analytics (макс. 100 символов). |
| `google_analytics_version` | `string` | Версия Google Analytics (макс. 20 символов). |
| `google_analytics_is_active` | `boolean` | Включить отправку событий в Google Analytics. |
| `vk_pixel_code` | `string` | Идентификатор пикселя VK (макс. 100 символов). |
| `vk_pixel_is_active` | `boolean` | Включить пиксель VK. |
| `fb_pixel_code` | `string` | Идентификатор пикселя Facebook (макс. 100 символов). |
| `fb_pixel_is_active` | `boolean` | Включить пиксель Facebook. |
| `sso_active` | `boolean` | Требовать вход через SSO перед прохождением опроса. |
| `sso_allow_retake` | `boolean` | Разрешать SSO-респонденту пройти опрос повторно. |
| `answers_allow_edit` | `boolean` | Разрешить респонденту редактировать уже отправленный ответ. Работает только вместе с SSO (sso_active) или подтверждением e-mail (require_email_verification) — иначе ссылка на редактирование не выдаётся. |
| `require_email_verification` | `boolean` | Требовать подтверждение e-mail перед отправкой ответа. |
| `anonymous_responses` | `boolean` | Анонимные ответы: не сохранять данные, идентифицирующие респондента. |
| `anonymous_responses_show_label` | `boolean` | Показывать респонденту пометку о том, что опрос анонимный. |
| `category_scoring` | `boolean` | Включить категорийный скоринг: баллы опций суммируются по категориям, победившая категория пишется в ответ. |
| `quiz_categories` | `array` | Список категорий для категорийного скоринга: массив объектов { "id": "<строка>", "name": "<название>" }. Порядок значим: при равенстве баллов побеждает категория, идущая раньше. Веса категорий задаются у опций виджетов, а не здесь. |

### `update_quiz_logic`
Сохраняет логические условия, переходы и скоринг (баллы). Формируется на основе UUID виджетов.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Логика опроса успешно обновлена."}],
  "isError": false,
  "quiz": {
    "id": 1,
    "name": "Мой опрос",
    "quiz_data": {"ids": [], "entities": {}},
    "conditions": {"uuid-1": "uuid-2"},
    "settings": {},
    "info": {}
  }
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `defaultConditions`| `object` | Переходы по умолчанию — куда идти, если ни одно условие не сработало. Ключ — UUID виджета. Значение — UUID цели строкой либо объектом `{"widgetId": "uuid"}`. Цель `submit` завершает опрос. |
| `savedLogic` | `object` | Разветвлённая логика по ответам: `{"uuid": {"rules": [...]}}`. |
| `savedScoring` | `object` | Назначение баллов за варианты ответов. |
| `multiLogicFlags` | `object` | Флаги логики для конкретных виджетов. |

**Три правила, без которых маршрут собирается неверно.**

**1. Безусловный переход задаётся через `defaultConditions`, а не пустой группой условий.**

Группа из `savedLogic` с пустым `conditionsData` не означает «переходить всегда» — наоборот, такая группа отбрасывается как всегда-истинная, и вопрос уходит к следующему по порядку. Если из вопроса всегда нужно вести в одно место, это `defaultConditions`:

```json
{
  "defaultConditions": { "uuid-вопроса": {"widgetId": "uuid-цели"} },
  "savedLogic": { "uuid-вопроса": [ {"conditionGroupId": "g1", "jumpTo": {"widgetId": "uuid-цели"}, "conditionsData": [ ... ]} ] }
}
```

В `savedLogic` держите только переходы **по условию**: у каждой группы должно быть хотя бы одно условие в `conditionsData`.

**2. Циклы запрещены.**

Переход, который замыкает маршрут в кольцо (например, «вернуть на вопрос о формате участия» из вопроса ниже по потоку), инструмент отклоняет с ошибкой о зацикленном переходе. Это не ограничение MCP: конструктор при цикле отключает кнопку публикации, то есть такой опрос нельзя выпустить в принципе.

Если по замыслу человек должен что-то выбрать заново — заведите отдельный вопрос ниже по потоку вместо возврата к пройденному.

Переход **назад** сам по себе разрешён, если он не создаёт кольца: инструмент вернёт предупреждение о слиянии веток, но сохранит логику.

**3. Дисквалификация по квоте — это группа без условий.**

Квота — это группа с `actionType` = `screenout`, режимом `answer_quota` и **пустым** `conditionsData`. Условие в такой группе делает её обычной дисквалификацией по ответу: правило сработает, но квота считаться не будет. Инструмент на такую пару вернёт предупреждение.

Порог и источник квоты задаются внутри `screenoutConfig`, а не условиями:

```json
{
  "conditionGroupId": "g1",
  "actionType": "screenout",
  "jumpTo": {"widgetId": "uuid-экрана-отсева"},
  "conditionsData": [],
  "screenoutConfig": {
    "mode": "answer_quota",
    "sourceWidgetId": "uuid-вопроса",
    "quotaType": "answer_option",
    "quotaLimit": "100",
    "quotaOptionId": "uuid-варианта"
  }
}
```

### `update_quiz_note`
Добавляет или изменяет внутреннюю заметку опроса.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Заметка опроса обновлена."}],
  "isError": false,
  "quiz_id": 1,
  "notes": "Текст внутренней заметки"
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `notes` | `string` | (Обязательный) Текст заметки (видна только владельцу). |

### `upload_quiz_media`
Загрузка медиа (изображение, видео, аудио) для блока multimedia. (**Требуется платный тариф**).
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Медиа загружено. url: https://storage.webask.io/... — подставьте в multimedia.imgUrl."}],
  "isError": false,
  "url": "https://storage.webask.io/..."
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `type` | `string` | (Обязательный) Тип медиа: `image`, `video`, `audio`. |
| `url` | `string` | Ссылка (если загружается из сети). Укажите `url` или `file_base64`. |
| `file_base64` | `string` | Содержимое в base64. Укажите `url` или `file_base64`. |
| `filename` | `string` | Имя файла (опционально, для `file_base64`). |

**Отказ.** Текст отказа называет причину: недопустимый формат (список форматов в сообщении), превышен лимит размера или диска, недоступно на тарифе, не скачалось по `url`, некорректный base64. Если файл не сохранился по внутренней причине, в текст попадает её короткое описание (до 200 знаков) и пометка, что файл не сохранён — повтор того же запроса даст тот же результат, текст причины нужно передать в поддержку. Полная запись со стектрейсом — в канале `mcp` и `media`.

### `upload_quiz_widget_image`
Загрузка картинки для виджета "Выбор из списка" с медиа-вариантами.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Изображение загружено. url: https://storage.webask.io/... — подставьте в choiceImageEntities[].link."}],
  "isError": false,
  "url": "https://storage.webask.io/..."
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `url` | `string` | Ссылка (если загружается из сети). Укажите `url` или `file_base64`. |
| `file_base64` | `string` | Картинка в base64. Укажите `url` или `file_base64`. |
| `filename` | `string` | Имя файла (опционально). |

### Библиотека избранных вопросов

Вопросы, которые автор переиспользует в разных опросах. В библиотеке лежит снимок вопроса, а не ссылка: правка исходного опроса библиотеку не меняет, и наоборот. До 100 вопросов на аккаунт, снимок до 64 КБ.

| Инструмент | Что делает |
|---|---|
| `get_favorite_questions` | список; в каждой записи снимок, готовый к передаче в `update_quiz_widgets` |
| `save_favorite_question` | добавляет вопрос или перезаписывает сохранённый, если передан `favorite_question_id` |
| `delete_favorite_question` | убирает из библиотеки; в опросах вопрос остаётся |

### Запись на встречи

Респондент отвечает на вопросы и в конце выбирает свободное время. Расписание живёт на уровне аккаунта и подключается к опросам отдельно — одно расписание может обслуживать несколько опросов.

| Инструмент | Что делает |
|---|---|
| `get_schedulers` | расписания аккаунта и их настройки |
| `save_scheduler` | создаёт расписание или правит существующее |
| `duplicate_scheduler` | копирует расписание без записей |
| `delete_scheduler` | удаляет расписание вместе с записями и блокировками |
| `get_booking_journal` | кто записался за период: время, статус, отмены |
| `update_booking` | подтвердить, отменить, отметить неявку, перенести |
| `create_booking_block` | закрыть промежуток для записи — отпуск, занятое окно |
| `delete_booking_block` | снова открыть время |

**Два способа задать время.** `weekly` — повторяющиеся часы по дням недели, самый частый случай. `slots` — список конкретных дат и интервалов для разовых встреч. Режим выбирается полем `schedule_mode`.

**Часовые пояса.** Часы в `weekly`, `manual_slots` и `overrides` задаются в поясе расписания — респонденту слоты пересчитываются при показе. А границы записей и блокировок задаются **в UTC**: пояс расписания и пояс респондента различаются, и UTC — единственный способ не промахнуться.

**Права.** Чтение расписаний и журнала требует права на просмотр результатов — в журнале персональные данные респондентов. Любые правки — права на настройки опроса.

Выключить приём записей можно, не удаляя расписание: `save_scheduler` с `is_active: false`. Удаление необратимо и уносит записи.


## Переменные опроса и скрытые опции {#tools-variables}

### Переменные опроса

### `create_quiz_variable`
Создаёт новую скрытую переменную (extra field) опроса.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Скрытая переменная создана."}],
  "isError": false,
  "item": {
    "field_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "Имя переменной"
  }
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `name` | `string` | (Обязательный) Имя переменной. |
| `id` | `string` | (Опционально) Кастомный UUID переменной. |

### `update_quiz_variable`
Обновляет имя скрытой переменной.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Скрытая переменная обновлена."}],
  "isError": false,
  "item": {
    "field_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "Новое имя переменной"
  }
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `field_id` | `string` | (Обязательный) UUID переменной (`quiz://{id}/variables`). |
| `name` | `string` | (Обязательный) Новое имя. |

### `delete_quiz_variable`
Удаляет скрытую переменную.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Скрытая переменная удалена."}],
  "isError": false
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `field_id` | `string` | (Обязательный) UUID переменной. |

---

### Скрытые опции

### `create_hidden_option`
Создаёт скрытую служебную опцию всего опроса.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Скрытая опция создана."}],
  "isError": false,
  "item": {
    "id": 10,
    "name": "redirect_url",
    "value": "https://example.com/thanks"
  }
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `name` | `string` | (Обязательный) Ключ опции (напр. `redirect_url`). |
| `value` | `string` | (Обязательный) Значение. |

### `update_hidden_option`
Обновляет скрытую служебную опцию опроса.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Скрытая опция обновлена."}],
  "isError": false
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `opt_id` | `integer` | (Обязательный) ID опции (`quiz://{id}/hidden_options`). |
| `name` | `string` | (Обязательный) Ключ опции. |
| `value` | `string` | (Обязательный) Значение. |

### `delete_hidden_option`
Удаляет скрытую опцию опроса.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Скрытая опция удалена."}],
  "isError": false
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `opt_id` | `integer` | (Обязательный) ID опции (`quiz://{id}/hidden_options`). |

### `create_widget_hidden_option`
Создаёт скрытую служебную опцию конкретного виджета.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Скрытая опция виджета создана."}],
  "isError": false,
  "item": {
    "id": 11,
    "widget_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "name": "custom_key",
    "value": "custom_value"
  }
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `widget_uuid` | `string` | (Обязательный) UUID виджета. |
| `name` | `string` | (Обязательный) Ключ опции. |
| `value` | `string` | (Обязательный) Значение. |

### `update_widget_hidden_option`
Обновляет опцию конкретного виджета.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Скрытая опция виджета обновлена."}],
  "isError": false
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `opt_id` | `integer` | (Обязательный) ID опции. |
| `name` | `string` | (Обязательный) Ключ опции. |
| `value` | `string` | (Обязательный) Значение. |

### `delete_widget_hidden_option`
Удаляет опцию конкретного виджета.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Скрытая опция виджета удалена."}],
  "isError": false
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `opt_id` | `integer` | (Обязательный) ID опции. |

---


## Папки и доступ к ним {#tools-folders}

### `create_folder`
Создаёт новую папку для группировки опросов.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Папка успешно создана."}],
  "isError": false,
  "message": "OK"
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `workspace_id`| `integer` | (Обязательный) ID workspace (`workspace://list`). |
| `name` | `string` | (Обязательный) Название папки (макс. 255 символов). |

### `manage_workspace_folder`
Наводит порядок в папках: переименование, удаление, перестановка.

Удаление папки уносит и все опросы внутри неё вместе с ответами респондентов, поэтому непустая папка удаляется только с `confirm_delete_quizzes`. Папка по умолчанию не удаляется. Одноимённых папок в аккаунте не заводится.

**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Папка удалена."}],
  "isError": false,
  "message": "OK",
  "data": {"workspace_id": 1, "folder_id": 12, "deleted_quizzes": 3}
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `workspace_id` | `integer` | (Обязательный) ID workspace (`workspace://list`). |
| `action` | `string` | (Обязательный) `rename`, `delete` или `reorder`. |
| `folder_id` | `integer` | ID папки. Обязателен для `rename` и `delete`. |
| `name` | `string` | Новое название (макс. 255 символов). Обязательно для `rename`. |
| `folder_ids` | `array` | Полный список ID папок в нужном порядке. Обязателен для `reorder`. |
| `confirm_delete_quizzes` | `boolean` | Подтверждение удаления опросов вместе с папкой. |

### `manage_folder_access`
Доступ к папке со стороны самой папки: `members`, `add`, `remove`.

Доступ участника к опросам держится на привязке к папкам. Соседний `set_workspace_member_folders` смотрит с другой стороны — перезаписывает весь список папок одного участника целиком, поэтому для точечной выдачи и снятия годится этот инструмент.

Признак `active` в списке говорит, принял ли человек приглашение: пока нет, доступ выдан, но им никто не пользуется. Снять доступ у самого себя нельзя — человек остался бы без единственной папки.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `workspace_id` | `integer` | (Обязательный) ID workspace. |
| `folder_id` | `integer` | (Обязательный) ID папки (`folder://list`). |
| `action` | `string` | (Обязательный) `members`, `add`, `remove`. |
| `member_id` | `integer` | ID участника из `get_workspace_members`. Нужен для `add` и `remove`. |


## Темы оформления {#tools-themes}

### Темы оформления

### `apply_quiz_theme`
Применяет указанную тему оформления к опросу.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Тема #3 применена к опросу #1."}],
  "isError": false,
  "quiz": {
    "id": 1,
    "name": "Мой опрос",
    "url_shared": "https://...",
    "answer_count": 0,
    "quiz_data": {"ids": [], "entities": {}},
    "settings": {"theme_id": 3, "..." : "..."},
    "info": {}
  }
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `theme_id` | `integer` | (Обязательный) ID темы (публичной или из вашего workspace). (`theme://list`). |

### `create_theme`
Создаёт новую тему в workspace и применяет к опросу. (**Требуется платный тариф**).
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Тема создана и применена к опросу."}],
  "isError": false,
  "theme_id": 5,
  "name": "Тема 1"
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `workspace_id` | `integer` | (Обязательный) ID рабочего пространства. |
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `name` | `string` | Название темы (макс. 255 символов). |
| `base_font_id` | `integer` | ID шрифта (`1`=Native, `2`=Roboto, `3`=Open Sans, `44`=Golos Text...). |
| `base_font` | `string` | Имя шрифта строкой (новый способ), напр. `Golos Text`. Если не задано — берётся по `base_font_id`/дефолт. |
| `background_type_id` | `integer` | Тип фона: `1`=color_image, `2`=gradient. |
| `bg_gradient_vector` | `integer` | Направление градиента в градусах (0–360). |
| `bg_gradient_start` | `string` | Начальный цвет градиента в hex (напр. `#00d4ff`). |
| `bg_gradient_end` | `string` | Конечный цвет градиента в hex. |
| `buttons_type_id` | `integer` | Тип кнопок: `1`=background, `2`=border. |
| `header_color` | `string` | Цвет заголовка в формате `#RRGGBB`. |
| `buttons_color` | `string` | Цвет кнопок в формате `#RRGGBB`. |
| `buttons_text_color` | `string` | Цвет текста кнопок в формате `#RRGGBB`. |
| `buttons_radius` | `integer` | Радиус скругления кнопок (0–999 px). |
| `bg_color` | `string` | Цвет фона в формате `#RRGGBB` (для `background_type_id=1`). |
| `widget_active_color` | `string` | Цвет активного элемента виджета в формате `#RRGGBB`. |
| `bg_image_file_id` | `integer` | ID загруженного файла фонового изображения. |
| `background_position_id` | `integer` | Позиция фона (1–9). |
| `background_opacity` | `integer` | Прозрачность фона (0–100%). |
| `background_lightness` | `integer` | Яркость фона (-100 – +100). |
| `background_placement_id`| `integer` | Размещение фона: `1`=stretch, `2`=drawin, `3`=cover. |
| `background_saturate` | `integer` | Насыщенность фона (-100 – +100). |
| `background_contrast` | `integer` | Контраст фона (-100 – +100). |
| `bg_logo_file_id` | `integer` | ID загруженного файла логотипа поверх фона. |
| `bg_logo_position_id`| `integer` | Позиция логотипа на фоне (min: 1). |
| `bg_logo_size_id` | `integer` | Размер логотипа (min: 1). |
| `answer_options_radius`| `integer` | Скругление карточек вариантов ответа в px. |
| `answer_border` | `integer` | Толщина рамки вариантов ответа в px. |
| `welcome_and_finish_size_id` | `integer` | Размер текста на приветствии и завершении. |
| `welcome_and_finish_position_id` | `integer` | Выравнивание текста на приветствии и завершении. |
| `questions_size_id` | `integer` | Размер текста вопросов. |
| `questions_position_id` | `integer` | Выравнивание текста вопросов. |
| `container_width` | `string` | Ширина содержимого: `narrow`, `normal`, `wide`, `full`. |
| `button_full_width` | `boolean` | Растянуть кнопку на всю ширину контейнера. |
| `question_transition` | `string` | Переход между вопросами: `slide`, `fade`, `zoom`, `blur`, `none`. |
| `heading_font` | `string` | Отдельный шрифт заголовков (Google Fonts). Пусто — как у остального текста. |
| `input_style` | `string` | Стиль полей ввода: `box` — в рамке, `underline` — одна линия. |
| `progress_style` | `string` | Индикатор прогресса: `percent`, `line`, `steps`. |
| `custom_css` | `string` | Свой CSS для страницы опроса, до 20000 символов. |
| `service_text_enabled` | `boolean` | Показывать служебный текст — строку для дисклеймера или согласия. |
| `service_text` | `string` | Служебный текст, до 500 символов. Простая разметка допустима, остальное вырезается. |
| `service_text_placement_id` | `integer` | Где на экране стоит служебный текст. |
| `service_text_align_id` | `integer` | Выравнивание служебного текста. |
| `service_text_size` | `integer` | Размер служебного текста: `12`, `14` или `16`. |
| `service_text_color` | `string` | Цвет служебного текста в формате `#RRGGBB`. |
| `service_text_scope` | `string` | Где показывать: `all`, `welcome`, `questions` или `finish`. |

### `update_theme`
Обновляет существующую тему (настройки аналогичны `create_theme`).
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Тема успешно обновлена."}],
  "isError": false,
  "theme": {
    "id": 5,
    "workspace_id": 1,
    "name": "Тема 1",
    "base_font_id": 1,
    "background_type_id": 2,
    "bg_gradient_start": "#00d4ff",
    "bg_gradient_end": "#090979",
    "buttons_type_id": 1,
    "created_at": "...",
    "updated_at": "..."
  }
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `workspace_id` | `integer` | (Обязательный) ID рабочего пространства. |
| `theme_id` | `integer` | (Обязательный) ID темы (`theme://list`). |
| Остальные параметры | | Аналогичны параметрам `create_theme` (от `name` до `service_text_scope`). |

---

### `manage_theme`
Копия, удаление и возврат темы оформления: `copy`, `delete`, `restore`.

Опросы, которые пользовались удалённой темой, переводятся на тему по умолчанию — и черновики, и опубликованные, у последних сбрасывается кэш рендера. В ответе `reassigned_quizzes` говорит, скольких это коснулось. Общие темы сервиса аккаунту не принадлежат: их нельзя ни удалить, ни восстановить.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `workspace_id` | `integer` | (Обязательный) ID workspace. |
| `action` | `string` | (Обязательный) `copy`, `delete`, `restore`. |
| `theme_id` | `integer` | (Обязательный) ID темы из `theme://list`. |


## Раздача опроса: ссылка, QR-код, пароли, печать {#tools-sharing}

### Адрес ссылки на опрос

`set_quiz_link` меняет ту часть публичной ссылки, которую видит респондент: вместо машинного адреса — понятный.

Занятый адрес инструмент не подменит молча, а откажет. Со старого адреса заводится переадресация, так что уже разосланные ссылки продолжают работать. Возможность доступна не на всех тарифах.

### `manage_quiz_qr_code`
QR-код опроса: `show`, `generate`, `delete_logo`.

Код собирается по ссылке опубликованного опроса, поэтому неопубликованный опрос получает отказ — кодировать нечего. Незаданные настройки берутся из прошлой сборки, а не из умолчаний. Повторный `generate` с теми же настройками отдаёт готовый файл и `regenerated: false`: каждая сборка пишет новый файл и списывает место с тарифа. Пересобрать принудительно — `force`.

Логотип в центре кода загружают в кабинете: через ассистента картинку не передать. Уже загруженный логотип код учитывает, а убрать его можно — `delete_logo`; из самого кода он уйдёт после повторной сборки.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `action` | `string` | (Обязательный) `show`, `generate`, `delete_logo`. |
| `format` | `string` | `png`, `svg`, `eps`. По умолчанию — как в прошлый раз, иначе `png`. |
| `size` | `integer` | Сторона картинки, 100–1000. По умолчанию 320. |
| `color`, `background` | `string` | Шестизначный HEX: `#000000`, `#ffffff`. |
| `margin` | `integer` | Поля вокруг кода, 0–10. По умолчанию 1. |
| `force` | `boolean` | Пересобрать, даже если настройки не менялись. |

### `manage_quiz_passwords`
Ведёт список паролей доступа к закрытому опросу: `list`, `add`, `update`, `delete`.

Пароли хранятся хешами — прочитать заведённый пароль нельзя ни здесь, ни в конструкторе, только заменить новым. В списке видны номер и дата; по номеру в ответах (`password_number` визита) автор узнаёт, кто из приглашённых отвечал. Поэтому номера не переиспользуются после удаления.

Саму проверку пароля на входе включает настройка `use_password` в `update_quiz_settings`.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `action` | `string` | (Обязательный) `list`, `add`, `update`, `delete`. |
| `password` | `string` | Пароль. Для `update` — новое значение. |
| `passwords` | `array` | Список паролей для `add`, до 500 за раз. |
| `password_id` | `integer` | ID пароля из `list`. Нужен для `update` и `delete`. |

### `export_quiz_print`
Собирает печатную версию опроса — самих вопросов, не ответов.

Форматы `pdf`, `word`, `txt`, `html`; размер шрифта влияет на вёрстку печатной страницы. Возвращает подписанную ссылку на час. PDF собирает headless-браузер, поэтому падает независимо от остальных форматов — отказ приходит с внятным текстом.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `format` | `string` | `pdf` (по умолчанию), `word`, `txt`, `html`. |
| `font` | `string` | `little`, `medium` (по умолчанию), `large`. |


## Ответы: видимость, теги, пометки, чистка {#tools-answers}

### Ответы и аналитика

### `toggle_answer_visibility`
Скрывает или показывает конкретный ответ респондента.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Видимость ответа обновлена."}],
  "isError": false,
  "answer_id": 100,
  "is_exclude": true
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `answer_id` | `integer` | (Обязательный) ID ответа. |
| `is_hide` | `boolean` | (Обязательный) `true` (скрыть) или `false` (показать). |

### `tag_answer`
Добавляет/синхронизирует теги для выбранного ответа.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Теги ответа обновлены."}],
  "isError": false,
  "result": {
    "attached": [5],
    "detached": []
  }
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `answer_id` | `integer` | (Обязательный) ID ответа. |
| `tags` | `array` | (Обязательный) Массив строк-тегов (заменяет старые). |
Передайте пустой массив `tags`, чтобы снять с ответа все теги: инструмент синхронизирует список, открепляя всё, чего в нём нет. Сам ключ `tags` при этом обязателен.

### Теги ответов

`tag_answer` сопоставляет теги одного ответа по именам, поэтому перед ним стоит прочитать справочник — иначе появятся новые теги вместо существующих.

| Инструмент | Что делает |
|---|---|
| `get_answer_tags` | теги аккаунта |
| `create_answer_tag` | создаёт тег; одноимённый не дублируется |
| `delete_answer_tag` | удаляет тег вместе со всеми привязками к ответам |

### Пометка у ответа

`set_answer_note` ставит короткую пометку у отдельного прохождения — «перезвонить», «дубль», «жалоба». До 100 символов, пустое значение снимает.

Это не заметка к опросу целиком (`update_quiz_note`) и не тег (`tag_answer`): пометка живёт у самого ответа и видна в его строке.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `answer_id` | `integer` | (Обязательный) ID ответа из `quiz://{id}/answers`. |
| `note` | `string` | Текст до 100 символов; пусто — снять пометку. |

### Чистка ответов

`delete_quiz_answers` убирает ответы в корзину, возвращает их обратно или удаляет насовсем — типичная уборка после тестовых прохождений.

Обычное удаление обратимо: ответ уходит из списка и отчёта, но лежит в корзине. Безвозвратное необратимо, вместе с ответом убираются файлы респондента, и поэтому оно требует явного подтверждения в самом запросе — одного намерения модели мало.

Скрытые и отправленные на модерацию ответы инструмент не трогает, как и конструктор.

По умолчанию действие работает над перечисленными ответами (`answer_ids`, до 500 за раз). Для «очисти опрос» есть область `scope: all` — она идёт запросом, а не списком, и потому справляется с десятками тысяч ответов. В этом режиме `answer_ids` не нужен, зато можно передать `excluded_answer_ids` — что оставить как есть.

Область `all` затрагивает всю собранную работу, и списка id, по которому видно объём, в запросе нет — поэтому она требует `confirm_purge` даже для обычного удаления в корзину. Возврат из корзины ничего не стирает и подтверждения не требует. Скрытые и отправленные на модерацию ответы массовый режим тоже не трогает.

### `set_answers_order_mode`

Порядок вопросов внутри карточки ответа. Тот же порядок получают публичная ссылка на ответы и выгрузка в Word, поэтому настройка меняет и то, что видят другие.

| Параметр | Тип | Описание |
|---|---|---|
| `quiz_id` | `integer` | **Обязательный.** ID опроса. |
| `answers_order_mode` | `string` | **Обязательный.** `respondent` — в том порядке, в каком человек проходил опрос; `survey` — в порядке вопросов самого опроса. |

### `get_answer_extra_field_values`
Показывает, какие значения встречались у метки ссылки в ответах опроса.

Метки цепляют к ссылке — `utm_source`, идентификатор ученика, номер курса — и они приходят вместе с ответом; по ним отбирают ответы в отчёте. Имена меток отдаёт `quiz://{id}/hidden_options`, а этот инструмент — значения выбранного имени, с поиском и постранично: у крупного опроса их бывают тысячи. Отбор по меткам доступен не на всех тарифах.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `key` | `string` | (Обязательный) Имя метки, например `utm_source`. |
| `search` | `string` | Поиск среди значений. |
| `page` | `integer` | Страница, с 1. |
| `per_page` | `integer` | Значений на странице, 1–100. По умолчанию 20. |


## Отчёты: фильтры, AI-отчёты, оформление, ссылки {#tools-reports}

### `generate_filtered_report`
Формирует отчёт по опросу с фильтрацией (даты, виджеты, теги). Возвращает `report_uuid`.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Фильтрованный отчет сформирован."}],
  "isError": false,
  "data": {
    "report_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "total": 150
  }
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `dateFrom` | `string` | Начало (формат `d.m.Y`, напр. `01.01.2024`). |
| `dateTo` | `string` | Конец (формат `d.m.Y`). |
| `is_complete` | `boolean` | Учитывать только полные ответы. |
| `widgets` | `array` | Массив UUID виджетов для отчёта. |
| `tags` | `array` | Массив тегов. |

### `save_quiz_report_filters`

Сохраняет набор фильтров списка ответов или отчёта. Тот же набор читают экран, публичная ссылка, PDF и Word, поэтому он меняет и то, что видят другие. Текущее состояние — в `quiz://{id}/report_filters`.

| Параметр | Тип | Описание |
|---|---|---|
| `quiz_id` | `integer` | **Обязательный.** ID опроса. |
| `type` | `string` | **Обязательный.** `answers` — список ответов, `report` — отчёт. |
| `filters` | `object` | **Обязательный.** Объект фильтров той же формы, что при чтении. |

У типа `report` в наборе есть ключ `statuses` — срез по статусам прохождения, то есть по
каким ответам считается отчёт: массив из `completed` (завершившие), `unfinished` (ушедшие,
не дойдя до конца) и `screenout` (дисквалифицированные). Ключ не передан — прежний срез
остаётся: сохранение фильтров без него не сбрасывает вкладку. Пустой массив и неизвестные
значения приводятся к `[completed]`; `unfinished` и `screenout` закрыты опцией тарифа и на
закрытом тарифе отбрасываются. Срез действует не только на экране — по нему же считаются
PDF, Word и отчёт по публичной ссылке. У типа `answers` ключа нет.

На тарифах без опции сохранённых фильтров набор не сохраняется — инструмент вернёт отказ по
тарифу. Срез отчёта — исключение: он сохраняется и без опции, потому что это состояние
отчёта, а не платный фильтр.

### `generate_ai_report`
Строит AI-отчёт по ответам опроса.

Отчёт состоит из трёх независимых разделов: текстовый анализ, сравнительный и количественный. Каждый строится отдельно, поэтому текстовый можно пересобрать, не трогая остальные. Работа идёт в очереди — инструмент сообщает, какие разделы поставлены в работу, а готовый текст забирает `get_ai_report`.

Пока ответов меньше порога (`report_ai_v2.min_answer_count`), ни один раздел не запускается: приходит отказ с числами, а не пустой успех. Повторный запуск раздела, который уже строится, ничего не ломает — он попадёт в `already_running_sections`. Тариф ограничивает число опросов с отчётом; пересборка существующего лимит не расходует.

**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Разделы отчёта поставлены в работу. Готовый текст — в get_ai_report."}],
  "isError": false,
  "quiz_id": 42,
  "queued_sections": ["text_analysis", "comparative"],
  "already_running_sections": ["quantitative"],
  "refused_sections": {}
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `section` | `string` | `all` (по умолчанию), `text_analysis`, `comparative`, `quantitative`. |
| `locale` | `string` | Язык отчёта. По умолчанию — язык владельца ключа. |

### `get_ai_report`
Отдаёт готовый AI-отчёт — тот самый, что автор видит в разделе результатов.

У каждого из трёх разделов свой статус: готов, строится сейчас или не построен, и почему — мало ответов либо нет подходящих вопросов. Читать отчёт следует этим инструментом: генерация тратит лимит тарифа и упирается в задержку между перезапусками.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |

### `share_ai_report`
Даёт поделиться AI-отчётом: публичной ссылкой или файлом PDF.

Ссылку открывают без входа в аккаунт. Делиться можно только построенным отчётом, и опрос должен быть опубликован: страница отчёта живёт на его адресе. Ссылка требует тарифа на публикацию результатов, PDF — на выгрузку. Ссылка на скачивание PDF действительна час.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `format` | `string` | `link` (по умолчанию) или `pdf`. |

### `share_summary_link` / `share_report_link` / `share_answers_link`
Создаёт публичные ссылки для шеринга аналитики с заказчиками (без авторизации).
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Публичная ссылка summary создана."}],
  "isError": false,
  "url": "https://webask.io/results/..."
}
```
Для `share_answers_link` также может возвращаться поле `shared_url`.
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `date_from` / `date_to` | `string` | (Для summary и answers). Формат `Y-m-d`. |
| `type` | `string` | (Для answers) `full`, `in`, `not_in`, `answer`. |
| `answerIds` | `array` | (Для answers) ID ответов. |

---

### Оформление отчётов

Цвета и подписи графиков в отчёте настраиваются на двух уровнях.

- **Общая палитра аккаунта** — одна на весь воркспейс. Её используют опросы, у которых включён флаг `results_appearance_use_shared`.
- **Своя палитра опроса** — поле `results_appearance` в `update_quiz_settings`.

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

Объект оформления одинаков везде:

| Поле | Значения |
|---|---|
| `palette_id` | `default`, `pastel`, `vivid`, `ocean`, `contrast`, `warm`, `mono`, `custom` |
| `custom_colors` | цвета своей палитры, `#RRGGBB`, от 2 до 14; меньше двух — палитра откатится на `default` |
| `bar_color_mode` | `single` — один цвет на график, `palette` — свой цвет каждому варианту |
| `font_family` | название гарнитуры Google Fonts или `null` |
| `show_value_labels` | показывать значения на графике |
| `show_grid_lines` | показывать линии сетки |

Настройки строк отчёта задаются отдельным полем `report_row_options`: `sort_mode` (`default`, `asc`, `desc`) и `value_format` (`both`, `count`, `percent`). `null` в любом из этих полей возвращает стандартный вид.

Действующее оформление опроса приходит в `quiz://{id}/structure`: `results_appearance` (своё), `results_appearance_use_shared` (флаг), `report_row_options` и `effective_results_appearance` — то, которое реально применится.

### `get_workspace_report_appearance`

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

| Параметр | Тип | Описание |
|---|---|---|
| `workspace_id` | `integer` | **Обязательный.** ID воркспейса из `workspace://list`. |

### `update_workspace_report_palette`

Задаёт общую палитру аккаунта. Все опросы с включённым флагом начинают рисовать отчёты ею сразу.

| Параметр | Тип | Описание |
|---|---|---|
| `workspace_id` | `integer` | **Обязательный.** ID воркспейса. |
| `settings` | `object` | **Обязательный.** Объект оформления (таблица выше). |

### `create_report_appearance_preset`

Сохраняет набор оформления в библиотеку аккаунта, чтобы применять его к другим опросам. Набор — шаблон: его удаление или правка не меняют опросы, к которым его применяли. В воркспейсе можно хранить до 20 наборов.

| Параметр | Тип | Описание |
|---|---|---|
| `workspace_id` | `integer` | **Обязательный.** ID воркспейса. |
| `name` | `string` | **Обязательный.** Название, до 40 символов. |
| `settings` | `object` | **Обязательный.** Объект оформления. |

### `delete_report_appearance_preset`

Удаляет набор из библиотеки. Опросы, к которым его применяли, своё оформление сохраняют.

| Параметр | Тип | Описание |
|---|---|---|
| `workspace_id` | `integer` | **Обязательный.** ID воркспейса. |
| `preset_id` | `integer` | **Обязательный.** ID набора из `get_workspace_report_appearance`. |


## Выгрузки ответов и отчётов {#tools-exports}

### Экспорт списка ответов и отчёта

Инструменты экспорта: `export_answers_csv`, `export_answers_xlsx`, `export_answers_word`, `export_summary_pdf`, `export_filtered_report_pdf`, `export_filtered_report_word`.

**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Файл CSV готов. Скачать по ссылке (действует 1 час, после скачивания файл удаляется): https://storage.webask.io/..."}],
  "isError": false,
  "quiz_id": 1,
  "format": "csv",
  "download_url": "https://storage.webask.io/..."
}
```
Ссылка на скачивание действует 1 час. Для вызова любого из этих инструментов используется один обязательный параметр:
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |

---

### `export_answers_spss`
Выгружает ответы в формат SPSS (`.sav`) — тот же набор данных, что в CSV и XLSX.

Вместе с ответами в файл идут параметры ссылки, иначе часть колонок осталась бы безымянной. Возвращает подписанную ссылку на час; забытые файлы убирает почасовая уборка. Выгрузка результатов доступна не на всех тарифах.

Параметры те же, что у `export_answers_csv` и `export_answers_xlsx`. Если подходящих ответов нет, `download_url` приходит `null` — файла не создаётся.


## Интеграции: CRM, мессенджеры, таблицы, вебхуки {#tools-integrations}

### Интеграции опроса

`get_quiz_integrations` показывает, куда опрос отправляет ответы кроме почты — Telegram, MAX, AmoCRM, Битрикс24, Zapier, Google Таблицы.

По каждой видно три вещи: подключена ли она, включена ли отправка и разрешает ли её тариф. Поле `blocked` называет причину, по которой ответы не уходят: `not_connected`, `disabled` или `blocked_by_tariff`; пусто — значит отправка работает.

Подключение остаётся в интерфейсе: это вход в чужой аккаунт или ввод токена. Вебхуки живут отдельными инструментами — там доступны и настройка, и журнал доставки.

### `manage_quiz_crm`
Отправка ответов в CRM: `show`, `activate`, `deactivate`, `check` для `amocrm` или `bitrix24`.

Переключатель отправки и проверка живости подключения. Токен CRM отзывают на её стороне, и со стороны опроса это выглядит как молчащая интеграция — `check` отвечает **отказом**, когда доступа нет, а не «связь в порядке». Без подключённого портала любое действие отказывает: подключение требует входа в аккаунт CRM и делается в кабинете. Сопоставление полей здесь не настраивается — см. `get_quiz_crm_fields`.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `crm` | `string` | (Обязательный) `amocrm` или `bitrix24`. |
| `action` | `string` | (Обязательный) `show`, `activate`, `deactivate`, `check`. |

### `get_quiz_crm_fields`
Справочники CRM и текущее сопоставление полей. Только чтение.

Отдаёт то же, что человек видит в форме настройки: поля лида, сделки, контакта и компании, воронки со стадиями, ответственных, типы — и рядом сохранённое у опроса сопоставление. Справочники читаются живыми запросами в портал, поэтому вызов не мгновенный и вернёт отказ при отозванном доступе. По умолчанию берутся только `lead` и `contact`: каждый раздел — отдельный запрос в CRM.

Менять сопоставление нельзя намеренно: настройки хранятся свободным JSON, форму которого задаёт адаптер доставки, и блок не той формы молча ломает отправку заявок. В ответе стоит `mapping_editable: false`.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `crm` | `string` | (Обязательный) `amocrm` или `bitrix24`. |
| `sections` | `array` | `lead`, `deal`, `contact`, `company`, `users`, `pipelines`, `types`. По умолчанию `lead` и `contact`. |
| `deal_category_id` | `integer` | Номер воронки: стадии принадлежат воронке. Только Bitrix24. |

### `manage_quiz_crm_mapping`
Сопоставление полей опроса с полями **Bitrix24**. Одна сущность за вызов: `lead`, `contact`, `company`, `deal`.

Сущность заменяется целиком, остальные вызов не трогает; набор полей тоже заменяется целиком, иначе убранное поле осталось бы в настройках. Правка полей не выключает отправку: незаданный `enabled` берётся из прежних настроек.

Форма настроек — свободный JSON, и её задаёт адаптер доставки, а не валидация запроса (кабинет проверяет из всего блока два поля). Поэтому блок неверной формы не отклоняется, а молча ломает отправку заявок.

Форма блока сущности:

```
<entity> = {
    enabled: bool,            // сущность участвует в отправке
    fields: {                 // ключ — имя поля Bitrix24 (TITLE, PHONE, UF_CRM_…)
        <field>: {
            widget_id?: string,     // UUID вопроса: значение берётся из ответа
            static_value?: string   // либо постоянное значение
        }
    },
    category_id?: string,     // воронка, только у deal
    stage_id?: string         // стадия этой воронки, только у deal
}
```

В поле берётся `widget_id`, а при его отсутствии `static_value`. Название сущности обязательно: если `TITLE` собрать не удалось, адаптер подставляет `Lead #<id ответа>` или `Deal #<id ответа>`. У воронки по умолчанию стадия не начинается с `C`, у воронки N — начинается с `CN:`.

Инструмент отклоняет до записи: поле, где заданы и вопрос, и постоянное значение (адаптер взял бы вопрос, значение потерялось бы); поле без источника; стадию из чужой воронки; стадию у сущности, кроме сделки. Если стадия всё же отвалилась при сохранении, в ответе `stage_dropped`.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `entity` | `string` | (Обязательный) `lead`, `contact`, `company`, `deal`. |
| `enabled` | `boolean` | Отправлять ли сущность. Незаданное остаётся прежним. |
| `fields` | `object` | Имя поля Bitrix24 → `{widget_id}` или `{static_value}`. Набор заменяется целиком. |
| `title_type` | `string` | `Field` (из ответа) или `Static` (заданный текст). |
| `title_value` | `string` | ID вопроса при `Field`, текст при `Static`. |
| `company_title_value` | `string` | То же для названия компании (`lead`, `company`). |
| `status` | `string` | Статус лида (`STATUS_ID`). Только `lead`. |
| `add_id_to_title` | `boolean` | Дописать номер заявки в название. Только `lead`. |
| `category_id` | `integer` | Номер воронки, `0` — по умолчанию. Только `deal`. |
| `default_stage` | `string` | Стадия. У воронки по умолчанию не с `C`, у воронки N — с `CN:`. Только `deal`. |
| `link_to_lead` | `boolean` | Привязать сделку к созданному лиду. Только `deal`. |

### `manage_quiz_amocrm_mapping`
Сопоставление полей опроса с полями **amoCRM**. Отдельный инструмент, потому что форма другая: не «сущность → поля», а «что активно» плюс словари по номерам.

Блоки: `deal` — сделки в перечисленных воронках, `list` — контакт (`0`), компания (`1`) и каталоги, `task` — задачи. Один блок за вызов, блок заменяется целиком, соседние не трогаются, незаданные словари остаются прежними — правка стадии не стирает сопоставление полей. Пустой `active` сохраняется как осмысленное значение «не создавать».

Каждое значение проверяется по живым справочникам портала **до** записи: воронка существует, стадия принадлежит своей воронке, идентификатор поля есть в справочнике, ответственный есть в списке пользователей. В `bind` значение обязано быть идентификатором вопроса — постоянных значений эта форма не поддерживает, и текст молча дал бы пустое поле в CRM. Если портал справочники не отдал, инструмент отказывает, а не пишет «на удачу».

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `block` | `string` | (Обязательный) `deal`, `list`, `task`. |
| `active` | `array` | Номера воронок; для `list` — `0`, `1` и номера каталогов. Пустой список — не создавать. |
| `titles` | `object` | Номер → ID вопроса или текст названия. |
| `statuses` | `object` | Номер воронки → стадия. |
| `users` | `object` | Номер воронки → ответственный. |
| `use_price` | `object` | Номер воронки → отправлять ли баллы в сумму сделки. |
| `bind` | `object` | ID поля amoCRM → ID вопроса опроса. |
| `subtype` | `object` | ID поля → уточнение вида значения (например, какой телефон). |

### `manage_quiz_messenger`
Настройки отправки ответов в мессенджер: `telegram` или `max`.

Отвечает на вопрос «куда именно уходят заявки»: список чатов, групп и людей, тема сообщения, какие вопросы из него исключены, есть ли кнопки. Действия: `show`, `activate`, `deactivate`, `delete`, `toggle_buttons`, `save_widgets`, `remove_recipient`, `test`.

Токены здесь не вводят: бот платформенный. `activate` заводит подключение и отдаёт `bot_url` — по этой ссылке человек добавляет бота в чат, и до этого шага отправлять некуда. Любое действие кроме `activate` отвечает отказом, если интеграция не подключена. `test` отказывает при выключенной отправке и при пустом списке получателей. Выбор темы форума в группе Telegram в инструмент не входит — текущая тема каждого получателя видна в `show`. Журнал доставки — `get_integration_logs`.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `messenger` | `string` | (Обязательный) `telegram` или `max`. |
| `action` | `string` | (Обязательный) Действие из списка выше. |
| `buttons_enabled` | `boolean` | (Обязательный для `toggle_buttons`) Кнопки под сообщением. |
| `subject` | `string` | Тема сообщения, до 70 символов. Применяется при `save_widgets`. |
| `hidden_widget_ids` | `array` | ID вопросов, которые НЕ включать в сообщение. Список задаётся целиком: чего в нём нет, то вернётся в сообщение. |
| `recipient_id` | `string` | (Обязательный для `remove_recipient`) ID получателя из `show`. |

### `get_integration_logs`
Журнал отправки ответов в мессенджер: `telegram` или `max`.

Отдаёт текст отказа (бота выгнали из группы, чат удалён, токен отозван) и `unsent_count`. Журнал вебхуков — отдельный `get_quiz_webhook_logs`. Неподключённая интеграция отвечает отказом, а не пустым журналом.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `integration` | `string` | (Обязательный) `telegram` или `max`. |
| `page` | `integer` | Страница, с 1. От новых к старым. |
| `per_page` | `integer` | 1–200, по умолчанию 20. |

### `resend_integration_logs`
Дослать в мессенджер то, что не ушло: одну запись по `log_id` или все неудачные за период.

Отправка идёт очередью, поэтому в ответе `queued` — сколько поставлено, а не сколько дошло. Повтор пачкой берёт ограниченное число записей за раз; если срезано, приходит `truncated: true` и вызов нужно повторить.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `integration` | `string` | (Обязательный) `telegram` или `max`. |
| `log_id` | `integer` | Одна запись. Без неё — все неудачные. |
| `date_from`, `date_to` | `string` | Период для повтора пачкой, `ГГГГ-ММ-ДД`. |

### `manage_quiz_google_sheets`
Выгрузка ответов в Google Таблицы: `show`, `activate`, `deactivate`, `delete`, `save_widgets`, `save_switchers`, `set_sync_mode`, `sync_now`, `export_all`.

`show` отвечает на «почему в таблице нет новых ответов»: адрес и название таблицы, живой ли доступ (его отзывают на стороне Google), режим обновления, когда выгружали последний раз, какие вопросы исключены.

Состав вопросов задаётся **целиком**, поэтому вызов `save_widgets` без `hidden_widget_ids` отклоняется: иначе он снял бы все исключения и в таблицу поехали бы вопросы, которые автор оттуда убрал. Переключатели меняются по одному, но хотя бы один нужен. Подключение аккаунта и выбор таблицы — в кабинете: там вход в аккаунт Google и окно Google Диска.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `action` | `string` | (Обязательный) Действие из списка выше. |
| `hidden_widget_ids` | `array` | ID вопросов, которые НЕ попадают в таблицу. Список задаётся целиком. |
| `enable_timestamp` | `boolean` | Столбец со временем ответа. |
| `enable_personal_data` | `boolean` | Выгружать персональные данные респондента. |
| `sync_mode` | `string` | `realtime`, `scheduled`, `manual`. |
| `schedule_hour`, `schedule_minute` | `integer` | Время обновления. Обязательны при `scheduled`. |
| `export_mode` | `string` | Для `export_all`: `new_file`, `new_sheet`, `replace`. |

### Вебхуки опроса

Отправка каждого ответа во внешнюю систему. Порядок работы такой же, как в интерфейсе: создать → проверить → включить.

| Инструмент | Что делает |
|---|---|
| `get_quiz_webhooks` | список вебхуков и их состояние; идентификаторы приходят только отсюда |
| `create_quiz_webhook` | добавляет вебхук — выключенным |
| `update_quiz_webhook` | меняет вебхук; не переданные поля сохраняют текущие значения |
| `toggle_quiz_webhook` | включает и выключает |
| `get_quiz_webhook_logs` | журнал отправок: дошло или нет, что ответил приёмник |
| `resend_quiz_webhook_log` | повторяет отправку: одну запись по `log_id` либо все недоставленные за период (`date_from`, `date_to`) |
| `delete_quiz_webhook` | удаляет вебхук вместе с заголовками и журналом |

Адрес должен быть публичным `http` или `https` — внутренние адреса не принимаются.

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

Отправка ответов доступна не на всех тарифах. Если опция не входит в тариф, вебхук нельзя создать, изменить или включить — но список, журнал, выключение и удаление остаются доступны, чтобы прежнюю настройку можно было разобрать.

После серии неудачных отправок вебхук отключается сам. Это видно в списке, включить обратно можно тем же `toggle_quiz_webhook`.

**Ответ не приходит во внешнюю систему?** Ответ на этот вопрос даёт только журнал: в нём видно код ответа приёмника и его тело. По самой настройке вебхука понять причину нельзя.

### Zapier

`manage_quiz_zapier` — со стороны опроса это один переключатель: `show`, `activate`, `deactivate`, `delete`.

Связки собираются на стороне Zapier по подписке, ключей здесь вводить не нужно. `delete` снимает и подписки: связка перестанет получать ответы, и собирать её придётся заново в Zapier. `show` заодно показывает, сколько связок слушает опрос.

### Счётчики и пиксели

`manage_quiz_analytics` ведёт Google Аналитику, Яндекс.Метрику и пиксели VK и Facebook: `show`, `save`, `activate`, `deactivate`, `delete`. Сервис выбирается параметром `service` — `google`, `yandex`, `vk`, `fb`.

Номер счётчика не секрет, он виден в коде любой страницы, поэтому здесь ничего чужого вводить не приходится. Значение пишется в двух местах — в строку интеграции и в версию опроса, откуда его читает рендер; иначе счётчик числился бы включённым, а на странице не появлялся. У Google есть `version` (`ga4` или `ua`), у Яндекса — `is_webvisor`.


## Почта опроса {#tools-quiz-emails}

Письма, которые опрос шлёт сам по себе: уведомление автору о новом ответе и копия
ответа респонденту. К e-mail рассылкам по контактным спискам это отношения не имеет —
они в [tools-mailing.md](/dev/api/mcp-tools#tools-mailing).

### Письма опроса

Две независимые рассылки: уведомление автору о новом ответе и копия ответа респонденту. У каждой свой переключатель и свой шаблон.

| Инструмент | Что делает |
|---|---|
| `get_quiz_email_settings` | что включено, адреса автора и их подтверждение, шаблоны писем |
| `toggle_quiz_email` | включает и выключает письма автору (`owner`) или респонденту (`client`) |
| `save_quiz_email_template` | тема и текст писем, адрес для ответа, промокод в письме |
| `add_quiz_email_recipient` | добавляет адрес для уведомлений автору |
| `toggle_quiz_email_recipient` | включает и выключает конкретный адрес |
| `delete_quiz_email_recipient` | убирает адрес |
| `set_quiz_email_questions` | какие вопросы не попадают в письмо |

**Порядок действий.** Настройки писем появляются у опроса в момент, когда письма впервые включают или добавляют адрес. До этого шаблон сохранять некуда — инструмент так и скажет.

**Три вида шаблона.** Стандартное письмо WebAsk (`use_default_tmpl`), свой визуальный шаблон (`formatted_letter` — HTML из редактора) и полностью свой HTML (`raw_html` + `raw_enabled`), который уходит как есть, без обёртки WebAsk.

Полностью свой HTML работает **только с подключённым своим SMTP**. Без него письмо всё равно уйдёт визуальным шаблоном — сохранение при этом успешно, поэтому инструмент возвращает предупреждение. Подключён ли свой SMTP, видно в `get_quiz_email_settings` (`own_smtp_connected`).

**Состав письма.** По умолчанию в письмо попадают все вопросы и ответы на них. `set_quiz_email_questions` задаёт исключения — например, чтобы не слать в почту персональные данные. Списки для письма автору и копии респонденту независимы.

**Адрес автора работает только после подтверждения.** При добавлении на него уходит письмо со ссылкой; пока человек не подтвердил, уведомления туда не идут. В списке это поле `checked`.

**Адрес респондента не задаётся напрямую.** Он берётся из ответа на вопрос с почтой или из скрытой переменной опроса — на источник указывает `client_source`. Если переменную переименовать, указатель переезжает сам.

Копия ответа респонденту доступна не на всех тарифах, число адресов автора тоже ограничено тарифом.

### `manage_workspace_smtp`
Своя почтовая служба аккаунта: `show`, `logs`, `enable`, `disable`, `test`.

Подключение одно на аккаунт: им уходят и письма опроса, и e-mail рассылки.

Пароль не возвращается — только `has_password`. Само подключение (хост, логин, пароль) заводит человек в кабинете: инструмент его не принимает. `test` шлёт письмо только на адрес владельца ключа, чужой адрес не передаётся. `enable` отказывает, пока подключение не заведено.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `workspace_id` | `integer` | (Обязательный) ID workspace. |
| `action` | `string` | (Обязательный) `show`, `logs`, `enable`, `disable`, `test`. |
| `limit` | `integer` | Записей журнала, 1–200. По умолчанию 50. |
| `offset` | `integer` | Сколько записей журнала пропустить. |

### `confirm_quiz_email_recipient`
Подтверждает адрес получателя копий ответов кодом из письма.

Без подтверждения адрес не получает ничего: `add_quiz_email_recipient` только заводит его, и в базе он лежит с `checked = false`. Код приходит на сам адрес — ассистент его не видит, диктует человек. Попыток ограниченное число, поэтому есть `resend_code`.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `action` | `string` | (Обязательный) `confirm` или `resend_code`. |
| `email_id` | `integer` | (Обязательный) ID адреса из `get_quiz_email_settings`. |
| `confirmation_code` | `integer` | Код из письма. Обязателен для `confirm`. |


## E-mail рассылки {#tools-mailing}

Кампании по контактным спискам аккаунта: письма уходят людям, которых автор завёл
сам, а не участникам прохождения опроса. Письма, которые шлёт сам опрос
(уведомление автору о новом ответе, копия ответа респонденту), — в
[tools-quiz-emails.md](/dev/api/mcp-tools#tools-quiz-emails).

**Ассистент рассылки только читает.** Отправка кампании, правка кампаний, списков и
шаблонов, покупка писем через него недоступны: письмо уходит живым людям и отменить
его нельзя, а цена ошибки — репутация домена аккаунта. Контакты загружают файлом.
Это решение, а не недоделка: всё перечисленное делается в кабинете.

### `get_mailing_state`
Состояние e-mail рассылок аккаунта. Только чтение.

Разделы: `campaigns` — список со сводкой, `campaign` — одна кампания с показателями, `recipients` — получатели с фильтром по статусу доставки, `contact_lists` — списки и сводка по контактам, `contacts` — контакты одного списка, `templates` — шаблоны писем, `reputation` — доля отказов и жалоб (по ней почтовые службы решают, пускать ли письма дальше), `balance` — сколько писем осталось, `settings` — отправитель и своя почтовая служба, `smtp_logs` — журнал отправок.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `workspace_id` | `integer` | (Обязательный) ID workspace. |
| `section` | `string` | (Обязательный) Раздел из списка выше. |
| `campaign_id` | `integer` | Обязателен для `campaign` и `recipients`. |
| `list_id` | `integer` | Обязателен для `contacts`. |
| `search` | `string` | По названию и теме в `campaigns`, по адресу в `recipients`. |
| `statuses` | `array` | Статус письма у получателя, для `recipients`. Пустой список — все. |
| `quiz_statuses` | `array` | Прохождение опроса получателем, для `recipients`. Пустой список — все. |
| `days` | `integer` | Период репутации, по умолчанию 30. |
| `limit`, `offset` | `integer` | Постранично, 1–200 (по умолчанию 50). |

#### Фильтры раздела `recipients`

Два независимых фильтра, оба необязательные.

`statuses` — что случилось с письмом: `sent` (отправлено), `delivered` (доставлено),
`opened` (открыл), `clicked` (перешёл по ссылке), `bounced` (не доставлено),
`unsubscribed` (отписался), `complaint` (пожаловался на спам). Статус у получателя
один — последнее случившееся событие, поэтому открывший письмо в `delivered` не
попадает, а перешедший по ссылке — в `opened`. Чтобы получить «письмо дошло» целиком,
перечисляют все подходящие значения.

`quiz_statuses` — что получатель сделал с опросом: `none` (не начинал), `started`
(начал и не закончил), `completed` (прошёл до конца). По этому фильтру в кабинете
отбирают тех, кому досылать письмо. Прохождение считается по скрытой переменной с id
получателя в ссылке письма, поэтому у кампании без привязки к опросу оно неизвестно
ни у кого — фильтр вернёт пустой список, а в самих записях `quizStatus` будет `null`.

Неизвестное значение в любом из фильтров — ошибка валидации, а не молчаливое
игнорирование: иначе ответ выглядел бы отфильтрованным, не будучи им.

Отправитель и своя почтовая служба аккаунта — общие с письмами опроса, настраиваются
инструментом `manage_workspace_smtp` (см. [tools-quiz-emails.md](/dev/api/mcp-tools#tools-quiz-emails)).


## Аккаунт: профиль, участники, брендинг, домен, файлы {#tools-account}

### `update_user_profile`
Правка своего профиля: имя, язык интерфейса, часовой пояс, оформление, подписки на письма.

Часовой пояс важен отдельно — от него зависят все даты в отчётах и выгрузках. Передаются только те поля, которые нужно поменять; подписки можно менять по одной, остальные останутся как были. Смена почты и пароля недоступна (код из письма и текущий пароль), аватар тоже — это загрузка картинки. Чтение профиля — `user://me`.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `name` | `string` | Имя, 3–255 символов. У владельца им же называются его аккаунты. |
| `lang` | `string` | Язык интерфейса и писем. |
| `timezone` | `string` | Часовой пояс вида `Europe/Moscow`. |
| `theme` | `string` | `light`, `dark`, `auto`. |
| `subscribe_action`, `subscribe_billing`, `subscribe_marketing` | `boolean` | Подписки на письма. |

### `manage_user_sessions`
Активные входы в аккаунт: `list`, `close`, `close_all`.

Показывает устройство, браузер, город и когда заходили последний раз. **Отличие от кабинета:** там «закрыть все» означает «все, кроме текущей», потому что запрос идёт из браузерной сессии. Ассистент работает по ключу и своей сессии не имеет, поэтому `close_all` закрывает **все** входы, включая тот, в котором человек сейчас работает в кабинете, — и требует `confirm_close_all`. Закрыть чужой вход нельзя: идентификатор сверяется со своим списком. На установке без Redis инструмент отказывает — там этих данных нет.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `action` | `string` | (Обязательный) `list`, `close`, `close_all`. |
| `session_id` | `string` | (Обязательный для `close`) Идентификатор входа из `list`. |
| `confirm_close_all` | `boolean` | (Обязательный для `close_all`) Подтверждение. |

### Сведения об аккаунте и права

`get_workspace_details` отвечает на «что это за аккаунт и что мне в нём можно»: название, владелец ли текущий пользователь, включены ли AI-отчёты и вход через SSO, сколько папок, опросов и участников, и полный список его прав.

Половина отказов инструментов — это отсутствующее право, и по списку `rights` видно заранее, что получится. Рядом `permissions` — то же с оговоркой «только свои объекты». Возможности тарифа отдельно, в `get_workspace_tariff`.

В конструкторе всё это приходит одним куском вместе со списком опросов открытой папки, и тот эндпоинт заодно запоминает последний открытый аккаунт и папку. Здесь он не используется: от чтения побочных действий не ждут.

### Участники и роли

Ассистент видит состав аккаунта и может раздавать доступы: приглашать людей, менять роли и папки, исключать участников, настраивать сами роли.

| Инструмент | Что делает |
|---|---|
| `get_workspace_members` | состав аккаунта: роль, принято ли приглашение, доступные папки, последняя активность |
| `get_workspace_roles` | роли аккаунта с правами и числом участников на каждой |
| `get_workspace_member_access` | права одного участника — те же, по которым система отказывает в действии |
| `invite_workspace_member` | приглашает человека: на его адрес уходит письмо со ссылкой |
| `set_workspace_member_role` | меняет роль участника |
| `set_workspace_member_folders` | задаёт, какие папки он видит; пустой список открывает все |
| `remove_workspace_member` | исключает из аккаунта |
| `resend_workspace_member_invite` | отправляет приглашение повторно |
| `save_workspace_role` | создаёт роль или меняет её права |
| `delete_workspace_role` | удаляет роль, её участники переходят на роль по умолчанию |

Все эти действия закрыты тем же правом, что и в интерфейсе, — управлять участниками может владелец аккаунта и те, кому он это разрешил. Настройка ролей вдобавок доступна не на всех тарифах, число участников тоже ограничено тарифом.

О чём стоит помнить, поручая это ассистенту:

- приглашение — письмо живому человеку, а исключение отбирает доступ сразу; вернуть исключённого можно только новым приглашением, и папки придётся назначить заново;
- правка роли меняет возможности всех, кто на ней сидит, а не одного человека;
- список папок задаётся целиком: передали три — участник видит ровно их.

Права принимают три значения: `allowed` — разрешено, `partially` — только свои объекты, `forbidden` — запрещено.

У роли есть базовые права на весь аккаунт и, дополнительно, отличия по отдельным папкам: в такой папке действует своё значение, в остальных — базовое. Отличия задаются аргументом `folder_permissions` у `save_workspace_role`: аргумент не передан — отличия не меняются, пустой список снимает все. По папкам настраиваются только действия с опросами и самими папками; участники, биллинг, рассылки, файлы, API, почта, шаблоны и темы остаются правами аккаунта целиком.

Что действует у конкретного участника, показывает `get_workspace_member_access`: общий набор в поле `access`, отличия — в `folder_access` (папка, её имя и права, которые действуют именно в ней). Папки, к которым участника не пускают, в отличия не попадают: там они ни на что не влияют.

Три повода, по которым доступ пропадает, а в роли всё выглядит правильно:

- приглашение отправлено, но человек по нему не перешёл;
- участник не подтвердил почту — до подтверждения ему отказывают независимо от роли;
- у участника ограничен список папок — тогда опросы из других папок он не видит.

Первые две причины `get_workspace_member_access` называет прямо в поле `blocked`.

### Брендинг воркспейса

Логотип и копирайт — настройки **воркспейса**, а не отдельного опроса: они действуют сразу на все его опросы, отчёты и PDF. Поэтому у них свои инструменты, а в `update_quiz_settings` этих полей нет.

### `update_workspace_branding`

Показ логотипа и копирайта.

| Параметр | Тип | Описание |
|---|---|---|
| `workspace_id` | `integer` | **Обязательный.** ID воркспейса из `workspace://list`. |
| `logo_show` | `boolean` | Показывать логотип воркспейса. |
| `show_copyright` | `boolean` | Показывать копирайт WebAsk. |

Нужно передать хотя бы один из двух параметров.

Скрытие копирайта (`show_copyright: false`) доступно не на всех тарифах — на остальных инструмент вернёт ошибку, а значение останется включённым.

### `upload_workspace_logo`

Загрузка логотипа по ссылке или из base64. Укажите ровно один способ.

| Параметр | Тип | Описание |
|---|---|---|
| `workspace_id` | `integer` | **Обязательный.** ID воркспейса. |
| `url` | `string` | Публичная http(s) ссылка на файл. |
| `file_base64` | `string` | Содержимое файла в base64 (допускается префикс `data:...;base64,`). |
| `filename` | `string` | Имя файла — для загрузки из base64. |

Форматы: `jpeg`, `jpg`, `png`, `webp`. Предел размера — 1 МБ. Свой логотип доступен не на всех тарифах.

### `delete_workspace_logo`

Удаляет логотип воркспейса. Параметр один — `workspace_id`.

### Своё хранилище файлов

Аккаунт на Premium и выше может складывать файлы ответов в собственное хранилище — S3 или Яндекс.Диск. Подключение остаётся в интерфейсе: для S3 это ввод ключа доступа и секрета, для Яндекс.Диска — вход в аккаунт. Ассистенту доступно состояние и диагностика.

| Инструмент | Что делает |
|---|---|
| `get_workspace_storage` | что подключено, работает ли, шаблон пути, сколько файлов лежит в хранилище |
| `get_workspace_storage_usage` | занятое место: диск платформы отдельно от своего хранилища |
| `verify_workspace_storage` | проверяет связь: пробует записать и прочитать пробный файл |
| `get_workspace_hidden_files` | сколько файлов скрыто из-за нехватки места и сколько стоит их раскрыть |

Два состояния, которые стоит знать:

- **Хранилище отвалилось.** Запись временно уходит на диск платформы, статус становится `fallback`. Удачная проверка через `verify_workspace_storage` возвращает запись обратно; запустить её может только владелец аккаунта.
- **Файлы скрыты.** Сверх дисковой квоты вложения не удаляются, а скрываются — со стороны автора это выглядит как их пропажа. Раскрывает апгрейд тарифа или оплата; оплату ассистент не проводит.

Идентификатор ключа доступа S3 ассистент видит только последними символами, секрет и токены — никогда.

Две особенности: подсчёт занятого места в своём S3 при первом обращении обходит бакет и может занять несколько секунд, а проверка связи, в отличие от правок настроек опроса, попадает не в журнал правок ассистента, а в журнал хранилища.

### `manage_workspace_files`
Файлы аккаунта: `overview`, `list`, `delete`.

`overview` — сколько занято, сколько разрешает тариф, разбивка по опросам. `list` — файлы с фильтрами по опросу, виду медиа, хранилищу и владельцу, с поиском и сортировкой.

Удаление необратимо, а файл респондента приложен к его ответу — после удаления в ответе останется ссылка в никуда. Поэтому `confirm_delete` обязателен **всегда**, а не только для режима «все». `delete_all` вместе со списком файлов отклоняется. Режим «все» уходит очередью, поэтому числа удалённых в ответе нет — приходит `queued: true`. Загрузка файлов и скачивание архивом недоступны.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `workspace_id` | `integer` | (Обязательный) ID workspace. |
| `action` | `string` | (Обязательный) `overview`, `list`, `delete`. |
| `owner` | `string` | `owner`, `respondent`, `both`. |
| `media_type` | `string` | `image`, `video`, `audio`. |
| `storage` | `string` | `default`, `s3`, `yandex_disk`. |
| `quiz_id` | `integer` | Файлы только этого опроса. |
| `search` | `string` | Поиск по имени файла. |
| `sort_by`, `sort_direction` | `string` | `quiz_id`/`size`/`created_at`/`storage`, `asc`/`desc`. |
| `page`, `per_page` | `integer` | Страница и размер, 1–200 (по умолчанию 30). |
| `quiz_ids` | `array` | (Обязательный для `delete`) Опросы, в которых удаляем. |
| `file_ids` | `array` | Конкретные файлы. Без них нужен `delete_all`. |
| `delete_all` | `boolean` | Все файлы перечисленных опросов. |
| `confirm_delete` | `boolean` | (Обязательный для `delete`) Подтверждение. |

### `manage_workspace_domain`
Адрес аккаунта: `show`, `set_subdomain`, `set_type`.

`show` отдаёт текущий вид адреса, поддомены, свой домен, состояние сертификата и что разрешает тариф. Подключение своего домена в инструмент не входит: там правка DNS на стороне владельца и выпуск сертификата с недельным лимитом на повторы — это делают в кабинете. Адрес отдельного опроса внутри домена меняет `set_quiz_link`.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `workspace_id` | `integer` | (Обязательный) ID workspace. |
| `action` | `string` | (Обязательный) `show`, `set_subdomain`, `set_type`. |
| `subdomain` | `string` | Имя поддомена. Обязателен для `set_subdomain`; пустая строка убирает поддомен и возвращает аккаунт на служебный адрес. |
| `subdomain_kind` | `string` | `default` или `other_subdomain`. |
| `domain_type` | `string` | `default`, `subdomain`, `other_subdomain`, `domain`. |

### `get_workspace_domain_status`

Свой домен или поддомен аккаунта и состояние его сертификата. Только чтение: привязка домена — это правка DNS на стороне владельца. Нужен, когда публичная ссылка не открывается: чаще всего домен привязан, а сертификат ещё не выпущен.

| Параметр | Тип | Описание |
|---|---|---|
| `workspace_id` | `integer` | **Обязательный.** ID воркспейса из `workspace://list`. |


## Тариф, оплата в опросе, партнёрка, респонденты {#tools-billing}

### Тариф аккаунта

`get_workspace_tariff` отвечает на вопрос, который стоит за половиной отказов: что именно мешает — кончился лимит, истёк срок или возможности нет в тарифе.

Показывает код тарифа, оплачен ли он и сколько дней осталось; остатки по опросам, ответам, участникам, темам, месту на диске и адресам для писем; и полный список возможностей — свой домен, своё хранилище, роли, выгрузка ответов, публичные ссылки на результаты, AI-отчёт, каждая интеграция отдельно.

Приём оплаты в опросе стоит отдельным признаком `features.quiz_payment`, а не
среди интеграций: тарифная опция у него своя, и раньше он всегда показывался
недоступным. Интеграции лежат в `features.integrations` под своими кодами
(`google_sheets`, `telegram`, `webhook` и остальные).

Только чтение: сменить тариф или оплатить через ассистента нельзя.

### Список тарифов

`get_tariff_list` показывает все тарифы с ценами, периодами и составом. Нужен, когда упёрлись в «недоступно на текущем тарифе»: по списку видно, какой тариф решает задачу. Цены считаются с учётом текущего тарифа — доплата при переходе уже учтена.

Только чтение: оплатить или сменить тариф через ассистента нельзя.

### `manage_quiz_payment`
Приём оплаты в опросе через ЮKassa: `show`, `save`.

Счета продавца здесь **не заводятся**: для них нужны идентификатор магазина и секретный токен ЮKassa, а платёжные ключи через ассистента не передают. По той же причине токен вырезан из списка счетов — наружу уходит только то, по чему счёт можно выбрать.

Отдельного переключателя «включить оплату» нет, и это не упущение: такой колонки в настройках не существует. Оплата работает, когда у аккаунта есть счёт продавца и тариф разрешает виджет оплаты (в ответе — `payment_ready`), а показ самого вопроса с оплатой задаётся его виджетом через `update_quiz_widgets`.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `action` | `string` | (Обязательный) `show` или `save`. |
| `yookassa_account_id` | `integer` | (Обязательный для `save`) Счёт продавца из `show`. Чужой отклоняется. |
| `payment_type` | `string` | (Обязательный для `save`) `fixed` или `scoring`. |
| `fixed_amount` | `number` | Сумма. Обязательна при `fixed`: иначе оплата ушла бы на ноль. |
| `contact_widget_id` | `string`/`null` | Вопрос, из которого берут контакт для чека. |

### `get_respondent_campaigns`
Заказанные респонденты: кампании закупки прохождений и их результат. Только чтение.

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

| Параметр | Тип | Описание |
|----------|-----|----------|
| `workspace_id` | `integer` | (Обязательный) ID workspace. |
| `campaign_id` | `integer` | Одна кампания. Без него — список. |

### Партнёрская программа

`get_partner_program` показывает партнёрскую статистику: баланс к выплате и удержанное, кто пришёл по партнёрской ссылке и сколько раз оплатил, начисления за период и метки источников — их партнёр вешает на свои каналы, чтобы отличать, откуда идут люди.

Период задаётся датами, выдачу можно сузить одной меткой. Только чтение: заявка на выплату и платёжные реквизиты остаются в кабинете.

### Метки партнёрских источников

`manage_partner_source` заводит, переименовывает и удаляет метки: `create`, `rename`, `delete`. Название — от 3 до 25 символов; при `create` без названия подставляется имя по порядку, как в кабинете.

Метка отделяет один канал продвижения от другого — по ней в статистике видно, откуда пришёл человек. Сами метки и статистику по ним показывает `get_partner_program`. Метки принадлежат партнёру, а не аккаунту: чужую не тронуть.


## Промокоды {#tools-promocodes}

### Промокоды (Доступно на платных тарифах)

### `create_promocode_group`
Создаёт новый список промокодов для workspace.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Список промокодов создан."}],
  "isError": false,
  "list_id": 10
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса в нужном workspace. |
| `name` | `string` | (Обязательный) Название списка. |
| `codes` | `array` | (Обязательный) Массив строковых кодов (до 1000). |

### `add_promocodes`
Добавляет новые промокоды в существующий список.
**Структура ответа (успех):**
```json
{
  "content": [{"type": "text", "text": "Промокоды успешно добавлены."}],
  "isError": false,
  "added_count": 5
}
```
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса в нужном workspace. |
| `list_id` | `integer` | (Обязательный) ID списка промокодов (`promocode://list`). |
| `codes` | `array` | (Обязательный) Массив строковых кодов. |

---

### `manage_promocode_group`
Правит списки промокодов: `update` (название, вид и размер кода), `delete`, `attach`, `detach`, `delete_code`.

Завести список и досыпать кодов — это `create_promocode_group` и `add_promocodes`. Удаление списка уносит все его коды и требует `confirm_delete`. Промокоды доступны не на всех тарифах.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) Опрос, от которого проверяются права и тариф. |
| `action` | `string` | (Обязательный) `update`, `delete`, `attach`, `detach`, `delete_code`. |
| `list_id` | `integer` | ID списка. Не нужен для `detach` и `delete_code`. |
| `name` | `string` | Новое название списка. Обязательно для `update`. |
| `code_format` | `string` | `text`, `qr`, `barcode`, `datamatrix`. |
| `code_width`, `code_height` | `integer` | Размер кода в пикселях, 20–2000. |
| `quiz_ids` | `array` | Опросы для `attach` и `detach`, до 200. Опрос чужого аккаунта не проглатывается молча — приходит отказ со списком. |
| `code_id` | `integer` | ID кода для `delete_code` (`get_promocode_codes`). |
| `confirm_delete` | `boolean` | Подтверждение удаления списка вместе с кодами. |

### `get_promocode_list_quizzes`
Показывает, к каким опросам аккаунта привязан список промокодов.

Сверху идут привязанные и текущий опрос, как в кабинете. Нужен, чтобы не отвязывать вслепую: саму привязку и отвязку делает `manage_promocode_group`.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) Опрос, от которого проверяются права и тариф. |
| `list_id` | `integer` | (Обязательный) ID списка из `get_promocode_list`. |
| `search` | `string` | Поиск опроса по названию. |
| `limit` | `integer` | Сколько вернуть, 1–500. По умолчанию 50. |
| `offset` | `integer` | Сколько пропустить. |


## Чтение данных: get-инструменты {#tools-read}

### Чтение данных (Get tools)

Эти инструменты являются полными аналогами ресурсов (Resources) для клиентов, не поддерживающих `resources/read`.

### `get_user_me`
Возвращает информацию о текущем авторизованном пользователе. Параметры не требуются.

### `get_workspace_list`
Возвращает список рабочих пространств. Параметры не требуются.

### `get_folder_list`
Возвращает список папок текущего пользователя. Параметры не требуются.

### `get_quiz_list`
Возвращает список опросов аккаунта. Без параметров — все живые опросы, как раньше.

Архивные опросы в обычный список не попадают: их показывает `archived: true`. У каждого опроса есть признак `is_archived`.

| Параметр | Тип | Описание |
|----------|-----|----------|
| `name` | `string` | Поиск по названию, частичное совпадение без учёта регистра. |
| `folder_id` | `integer` | Только опросы этой папки (`folder://list`). |
| `archived` | `boolean` | `true` — архивные вместо обычных. По умолчанию `false`. |
| `limit` | `integer` | Сколько вернуть, 1–200. |
| `offset` | `integer` | Сколько пропустить. |

### `get_quiz_structure`
Возвращает структуру виджетов опроса. Аналог `quiz://{id}/structure`.
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |

### `get_quiz_answers`
Возвращает ответы респондентов. Аналог `quiz://{id}/answers`.
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `limit` | `integer` | Количество ответов (по умолчанию 20, макс. 100). |
| `offset` | `integer` | Смещение (по умолчанию 0). |
| `date` | `string` | Фильтр по дате (формат `Y-m-d`). |
| `is_complete` | `boolean` | `true` — только завершённые, `false` — незавершённые. |
| `sort` | `string` | Поле сортировки: `date_end` или `date_start`. |
| `order` | `string` | Направление: `asc` или `desc`. |

### `get_quiz_hidden_options`
Возвращает скрытые служебные опции опроса. Аналог `quiz://{id}/hidden_options`.
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |

### `get_quiz_texts`
Возвращает надписи (тексты) опроса. Аналог `quiz://{id}/texts`.
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |

### `get_quiz_variables`
Возвращает скрытые переменные (UTM/extra_fields). Аналог `quiz://{id}/variables`.
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |

### `get_quiz_summary`
Возвращает сводную аналитику опроса. Аналог `quiz://{id}/summary`.
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |

**Ответ приходит в одной из двух форм — по формату опроса.**

Обычный опрос (`type = regular`) — как раньше: визиты, устройства, география, воронка по дням.

Опрос на сайте (`type = micro`) показывается карточкой внутри чужой страницы и своей страницы не имеет, поэтому визитов у него нет и обычная сводка отдала бы нули. Вместо неё приходит воронка показов:

| Поле | Тип | Описание |
|----------|-----|----------|
| `quiz_type` | `string` | Всегда `micro`. У обычного опроса ключа нет вовсе — по нему форма и различается. |
| `is_connected` | `boolean` | `false` — опрос ещё не подключён ни к одному сайту, показов не было, остальных полей нет. |
| `site_domain` | `string` | Домен сайта, на котором показывается опрос. |
| `summary` | `object` | Воронка и разрезы: показы (события и посетители), открытия, начатые и завершённые прохождения, отказы, на каком вопросе бросают, по дням, страницам, устройствам и странам, а также причины, по которым опрос не показали. |

Пока подключения нет, ответ ограничен признаками:

```json
{ "quiz_type": "micro", "is_connected": false }
```

### `get_quiz_report`
Возвращает детальный отчёт по виджетам. Аналог `quiz://{id}/report`. Возвращает `report_uuid`, нужный для `get_quiz_report_files` и `get_quiz_report_inputs`.
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `is_complete` | `boolean` | Только завершённые (по умолчанию `true`). |
| `date_from` | `string` | Начало периода (`Y-m-d`). |
| `date_to` | `string` | Конец периода (`Y-m-d`). |

### `get_quiz_widgets_hidden`
Возвращает скрытую мета-информацию виджетов. Аналог `quiz://{id}/widgets_hidden`.
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |

### `get_quiz_report_filters`
Возвращает сохранённые фильтры отчётов. Аналог `quiz://{id}/report_filters`.
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |

### `get_quiz_report_files`
Возвращает файловые ответы для виджета из отчёта. Аналог `quiz://{id}/report/{uuid}/files`.
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `report_uuid` | `string` | (Обязательный) UUID отчёта из `get_quiz_report`. |
| `widget_id` | `string` | (Обязательный) UUID виджета из `get_quiz_structure`. |
| `widget_hash` | `string` | (Обязательный) Hash виджета из `get_quiz_structure`. |
| `limit` | `integer` | Количество записей (по умолчанию 50). |
| `offset` | `integer` | Смещение (по умолчанию 0). |

### `get_quiz_report_inputs`
Возвращает текстовые ответы на input-виджеты из отчёта. Аналог `quiz://{id}/report/{uuid}/inputs`.
| Параметр | Тип | Описание |
|----------|-----|----------|
| `quiz_id` | `integer` | (Обязательный) ID опроса. |
| `report_uuid` | `string` | (Обязательный) UUID отчёта из `get_quiz_report`. |
| `widget_id` | `string` | (Обязательный) UUID виджета из `get_quiz_structure`. |
| `widget_hash` | `string` | (Обязательный) Hash виджета из `get_quiz_structure`. |
| `limit` | `integer` | Количество записей (по умолчанию 50). |
| `offset` | `integer` | Смещение (по умолчанию 0). |

### `get_theme_list`
Возвращает список тем оформления. Аналог `theme://list`. Параметры не требуются.

### `get_theme_details`
Возвращает детальные настройки темы. Аналог `theme://{id}/details`.
| Параметр | Тип | Описание |
|----------|-----|----------|
| `theme_id` | `integer` | (Обязательный) ID темы. |

### `get_promocode_list`
Возвращает списки промокодов воркспейса. Аналог `promocode://list`.
| Параметр | Тип | Описание |
|----------|-----|----------|
| `workspace_id` | `integer` | (Обязательный) ID рабочего пространства. |
| `search` | `string` | Поисковая строка. |
| `sort` | `string` | Поле сортировки. |
| `order` | `string` | `asc` или `desc`. |
| `offset` | `integer` | Смещение (по умолчанию 0). |
| `limit` | `integer` | Количество записей (по умолчанию 20). |

### `get_promocode_codes`
Возвращает коды конкретного списка промокодов. Аналог `promocode://{id}/codes`.
| Параметр | Тип | Описание |
|----------|-----|----------|
| `list_id` | `integer` | (Обязательный) ID списка из `get_promocode_list`. |
| `workspace_id` | `integer` | (Обязательный) ID рабочего пространства. |
| `search` | `string` | Поисковая строка. |
| `used` | `integer` | `1` — использованные, `0` — неиспользованные. |
| `sort` | `string` | Поле сортировки. |
| `order` | `string` | `asc` или `desc`. |
| `offset` | `integer` | Смещение (по умолчанию 0). |
| `limit` | `integer` | Количество записей (по умолчанию 20). |

---
