{
  "openapi": "3.0.1",
  "info": {
    "title": "Wikibot REST API",
    "description": "Wikibot REST API to manage knowledege bases and interacting with the bot\n\n---\n\n## Управление конфигурацией бота\n\nЧтение и изменение конфигурации бота Wikibot по HTTP: настройки бота и шаблоны, агенты бота — их навыки, сценарии, промпты и опции, проактивные задачи (jobs), таблицы данных (datatables), кастомные REST-инструменты (tools), приватные статьи базы знаний и журнал запросов / история диалогов.\n\nЭто тот же API, которым пользуется ассистент в дашборде. Через него можно создавать и редактировать, но ни один метод ничего не удаляет — удаление агента, задачи, таблицы данных или инструмента доступно только из дашборда.\n\nАутентификация. Каждый запрос передаёт API-ключ интеграции в заголовке `Authorization`, без префикса `Bearer`.\n\nПрава доступа (scopes). У ключа есть список прав доступа. Всё, что описано в этом документе, требует право `manage`, если явно не указано иное; `manage` никогда не выдаётся по умолчанию, поэтому у уже существующего ключа будет 403, пока его права не обновят на странице бота Settings → API Keys.\n\nЛимиты запросов. 120 запросов в минуту на бота суммарно по всем методам этого раздела. Превышение возвращает 429 с заголовком `Retry-After`, содержащим число секунд до конца окна. Тарифицируемые методы (`/ask`, `/search`, `/anonymize`, `/deanonymize`) под лимиты не подпадают.\n\nОшибки. Тело любого ответа не из диапазона 2xx имеет вид `{ \"error\": \"<message>\" }`.\n\ndryRun. Методы записи, которые его поддерживают (`PATCH /config`, `POST /flows/{flowId}/agents`, `PATCH /flows/{flowId}/agents/{name}`, `POST /flows/{flowId}/clone`), проверяют весь запрос и возвращают diff «было/стало» без записи, если `dryRun` равен `true`. Чтобы применить изменения, отправьте тот же запрос повторно без этого флага.",
    "license": {
      "name": "MIT"
    },
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "https://api.wikibot.pro"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Knowledge base",
      "description": "Источники базы знаний (краулинг/загрузка) и поиск по ней."
    },
    {
      "name": "Interaction",
      "description": "Задать вопрос боту и получить ответ."
    },
    {
      "name": "Anonymization",
      "description": "Анонимизация и деанонимизация текста — замена и восстановление персональных данных."
    },
    {
      "name": "Webhooks",
      "description": "Настройка webhook URL бота."
    },
    {
      "name": "Bot config",
      "description": "Общие настройки бота и шаблоны."
    },
    {
      "name": "Flows and agents",
      "description": "Агенты бота, их навыки, сценарии и клонирование агента."
    },
    {
      "name": "Jobs",
      "description": "Проактивные триггеры, которые срабатывают после затишья в диалоге."
    },
    {
      "name": "Datatables",
      "description": "Структурированные таблицы, которые может опрашивать агент, и то, каким агентам это разрешено."
    },
    {
      "name": "Tools",
      "description": "Кастомные REST-функции, которые может вызывать модель агента."
    },
    {
      "name": "Articles",
      "description": "Приватные статьи базы знаний."
    },
    {
      "name": "Journal and history",
      "description": "Журнал запросов и полная история диалогов с аудитом."
    }
  ],
  "paths": {
    "/api/bot/set-webhook-url": {
      "post": {
        "summary": "Задать webhook URL",
        "operationId": "set-webhook-url",
        "requestBody": {
          "description": "Задать webhook URL для бота",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Webhook"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Create webhook url response"
          },
          "400": {
            "description": "Unexpected error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "tags": [
          "Webhooks"
        ]
      }
    },
    "/api/bot/kb/upload-file": {
      "post": {
        "summary": "Загрузить документ",
        "description": "Загружает файл в базу знаний. Принимает multipart/form-data, где поле `file` содержит сам файл.",
        "operationId": "kb-upload",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Файл, который нужно загрузить (pdf или docx)"
                  }
                },
                "required": [
                  "file"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Файл успешно загружен и отправлен на индексирование",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "fileId": {
                      "type": "number",
                      "example": 1235
                    }
                  },
                  "required": [
                    "file"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Неверный запрос (например, файл больше 10мб или отт)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "file size is exceeded"
                    }
                  },
                  "required": [
                    "error"
                  ]
                }
              }
            }
          },
          "500": {
            "description": "Ошибка сервера при загрузке файла"
          }
        },
        "tags": [
          "Knowledge base"
        ]
      }
    },
    "/api/bot/ask": {
      "get": {
        "summary": "Задать вопрос",
        "operationId": "ask-get",
        "description": "Запрос возвращает синхронный ответ, либо вызывает webhook URL в зависимости от способа взаимодействия.",
        "parameters": [
          {
            "name": "chatId",
            "in": "query",
            "description": "Внешний идентификатор чата с клиентом",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "name": "query",
            "in": "query",
            "description": "Запрос",
            "schema": {
              "type": "string"
            },
            "required": true
          },
          {
            "name": "format",
            "in": "query",
            "description": "Форматирование ответа: links - добавить ссылки вида (name)[link], raw - неформатированный ответ",
            "schema": {
              "type": "string",
              "enum": [
                "links",
                "raw"
              ]
            }
          },
          {
            "name": "msgId",
            "in": "query",
            "description": "Идентификатор сообщения",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ask query response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/Answer"
                }
              }
            }
          },
          "400": {
            "description": "Unexpected error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "tags": [
          "Interaction"
        ]
      },
      "post": {
        "summary": "Задать вопрос",
        "operationId": "ask",
        "description": "Запрос возвращает синхронный ответ, либо вызывает webhook URL в зависимости от способа взаимодействия.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "chatId": {
                    "type": "string",
                    "description": "Внешний идентификатор чата с клиентом",
                    "example": "12345"
                  },
                  "query": {
                    "type": "string",
                    "description": "Запрос",
                    "example": "Как дела?"
                  },
                  "format": {
                    "type": "string",
                    "description": "Форматирование ответа: links - добавить ссылки вида (name)[link], raw - неформатированный ответ",
                    "enum": [
                      "links",
                      "raw"
                    ],
                    "example": "links"
                  },
                  "msgId": {
                    "type": "string",
                    "description": "Идентификатор сообщения",
                    "example": "67890"
                  },
                  "attachments": {
                    "type": "array",
                    "description": "Список вложений (URL)",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "https://example.com/file.png"
                    ]
                  },
                  "agentId": {
                    "type": "number",
                    "description": "Идентификатор агента, который должен выполнить запрос",
                    "example": 5
                  }
                },
                "required": [
                  "chatId",
                  "query"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ask query response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "$ref": "#/components/schemas/AnswerNew"
                }
              }
            }
          },
          "400": {
            "description": "Unexpected error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "tags": [
          "Interaction"
        ]
      }
    },
    "/api/bot/kb-create": {
      "post": {
        "summary": "Создать БЗ",
        "operationId": "kb-create",
        "requestBody": {
          "description": "Создать базу знаний",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/KbUrl"
              }
            }
          },
          "required": true
        },
        "description": "Создать базу знаний",
        "responses": {
          "200": {
            "description": "success"
          },
          "400": {
            "description": "fail"
          }
        },
        "tags": [
          "Knowledge base"
        ]
      }
    },
    "/api/bot/anonymize": {
      "post": {
        "summary": "Анонимизировать текст",
        "operationId": "anonymize",
        "description": "Анонимизация — заменяет в тексте PII на случайные значения. Возвращает анонимизированный текст и карту замен.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AnonymizeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Анонимизированный текст и список замен",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnonymizeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Ошибка валидации/парсинга",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "tags": [
          "Anonymization"
        ]
      }
    },
    "/api/bot/deanonymize": {
      "post": {
        "summary": "Деанонимизировать текст",
        "operationId": "deanonymize",
        "description": "Деанонимизация — обратная замена: пытается восстановить оригинальные значения по карте замен с учётом морфологии и форматов во входном тексте.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeanonymizeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Деанонимизированный текст",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeanonymizeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Ошибка валидации",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "tags": [
          "Anonymization"
        ]
      }
    },
    "/api/categorize": {
      "post": {
        "summary": "Категоризировать диалог",
        "operationId": "categorize",
        "description": "Отправляет завершённый диалог на категоризацию боту-категоризатору. Базовый URL метода — `https://api.wikibot.pro/api`, без `/bot`. Обработка асинхронная: метод сразу возвращает идентификатор запроса, а результат отправляется на webhook URL бота — см. [Подключения бота-категоризатора](/docs/categorizer/connections#api).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "chatId": {
                    "type": "string",
                    "description": "Идентификатор диалога или тикета в вашей системе",
                    "example": "12345"
                  },
                  "dialog": {
                    "type": "string",
                    "description": "Текст диалога, который нужно категоризировать",
                    "example": "Клиент: не могу оплатить подписку картой\nОператор: попробуйте другую карту\nКлиент: спасибо, получилось"
                  },
                  "dialogStartedAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Дата и время начала диалога в формате ISO 8601. Используется как ось времени в отчётах — если не передана, берётся время обработки",
                    "example": "2026-08-01T09:15:00Z"
                  },
                  "dialogEndedAt": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Дата и время окончания диалога в формате ISO 8601",
                    "example": "2026-08-01T09:42:00Z"
                  },
                  "meta": {
                    "type": "object",
                    "description": "Произвольные дополнительные данные о диалоге. Сохраняются вместе с результатом и не влияют на классификацию",
                    "additionalProperties": true,
                    "example": {
                      "channel": "telegram"
                    }
                  }
                },
                "required": [
                  "chatId",
                  "dialog"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Диалог принят в обработку",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "requestId": {
                      "type": "string",
                      "description": "Идентификатор запроса. По нему запрос можно найти в журнале бота",
                      "example": "a1b2c3d4-5e6f-7890-abcd-ef1234567890"
                    },
                    "chatId": {
                      "type": "string",
                      "description": "Идентификатор диалога, переданный в запросе",
                      "example": "12345"
                    }
                  },
                  "required": [
                    "requestId",
                    "chatId"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Неверный формат запроса или бот не является категоризатором",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Интеграция отключена",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "tags": [
          "Categorization"
        ]
      }
    },
    "/api/bot/config": {
      "get": {
        "tags": [
          "Bot config"
        ],
        "summary": "Получить текущие настройки бота",
        "description": "Возвращает разрешённый к чтению срез конфигурации: шаблоны, фильтры, флаги медиа/оператора, рабочие часы, язык и глоссарий.",
        "operationId": "getBotConfig",
        "responses": {
          "200": {
            "description": "Текущая конфигурация",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BotConfigView"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Bot config"
        ],
        "summary": "Изменить настройки бота",
        "description": "Применяет одно или несколько изменений настроек, адресованных по `path`. Принимаются только пути, которые возвращает `GET /api/bot/config/schema`.\n\n- Все изменения проверяются относительно одного снимка состояния; если хотя бы одно невалидно, весь запрос отклоняется с 400 и ничего не записывается.\n- Изменение, у которого новое значение совпадает с текущим, исключается из diff и никогда не отмечается как применённое.\n- Путь типа `text`, который сейчас хранит несколько вариантов (например, несколько приветствий), отклоняется с 400, а не схлопывается в одну строку.\n- При повторной отправке одного и того же `path` учитывается последнее вхождение.\n\nКаждое применённое изменение также пишет событие бота `bot_setting_update`.",
        "operationId": "patchBotConfig",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchConfigRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Diff того, что изменилось (или изменилось бы, для `dryRun`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PatchConfigResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/config/schema": {
      "get": {
        "tags": [
          "Bot config"
        ],
        "summary": "Получить список настроек, которые можно изменить",
        "description": "Полный и единственный набор путей, которые принимает `PATCH /api/bot/config`. `effect` присутствует у булевых настроек, чьё имя читается как отрицание, и является авторитетным источником того, что на самом деле делают значения `true`/`false`.",
        "operationId": "getBotConfigSchema",
        "responses": {
          "200": {
            "description": "Описания настроек",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SettingSpec"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/flows": {
      "get": {
        "tags": [
          "Flows and agents"
        ],
        "summary": "Получить список агентов бота",
        "operationId": "listFlows",
        "responses": {
          "200": {
            "description": "Агенты",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Flow"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/agents": {
      "get": {
        "tags": [
          "Flows and agents"
        ],
        "summary": "Список агентов бота (устаревший метод)",
        "description": "Этот метод устарел — оставлен, потому что часть существующих ключей уже его использует, и, в отличие от `GET /api/bot/flows`, он принимает любое из прав доступа — `ask` или `manage`. В новых интеграциях используйте `GET /api/bot/flows`.",
        "operationId": "listFlowsDeprecated",
        "deprecated": true,
        "responses": {
          "200": {
            "description": "Агенты",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Flow"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/flows/{flowId}/agents": {
      "parameters": [
        {
          "$ref": "#/components/parameters/FlowId"
        }
      ],
      "get": {
        "tags": [
          "Flows and agents"
        ],
        "summary": "Получить элементы агента",
        "description": "Возвращает основную инструкцию агента, его навыки и сценарии, включая навыки, которые ни разу не включались. Навык, для которого ещё нет строки в БД, возвращается с `id: 0` и промптом по умолчанию — строка создаётся при первой записи.",
        "operationId": "listFlowAgents",
        "responses": {
          "200": {
            "description": "Элементы агента",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Agent"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "tags": [
          "Flows and agents"
        ],
        "summary": "Создать сценарного агента",
        "description": "Создаёт только сценарного агента — служебные агенты уже существуют виртуально (см. `id: 0` выше) и создаются своим первым `PATCH`.\n\nСохранённое имя агента получает вид `scenario_<name>`. `description` читается логикой маршрутизации агента по умолчанию, чтобы решить, когда передавать диалог этому сценарию, и клиенту не показывается.\n\nСам по себе новый сценарий ничего не делает: агент по умолчанию узнаёт о его существовании только тогда, когда его `prompt` упоминает этот сценарий, — поэтому создание сценария стоит сопровождать `PATCH` промпта агента по умолчанию. При успехе записывается событие бота `scenario_create`.",
        "operationId": "createScenarioAgent",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateScenarioRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Созданный сценарий (или его превью, для `dryRun`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateScenarioResponse"
                }
              }
            }
          },
          "400": {
            "description": "Неверное тело запроса, либо имя пустое / уже занято у этого агента / уже содержит префикс `scenario_` / совпадает с зарезервированным именем (`default`, `spam`, `operator` и т.п.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ManageApiError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/flows/{flowId}/agents/{name}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/FlowId"
        },
        {
          "name": "name",
          "in": "path",
          "required": true,
          "description": "Имя агента в том виде, в каком его возвращает `GET /api/bot/flows/{flowId}/agents` — например, `default`, `spam`, `scenario_refunds`. Адресация по имени, а не по id, потому что у служебного агента ещё может не быть строки в БД.",
          "schema": {
            "type": "string"
          },
          "example": "scenario_refunds"
        }
      ],
      "patch": {
        "tags": [
          "Flows and agents"
        ],
        "summary": "Изменить промпт агента, его состояние вкл/выкл или опции",
        "description": "Отправьте любое подмножество полей.\n\n- `enabled` применяется только к `spam`, `operator`, `editor`, `translator`, `summary`; `default` работает всегда, а активация сценария — не переключатель, который открывает этот API (в обоих случаях 400).\n- `prompt` применяется к `default`, `spam`, `operator`, `editor`, `summary` и к любому сценарию; у `translator` нет инструкции (400).\n- `options.workAsSkill` и `options.description` применяются только к сценарию, `options.languages` — к `translator`, `options.minClientMessages` — к `summary`. Остальные ключи опций применяются к тому агенту, который был адресован, и должны быть булевыми.\n\nЗаписывает события бота `agent_enable`/`agent_disable` или `agent_options_change`; изменение промпта событий не создаёт.",
        "operationId": "patchFlowAgent",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PatchAgentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Diff того, что изменилось (или изменилось бы, для `dryRun`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PatchAgentResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/flows/{flowId}/clone": {
      "parameters": [
        {
          "$ref": "#/components/parameters/FlowId"
        }
      ],
      "post": {
        "tags": [
          "Flows and agents"
        ],
        "summary": "Продублировать агента",
        "description": "Копирует все навыки и сценарии (промпт, опции, состояние включённости), их доступ к инструментам и таблицам данных, а также все задачи.\n\n- Клон всегда создаётся выключенным, и склонированные задачи создаются выключенными независимо от состояния исходной задачи.\n- Инструменты и таблицы данных не дублируются: навыки и сценарии нового агента указывают на те же строки, поэтому изменение одной сущности затрагивает обоих агентов.\n\nЗаписывает событие бота `flow_clone`.",
        "operationId": "cloneFlow",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CloneFlowRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Созданный агент (или его превью, для `dryRun`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CloneFlowResponse"
                }
              }
            }
          },
          "400": {
            "description": "Пустое имя, отсутствует исходный агент, либо у бота уже достигнут лимит агентов (10 на бота)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ManageApiError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/flows/{flowId}/jobs": {
      "parameters": [
        {
          "$ref": "#/components/parameters/FlowId"
        }
      ],
      "get": {
        "tags": [
          "Jobs"
        ],
        "summary": "Получить список задач агента",
        "description": "Включает `enabledAt` и счётчики запусков по статусам. Растущее значение `runs.skipped` обычно означает, что диалоги не проходят `conversationFilter`, а не то, что задача сломана — однажды пропущенный диалог пропускается для этой задачи навсегда.",
        "operationId": "listJobs",
        "responses": {
          "200": {
            "description": "Задачи",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Job"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "tags": [
          "Jobs"
        ],
        "summary": "Создать задачу",
        "description": "Задача срабатывает самостоятельно после того, как диалог молчит `inactivityMinutes`, и только для диалогов, затихших после последнего включения задачи — повторное включение задачи сбрасывает этот отсчёт. При срабатывании агент получает системное сообщение, построенное из `instructions`, и сам пишет текст, видимый клиенту; `instructions` — это указания, а не готовое сообщение.\n\n`dryRun` здесь не поддерживается. Записывает событие бота `job_create`.",
        "operationId": "createJob",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateJobRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Задача в сохранённом виде",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/flows/{flowId}/jobs/{jobId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/FlowId"
        },
        {
          "name": "jobId",
          "in": "path",
          "required": true,
          "schema": {
            "type": "integer"
          },
          "example": 5
        }
      ],
      "patch": {
        "tags": [
          "Jobs"
        ],
        "summary": "Изменить задачу",
        "description": "Отправьте только те поля, которые меняются; остальные сохраняются из уже записанной задачи. Записывает событие бота `job_update`.",
        "operationId": "patchJob",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EditJobRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Задача в сохранённом виде после изменения",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/datatables": {
      "get": {
        "tags": [
          "Datatables"
        ],
        "summary": "Получить список таблиц данных",
        "operationId": "listDatatables",
        "responses": {
          "200": {
            "description": "Таблицы данных этого бота",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DatatableSummary"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "tags": [
          "Datatables"
        ],
        "summary": "Создать таблицу данных",
        "description": "Каждая таблица автоматически получает доступные только для чтения колонки `_id`, `_created_at` и `_updated_at` — их можно опрашивать, но они не входят в `columns`, а имя колонки, начинающееся с `_`, отклоняется. Не передавайте `assignedAgentIds`, чтобы использовать поведение по умолчанию из дашборда (сам агент и каждый включённый сценарий активного агента бота).",
        "operationId": "createDatatable",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDatatableRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Созданная таблица",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DatatableResponse"
                }
              }
            }
          },
          "400": {
            "description": "Неверное тело запроса, невалидная схема (некорректное или зарезервированное имя колонки, слишком много колонок, достигнут лимит), либо один из `assignedAgentIds` недоступен для назначения этому боту",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ManageApiError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/datatables/query": {
      "post": {
        "tags": [
          "Datatables"
        ],
        "summary": "Выполнить запрос только на чтение по таблицам данных",
        "description": "Читает реальные строки. Запрос выполняется над изолированной копией только указанных таблиц в памяти, а не над продакшн-базой, поэтому любой `SELECT` только на чтение безопасен; DDL/DML и конструкции, способные выйти за пределы песочницы, отклоняются.\n\n`truncated: true` означает, что строк подошло больше, чем вернулось (ограничение по количеству и размеру).",
        "operationId": "queryDatatables",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QueryDatatableRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Результат запроса",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QueryDatatableResponse"
                }
              }
            }
          },
          "400": {
            "description": "Неизвестное имя таблицы, невалидный или отклонённый SQL, либо запрос вытягивает больше данных, чем допускает лимит размера",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ManageApiError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/datatables/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DatatableId"
        }
      ],
      "patch": {
        "tags": [
          "Datatables"
        ],
        "summary": "Изменить описание или схему таблицы данных",
        "description": "Передача `columns` заменяет схему (но не данные).",
        "operationId": "patchDatatable",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EditDatatableRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Обновлённая таблица",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DatatableResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/datatables/{id}/agents": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DatatableId"
        }
      ],
      "get": {
        "tags": [
          "Datatables"
        ],
        "summary": "Получить список агентов, которым разрешено опрашивать эту таблицу",
        "operationId": "listDatatableAgents",
        "responses": {
          "200": {
            "description": "Назначения",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/DatatableAssignment"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/datatables/{id}/agents/{agentId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/DatatableId"
        },
        {
          "$ref": "#/components/parameters/AgentId"
        }
      ],
      "patch": {
        "tags": [
          "Datatables"
        ],
        "summary": "Выдать или отозвать доступ одного агента к одной таблице",
        "description": "Доступны только агенты `default` и сценарии — `spam`, `operator`, `translator`, `editor` и `summary` никогда не видят таблицы данных. Агент должен присутствовать в `GET /api/bot/agents/assignable` для этого бота.",
        "operationId": "setDatatableAgent",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetAssignmentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Итоговое назначение",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "tableId",
                    "agentId",
                    "enabled"
                  ],
                  "properties": {
                    "tableId": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "agentId": {
                      "type": "integer"
                    },
                    "enabled": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/agents/assignable": {
      "get": {
        "tags": [
          "Flows and agents"
        ],
        "summary": "Получить список агентов, которым можно выдать доступ к инструменту или таблице данных",
        "description": "Охватывает всех агентов бота и является источником допустимых значений `assignedAgentIds` как для `POST /api/bot/datatables`, так и для `POST /api/bot/tools`. `id` — тот же числовой идентификатор агента, который возвращает `GET /api/bot/flows/{flowId}/agents`.",
        "operationId": "listAssignableAgents",
        "responses": {
          "200": {
            "description": "Агенты, доступные для назначения",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AssignableAgent"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/tools": {
      "get": {
        "tags": [
          "Tools"
        ],
        "summary": "Получить список кастомных REST-инструментов",
        "description": "Агент присутствует в `assignedTo`, пока инструмент к нему подключён.",
        "operationId": "listTools",
        "responses": {
          "200": {
            "description": "Инструменты",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ToolSummary"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "tags": [
          "Tools"
        ],
        "summary": "Создать кастомный REST-инструмент",
        "description": "Инструмент — это функция, которую модель агента может вызвать посреди диалога, чтобы обратиться к внешнему REST API. Один вызов может развернуться в до 5 запросов (`actions`).\n\n- `parameters` — это объект JSON Schema (формат OpenAI function-calling), а не строка.\n- `{placeholder}` в `url`/`payload` подставляется из соответствующего аргумента в момент вызова; допустимыми плейсхолдерами являются только имена, объявленные в `parameters.properties`.\n- `payload` применяется только к `POST`/`PUT`/`PATCH`.\n- Каждый `url` должен быть публичным http(s)-адресом — `localhost`, приватные диапазоны и эндпоинты метаданных облака отклоняются с 400, поскольку запрос выполняют серверы платформы без присмотра.\n- Значения заголовков сохраняются как переданы и считываются обратно через `GET /api/bot/tools/{id}` любым держателем ключа с правом `manage`.\n- Не передавайте `assignedAgentIds`, чтобы создать инструмент без назначения.\n\nЛимит — 50 инструментов на бота. Метод не идемпотентен — уникального ограничения на имя нет, поэтому слепой повтор после таймаута может создать дубликат, и удалить его можно только из дашборда. Записывает событие бота `create_function`.",
        "operationId": "createTool",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateToolRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Созданный инструмент",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResponse"
                }
              }
            }
          },
          "400": {
            "description": "Неверное тело запроса, невалидное определение функции или действия (включая непубличный URL), достигнут лимит инструментов, либо один из `assignedAgentIds` недоступен для назначения этому боту",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ManageApiError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/tools/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ToolId"
        }
      ],
      "get": {
        "tags": [
          "Tools"
        ],
        "summary": "Прочитать инструмент целиком",
        "description": "Включает `parameters` и каждое действие с его реальными сохранёнными значениями заголовков и URL — токены и API-ключи возвращаются дословно держателю ключа с правом `manage`.",
        "operationId": "getTool",
        "responses": {
          "200": {
            "description": "Определение инструмента",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolDetail"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Tools"
        ],
        "summary": "Изменить инструмент",
        "description": "Отправьте любое подмножество полей. Передача `actions` заменяет весь список — сначала прочитайте `GET /api/bot/tools/{id}` и повторно отправьте неизменённые действия, если редактируете только одно. Записывает событие бота `update_function`.",
        "operationId": "patchTool",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EditToolRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Обновлённый инструмент",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/tools/{id}/agents": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ToolId"
        }
      ],
      "get": {
        "tags": [
          "Tools"
        ],
        "summary": "Получить список агентов, к которым подключён этот инструмент",
        "operationId": "listToolAgents",
        "responses": {
          "200": {
            "description": "Подключённые агенты",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ToolAssignment"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/tools/{id}/agents/{agentId}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ToolId"
        },
        {
          "$ref": "#/components/parameters/AgentId"
        }
      ],
      "patch": {
        "tags": [
          "Tools"
        ],
        "summary": "Подключить или отключить инструмент от агента",
        "description": "Доступны только агенты `default` и сценарии, и агент должен присутствовать в `GET /api/bot/agents/assignable` для этого бота.",
        "operationId": "setToolAgent",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetAssignmentRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Итоговое состояние связи",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "toolId",
                    "agentId",
                    "enabled"
                  ],
                  "properties": {
                    "toolId": {
                      "type": "integer"
                    },
                    "agentId": {
                      "type": "integer"
                    },
                    "enabled": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/articles": {
      "post": {
        "tags": [
          "Articles"
        ],
        "summary": "Создать приватную статью базы знаний",
        "description": "Через этот API можно создавать и редактировать только приватные (написанные вручную) статьи; текст, полученный краулингом или загрузкой файла, принадлежит своему источнику. Переиндексация запускается автоматически — отдельного шага публикации нет.\n\nЭтот метод ограничивает длину одной статьи, но не общую квоту размера базы знаний аккаунта, поэтому массовые импорты стоит держать в разумных пределах.",
        "operationId": "createArticle",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateArticleRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Id созданной статьи",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id"
                  ],
                  "properties": {
                    "id": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/articles/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ArticleId"
        }
      ],
      "get": {
        "tags": [
          "Articles"
        ],
        "summary": "Прочитать одну статью базы знаний",
        "description": "`editable: false` помечает статью, полученную краулингом или загрузкой: её флаг `indexed` можно переключать, но `title`/`content` изменить нельзя. Идентификаторы статей также возвращает `GET /api/bot/search`.",
        "operationId": "getArticle",
        "responses": {
          "200": {
            "description": "Статья",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Article"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "tags": [
          "Articles"
        ],
        "summary": "Изменить текст статьи или её включение в поисковый индекс",
        "description": "`title` и `content` нужно отправлять вместе (если меняется только одно поле, сначала прочитайте статью), и они работают только для редактируемой статьи. `indexed` можно отправлять отдельно, и он работает для любой статьи. Можно отправить изменение текста и `indexed` одним вызовом. Требуется хотя бы одно поле.",
        "operationId": "patchArticle",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EditArticleRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Применено",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "id",
                    "applied"
                  ],
                  "properties": {
                    "id": {
                      "type": "integer"
                    },
                    "applied": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Неверное тело запроса, нечего менять, `title`/`content` отправлены раздельно, либо статья недоступна для редактирования",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ManageApiError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/conversations/{chatId}/history": {
      "get": {
        "tags": [
          "Journal and history"
        ],
        "summary": "Прочитать сообщения и аудит диалога",
        "description": "`chatId` — это внешний идентификатор диалога, и его формат зависит от канала (id чат-центра, голый номер в мессенджере, номер тикета хелпдеска, id песочницы) — передавайте идентификатор как есть, не проверяя его формат; 404 означает, что для этого бота он не разрешился.",
        "operationId": "getConversationHistory",
        "parameters": [
          {
            "name": "chatId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "wb_85"
          },
          {
            "name": "page",
            "in": "query",
            "description": "Номер страницы сообщений, начиная с 0, по 30 на страницу.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "История диалога, либо форма `preprocessed` с одной парой запрос/ответ",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ConversationHistory"
                    },
                    {
                      "$ref": "#/components/schemas/PreprocessedHistory"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/journal": {
      "get": {
        "tags": [
          "Journal and history"
        ],
        "summary": "Искать и просматривать журнал запросов",
        "description": "Одна строка на пару запрос/ответ — тот же журнал, что показывает страница Journal в дашборде, а не по одной строке на диалог. Используйте его, чтобы найти запросы по диапазону дат, результату или текстовому поиску; затем передайте `chatId` найденной строки в `GET /api/bot/conversations/{chatId}/history`, чтобы получить полный диалог.\n\nСтроки, которые администратор пометил как внутренние, здесь никогда не возвращаются.",
        "operationId": "listJournal",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "Номер страницы, начиная с 0. Размер страницы фиксирован — 20.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "query",
            "in": "query",
            "description": "Ищет подстроку в тексте запроса (без учёта регистра) либо точное совпадение `chatId`/`requestId`/id строки. Id строки можно указать с префиксом `A`, как в дашборде, или без него (`A1234` или `1234`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Нижняя граница в формате ISO 8601, включительно. Должна передаваться вместе с `to` (иначе 400). Расширение до границ суток не применяется — передавайте именно тот момент времени, который имеете в виду.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Верхняя граница в формате ISO 8601, включительно. Должна передаваться вместе с `from`.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "type",
            "in": "query",
            "description": "Типы результата через запятую (либо параметр, повторённый несколько раз).",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/QueryResultType"
              }
            }
          },
          {
            "name": "operatorReason",
            "in": "query",
            "description": "Причины передачи запроса оператору через запятую; имеет смысл, когда `type` включает `OPERATOR`.",
            "style": "form",
            "explode": false,
            "schema": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/OperatorReason"
              }
            }
          },
          {
            "name": "sortField",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "id",
                "chatId",
                "type",
                "createdAt"
              ],
              "default": "createdAt"
            }
          },
          {
            "name": "sortDesc",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": true
            }
          },
          {
            "name": "includeAudit",
            "in": "query",
            "description": "Также вернуть для каждой строки разобранные `steps` журнала (какой шаблон/агент/скилл обработал запрос и почему).",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Одна страница строк журнала",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JournalPage"
                }
              }
            }
          },
          "400": {
            "description": "Неверные параметры запроса, либо передан только один из `from`/`to`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ManageApiError"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/journal/{id}": {
      "get": {
        "tags": [
          "Journal and history"
        ],
        "summary": "Прочитать одну строку журнала",
        "operationId": "getJournalEntry",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Числовой `id` строки из `GET /api/bot/journal`.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "includeAudit",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Строка журнала",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JournalItem"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/kb": {
      "get": {
        "tags": [
          "Knowledge base"
        ],
        "summary": "Получить список источников базы знаний",
        "description": "Принимает любое из прав доступа — `ask` или `manage`.\n\nПоле верхнего уровня `url` — это URL самого раннего источника, сохранённое для обратной совместимости с интеграциями, появившимися до `sources`; новым интеграциям следует читать `sources`.",
        "operationId": "listKbSources",
        "responses": {
          "200": {
            "description": "Источники",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KbSources"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/api/bot/search": {
      "get": {
        "tags": [
          "Knowledge base"
        ],
        "summary": "Выполнить собственный поиск бота по его базе знаний",
        "description": "Принимает любое из прав доступа — `ask` или `manage`. Полезно, чтобы проверить, действительно ли ответ можно обосновать документами, прежде чем редактировать промпт из-за неверного ответа.\n\nКаждый результат содержит `id` — тот же идентификатор статьи, что использует `GET`/`PATCH /api/bot/articles/{id}`, и единственный способ узнать id существующей статьи через этот API.\n\nТарифицируется за запрос и поэтому лимитам не подчиняется.",
        "operationId": "searchKb",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "skip",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "name": "take",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Найденные статьи",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "DeanonymizeResponse": {
        "type": "object",
        "required": [
          "deanonymized_text"
        ],
        "properties": {
          "deanonymized_text": {
            "type": "string",
            "description": "Текст после обратной замены (восстановлены оригинальные значения с учётом морфологии и форматов)"
          }
        }
      },
      "DeanonymizeRequest": {
        "type": "object",
        "required": [
          "anonymized_text",
          "replacements"
        ],
        "properties": {
          "anonymized_text": {
            "type": "string",
            "description": "Анонимизированный текст, в котором встречаются фейки"
          },
          "replacements": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Replacement"
            },
            "description": "Карта замен — список объектов с fake и original"
          }
        }
      },
      "Replacement": {
        "type": "object",
        "required": [
          "fake",
          "original",
          "entity_type"
        ],
        "properties": {
          "fake": {
            "type": "string",
            "description": "Случайное значение, используемое в анонимизированном тексте"
          },
          "original": {
            "type": "string",
            "description": "Оригинальное значение (как в исходном тексте)"
          },
          "entity_type": {
            "$ref": "#/components/schemas/EntityType"
          },
          "ignored_names": {
            "type": "string",
            "description": "Опциональная строка с именами, которые нужно игнорировать при анонимизации"
          }
        }
      },
      "EntityType": {
        "type": "string",
        "description": "Тип сущности для замены (используется в replacements).",
        "enum": [
          "PERSON",
          "RU_FIRST_NAME",
          "RU_LAST_NAME",
          "RU_PATRONYMIC",
          "PHONE_NUMBER",
          "RUSSIAN_PASSPORT",
          "RU_FOREIGN_PASSPORT",
          "RU_DRIVER_LICENSE",
          "CREDIT_CARD",
          "RU_INN",
          "RUSSIAN_INN",
          "PII_DATE",
          "BIRTH_DATE",
          "PASSPORT_ISSUE_DATE",
          "EMAIL_ADDRESS",
          "RU_SNILS",
          "ADDRESS_BUILDING",
          "ADDRESS_APARTMENT"
        ]
      },
      "AnonymizeResponse": {
        "type": "object",
        "required": [
          "anonymized_text",
          "replacements"
        ],
        "properties": {
          "anonymized_text": {
            "type": "string",
            "description": "Текст с заменённой PII"
          },
          "replacements": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Replacement"
            },
            "description": "Список произведённых замен (поля: fake, original, entity_type)"
          }
        }
      },
      "AnonymizeRequest": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "description": "Исходный текст для анонимизации"
          },
          "existingReplacements": {
            "type": "array",
            "description": "Существующие замены, которые нужно применить/учесть при анонимизации",
            "items": {
              "$ref": "#/components/schemas/Replacement"
            }
          },
          "ignored_names": {
            "type": "string",
            "description": "Опциональная строка с именами, которые нужно игнорировать при анонимизации"
          }
        }
      },
      "KbUrl": {
        "required": [
          "url"
        ],
        "type": "object",
        "properties": {
          "url": {
            "description": "Адрес источника данных",
            "type": "string"
          }
        }
      },
      "AnswerNew": {
        "required": [
          "answer",
          "botId",
          "chatId",
          "type",
          "agentId"
        ],
        "type": "object",
        "properties": {
          "answer": {
            "description": "Ответ на вопрос",
            "type": "string"
          },
          "botId": {
            "description": "Идентификатор бота",
            "type": "string"
          },
          "chatId": {
            "description": "Идентификатор бота",
            "type": "string"
          },
          "msgId": {
            "description": "Идентификатор сообщения, переданный в запросе",
            "type": "string"
          },
          "type": {
            "description": "Тип ответа:\n- `SUCCESS` — успешный ответ\n- `NO_ANSWER` — нет ответа\n- `ERROR` — внутренняя ошибка сервера (например, таймаут)\n- `GREETING` — содержит только приветствие\n- `OPERATOR` — вызов оператора\n- `SKIP` — пропуск сообщения\n- `GRATITUDE` — содержит только благодарность\n\nЕсли `type` равен `OPERATOR`, `ERROR` или `NO_ANSWER` — чат необходимо перевести на оператора.",
            "type": "string",
            "enum": [
              "SUCCESS",
              "NO_ANSWER",
              "ERROR",
              "GREETING",
              "OPERATOR",
              "SKIP",
              "GRATITUDE"
            ]
          },
          "attachments": {
            "description": "Список вложений (URL)",
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "agentId": {
            "description": "Идентификатор агента, который выполнил запрос",
            "type": "number"
          }
        }
      },
      "Answer": {
        "required": [
          "answer",
          "botId",
          "chatId",
          "type",
          "agentId"
        ],
        "type": "object",
        "properties": {
          "answer": {
            "description": "Ответ на вопрос",
            "type": "string"
          },
          "botId": {
            "description": "Идентификатор бота",
            "type": "string"
          },
          "chatId": {
            "description": "Идентификатор бота",
            "type": "string"
          },
          "msgId": {
            "description": "Идентификатор сообщения, переданный в запросе",
            "type": "string"
          },
          "type": {
            "description": "Тип ответа:\n- `SUCCESS` — успешный ответ\n- `NO_ANSWER` — агент не смог сформировать ответ\n- `ERROR` — внутренняя ошибка сервера (например, таймаут)\n- `GREETING` — содержит только приветствие\n- `OPERATOR` — вызов оператора\n- `SKIP` — пропуск сообщения\n- `GRATITUDE` — содержит только благодарность\n\nЕсли `type` равен `OPERATOR`, `ERROR` или `NO_ANSWER` — чат необходимо перевести на оператора.",
            "type": "string",
            "enum": [
              "SUCCESS",
              "NO_ANSWER",
              "ERROR",
              "GREETING",
              "OPERATOR",
              "SKIP",
              "GRATITUDE"
            ]
          },
          "agentId": {
            "description": "Идентификатор агента, который выполнил запрос",
            "type": "number"
          }
        }
      },
      "Error": {
        "required": [
          "error",
          "message"
        ],
        "type": "object",
        "properties": {
          "error": {
            "type": "integer",
            "format": "int32"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "Webhook": {
        "required": [
          "url"
        ],
        "type": "object",
        "properties": {
          "url": {
            "description": "Адрес вебхука",
            "type": "string"
          }
        }
      },
      "SearchResponse": {
        "type": "object",
        "properties": {
          "articles": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer",
                  "description": "Тот же id статьи, который использует `GET`/`PATCH /api/bot/articles/{id}`."
                },
                "title": {
                  "type": "string"
                },
                "content": {
                  "type": "string"
                },
                "link": {
                  "type": "string",
                  "nullable": true
                },
                "indexedAt": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          }
        }
      },
      "KbSources": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "nullable": true,
            "description": "URL самого раннего источника, сохранён для обратной совместимости."
          },
          "sources": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "url": {
                  "type": "string",
                  "nullable": true
                },
                "createdAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "indexedAt": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "JournalItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "chatId": {
            "type": "string",
            "nullable": true
          },
          "type": {
            "$ref": "#/components/schemas/QueryResultType"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "operatorReason": {
            "$ref": "#/components/schemas/OperatorReason"
          },
          "request": {
            "type": "string",
            "nullable": true
          },
          "response": {
            "type": "string",
            "nullable": true
          },
          "explain": {
            "type": "string",
            "nullable": true
          },
          "agentConversationId": {
            "type": "integer",
            "nullable": true
          },
          "mainConversationId": {
            "type": "integer",
            "nullable": true,
            "description": "Диалог, которому принадлежит эта строка, — то же значение, что возвращает `GET /api/bot/conversations/{chatId}/history`."
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            },
            "description": "Присутствует только при `includeAudit=true`."
          }
        }
      },
      "OperatorReason": {
        "type": "string",
        "enum": [
          "REQUEST",
          "RULE",
          "NO_ANSWER",
          "PREPROCESSING"
        ]
      },
      "QueryResultType": {
        "type": "string",
        "enum": [
          "SUCCESS",
          "FALLBACK",
          "NO_ANSWER",
          "ERROR",
          "GREETING",
          "OPERATOR",
          "SKIP",
          "GRATITUDE",
          "RATING_ACCEPTED",
          "NO_OPERATOR_ERROR"
        ]
      },
      "JournalPage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/JournalItem"
            }
          },
          "page": {
            "type": "integer"
          },
          "pageSize": {
            "type": "integer",
            "enum": [
              20
            ]
          },
          "total": {
            "type": "integer"
          },
          "hasMore": {
            "type": "boolean"
          }
        }
      },
      "PreprocessedHistory": {
        "type": "object",
        "description": "Возвращается, когда запрос вообще не дошёл до диалога с агентом — заблокирован фильтром, переведён на оператора или отвечен шаблоном.",
        "properties": {
          "chatId": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "preprocessed"
            ]
          },
          "note": {
            "type": "string"
          },
          "request": {
            "type": "string",
            "nullable": true
          },
          "response": {
            "type": "string",
            "nullable": true
          },
          "explain": {
            "type": "string",
            "nullable": true
          },
          "responseType": {
            "$ref": "#/components/schemas/QueryResultType"
          },
          "logs": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "ConversationHistory": {
        "type": "object",
        "properties": {
          "mainConversationId": {
            "type": "integer"
          },
          "page": {
            "type": "integer"
          },
          "pageSize": {
            "type": "integer",
            "enum": [
              30
            ]
          },
          "hasMore": {
            "type": "boolean"
          },
          "logs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AuditLog"
            },
            "description": "Возвращается только на `page=0` — аудит охватывает весь диалог, а не одну страницу."
          },
          "logsNote": {
            "type": "string",
            "description": "Присутствует вместо `logs` на последующих страницах."
          },
          "items": {
            "type": "array",
            "description": "Каждое сообщение диалога, включая под-диалоги, открытые сценарием. `message` — это обычный текст без служебных полей провайдера-обёртки; внутренние системные сообщения полностью опущены.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer"
                },
                "conversationId": {
                  "type": "integer"
                },
                "isSubAgent": {
                  "type": "boolean"
                },
                "author": {
                  "type": "string",
                  "example": "user"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "AuditLog": {
        "type": "object",
        "description": "Разобранный аудит одного запроса — тот же пошаговый анализ, что показан в панели «Analysis» дашборда.",
        "properties": {
          "requestId": {
            "type": "string",
            "nullable": true
          },
          "request": {
            "type": "string",
            "nullable": true
          },
          "response": {
            "type": "string",
            "nullable": true
          },
          "type": {
            "$ref": "#/components/schemas/QueryResultType"
          },
          "isSubAgent": {
            "type": "boolean"
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        }
      },
      "EditArticleRequest": {
        "type": "object",
        "description": "Требуется хотя бы одно поле. `title` и `content` нужно отправлять вместе.",
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1
          },
          "content": {
            "type": "string",
            "minLength": 1
          },
          "indexed": {
            "type": "boolean"
          }
        }
      },
      "Article": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "title": {
            "type": "string"
          },
          "content": {
            "type": "string"
          },
          "indexed": {
            "type": "boolean",
            "nullable": true
          },
          "editable": {
            "type": "boolean",
            "description": "`false` для статьи, полученной краулингом или загрузкой: у неё можно менять только `indexed`."
          }
        }
      },
      "CreateArticleRequest": {
        "type": "object",
        "required": [
          "title",
          "content"
        ],
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1
          },
          "content": {
            "type": "string",
            "minLength": 1
          },
          "indexed": {
            "type": "boolean",
            "default": true
          }
        }
      },
      "ToolAssignment": {
        "type": "object",
        "properties": {
          "agentId": {
            "type": "integer"
          },
          "agentName": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean",
            "description": "Всегда `true` — агент присутствует здесь только пока инструмент подключён."
          }
        }
      },
      "EditToolRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "parameters": {
            "type": "object",
            "additionalProperties": true
          },
          "actions": {
            "type": "array",
            "minItems": 1,
            "maxItems": 5,
            "items": {
              "$ref": "#/components/schemas/ToolAction"
            },
            "description": "Заменяет весь список."
          }
        }
      },
      "ToolAction": {
        "type": "object",
        "required": [
          "method",
          "url"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "method": {
            "type": "string",
            "enum": [
              "GET",
              "POST",
              "PUT",
              "DELETE",
              "PATCH"
            ]
          },
          "url": {
            "type": "string",
            "description": "Должен быть публичным http(s)-адресом. Имена `{placeholder}`, объявленные в `parameters.properties`, подставляются в момент вызова.",
            "example": "https://api.example.com/orders/{orderId}"
          },
          "headers": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "key",
                "value"
              ],
              "properties": {
                "key": {
                  "type": "string"
                },
                "value": {
                  "type": "string"
                }
              }
            }
          },
          "payload": {
            "type": "string",
            "description": "Шаблон тела запроса, используется только для `POST`/`PUT`/`PATCH`. Поддерживает такую же подстановку `{placeholder}`."
          }
        }
      },
      "ToolDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "parameters": {
            "type": "object",
            "additionalProperties": true,
            "description": "JSON Schema аргументов функции (формат OpenAI function-calling)."
          },
          "actions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ToolAction"
            }
          },
          "assignedTo": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ToolAssignment"
            }
          }
        }
      },
      "ToolResponse": {
        "type": "object",
        "required": [
          "tool"
        ],
        "properties": {
          "tool": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              },
              "description": {
                "type": "string"
              },
              "parameters": {
                "type": "object",
                "additionalProperties": true
              },
              "actions": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ToolAction"
                },
                "description": "Возвращается при создании; отсутствует при обновлении."
              }
            }
          }
        }
      },
      "CreateToolRequest": {
        "type": "object",
        "required": [
          "name",
          "description",
          "parameters",
          "actions"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "parameters": {
            "type": "object",
            "additionalProperties": true,
            "description": "Объект JSON Schema, а не строка.",
            "example": {
              "type": "object",
              "properties": {
                "orderId": {
                  "type": "string"
                }
              },
              "required": [
                "orderId"
              ]
            }
          },
          "actions": {
            "type": "array",
            "minItems": 1,
            "maxItems": 5,
            "items": {
              "$ref": "#/components/schemas/ToolAction"
            }
          },
          "assignedAgentIds": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Id из `GET /api/bot/agents/assignable`. Не указывайте, чтобы создать инструмент без назначения."
          }
        }
      },
      "ToolSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "assignedTo": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ToolAssignment"
            }
          }
        }
      },
      "AssignableAgent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "flowId": {
            "type": "integer"
          },
          "flowName": {
            "type": "string"
          },
          "flowEnabled": {
            "type": "boolean",
            "nullable": true
          }
        }
      },
      "SetAssignmentRequest": {
        "type": "object",
        "required": [
          "enabled"
        ],
        "properties": {
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "DatatableAssignment": {
        "type": "object",
        "properties": {
          "agentId": {
            "type": "integer"
          },
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "EditDatatableRequest": {
        "type": "object",
        "properties": {
          "description": {
            "type": "string"
          },
          "columns": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DatatableColumn"
            },
            "description": "Заменяет схему, но не данные."
          }
        }
      },
      "DatatableColumn": {
        "type": "object",
        "required": [
          "name",
          "type"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Не должно начинаться с `_` — колонки `_id`, `_created_at` и `_updated_at` добавляются автоматически и доступны только для чтения."
          },
          "type": {
            "type": "string",
            "enum": [
              "text",
              "number",
              "boolean",
              "date"
            ]
          },
          "description": {
            "type": "string"
          },
          "required": {
            "type": "boolean"
          }
        }
      },
      "QueryDatatableResponse": {
        "type": "object",
        "required": [
          "columns",
          "rows",
          "truncated"
        ],
        "properties": {
          "columns": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "rows": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "truncated": {
            "type": "boolean",
            "description": "`true`, если строк подошло больше, чем вернулось, — сузьте запрос."
          }
        }
      },
      "QueryDatatableRequest": {
        "type": "object",
        "required": [
          "tables",
          "sql"
        ],
        "properties": {
          "tables": {
            "type": "array",
            "minItems": 1,
            "items": {
              "type": "string"
            },
            "description": "Имена таблиц, которые есть у этого бота (см. `GET /api/bot/datatables`). `sql` может ссылаться на любую из них."
          },
          "sql": {
            "type": "string",
            "minLength": 1
          }
        },
        "example": {
          "tables": [
            "orders"
          ],
          "sql": "SELECT status, count(*) FROM orders GROUP BY status"
        }
      },
      "DatatableResponse": {
        "type": "object",
        "required": [
          "table"
        ],
        "properties": {
          "table": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DatatableSummary"
              },
              {
                "type": "object",
                "properties": {
                  "botId": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "dataSize": {
                    "type": "integer"
                  },
                  "version": {
                    "type": "integer"
                  }
                }
              }
            ]
          }
        }
      },
      "DatatableSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "columns": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DatatableColumn"
            }
          },
          "rowCount": {
            "type": "integer"
          }
        }
      },
      "CreateDatatableRequest": {
        "type": "object",
        "required": [
          "name",
          "description",
          "columns"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "columns": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/DatatableColumn"
            }
          },
          "assignedAgentIds": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Id из `GET /api/bot/agents/assignable`. Не указывайте, чтобы использовать поведение по умолчанию (сам агент и каждый включённый сценарий активного агента бота); id, недоступный для назначения, отклоняется, а не молча отбрасывается."
          }
        }
      },
      "EditJobRequest": {
        "description": "Любое подмножество полей задачи; всё, что не передано, сохраняет уже записанное значение.",
        "allOf": [
          {
            "$ref": "#/components/schemas/JobFields"
          }
        ]
      },
      "JobFields": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200
          },
          "instructions": {
            "type": "string",
            "maxLength": 4000,
            "description": "Указания для агента, а не готовое сообщение — текст, видимый клиенту, агент пишет сам."
          },
          "inactivityMinutes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10080
          },
          "conversationFilter": {
            "$ref": "#/components/schemas/ConversationFilter"
          },
          "maxRunsPerConversation": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10
          },
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "ConversationFilter": {
        "type": "string",
        "enum": [
          "ALL",
          "WITH_OPERATOR",
          "NO_CLIENT_REPLY"
        ],
        "description": "`ALL` — любой диалог, активный с момента включения задачи; `WITH_OPERATOR` — только диалоги, переданные оператору; `NO_CLIENT_REPLY` — диалоги без передачи оператору и без слов благодарности, то есть клиент просто перестал отвечать."
      },
      "JobResponse": {
        "type": "object",
        "required": [
          "job"
        ],
        "properties": {
          "job": {
            "$ref": "#/components/schemas/JobFields"
          }
        }
      },
      "CreateJobRequest": {
        "type": "object",
        "required": [
          "name",
          "instructions",
          "inactivityMinutes"
        ],
        "allOf": [
          {
            "$ref": "#/components/schemas/JobFields"
          }
        ]
      },
      "Job": {
        "allOf": [
          {
            "$ref": "#/components/schemas/JobFields"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "enabledAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Подбираются только диалоги, затихшие после этого момента."
              },
              "runs": {
                "type": "object",
                "properties": {
                  "completed": {
                    "type": "integer"
                  },
                  "skipped": {
                    "type": "integer",
                    "description": "Пропущенный диалог пропускается для этой задачи навсегда."
                  },
                  "failed": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        ]
      },
      "CloneFlowResponse": {
        "type": "object",
        "required": [
          "applied",
          "flow"
        ],
        "properties": {
          "applied": {
            "type": "boolean"
          },
          "flow": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Отсутствует в превью `dryRun`."
              },
              "name": {
                "type": "string"
              },
              "enabled": {
                "type": "boolean",
                "enum": [
                  false
                ]
              }
            }
          }
        }
      },
      "CloneFlowRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "dryRun": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "PatchAgentResponse": {
        "type": "object",
        "required": [
          "applied",
          "changes"
        ],
        "properties": {
          "applied": {
            "type": "boolean"
          },
          "changes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AgentDiff"
            }
          }
        }
      },
      "AgentDiff": {
        "type": "object",
        "required": [
          "field"
        ],
        "properties": {
          "field": {
            "type": "string",
            "description": "`enabled`, `prompt` или `options.<key>`.",
            "example": "options.workAsSkill"
          },
          "oldValue": {},
          "newValue": {}
        }
      },
      "PatchAgentRequest": {
        "type": "object",
        "description": "Любое подмножество полей; ожидается хотя бы одно значимое изменение.",
        "properties": {
          "enabled": {
            "type": "boolean"
          },
          "prompt": {
            "type": "string",
            "maxLength": 20000
          },
          "options": {
            "type": "object",
            "additionalProperties": {
              "oneOf": [
                {
                  "type": "string"
                },
                {
                  "type": "number"
                },
                {
                  "type": "boolean"
                },
                {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              ]
            }
          },
          "dryRun": {
            "type": "boolean",
            "default": false
          }
        },
        "example": {
          "dryRun": true,
          "prompt": "New instruction text",
          "options": {
            "workAsSkill": false
          }
        }
      },
      "ManageApiError": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          }
        },
        "example": {
          "error": "missing scope: \"manage\" is required"
        }
      },
      "CreateScenarioResponse": {
        "type": "object",
        "required": [
          "applied",
          "scenario"
        ],
        "properties": {
          "applied": {
            "type": "boolean"
          },
          "scenario": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer",
                "description": "Отсутствует в превью `dryRun`."
              },
              "name": {
                "type": "string",
                "example": "scenario_refunds"
              },
              "type": {
                "$ref": "#/components/schemas/AgentType"
              },
              "enabled": {
                "type": "boolean"
              },
              "prompt": {
                "type": "string"
              },
              "options": {
                "type": "object",
                "additionalProperties": true
              }
            }
          }
        }
      },
      "AgentType": {
        "type": "string",
        "enum": [
          "default",
          "spam",
          "operator",
          "translator",
          "scenario",
          "editor",
          "summary"
        ]
      },
      "CreateScenarioRequest": {
        "type": "object",
        "required": [
          "name",
          "description",
          "prompt"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 64,
            "description": "Латинские буквы, цифры и подчёркивание, начиная с буквы. Не указывайте префикс `scenario_` — он добавляется автоматически — и не используйте имя встроенного агента."
          },
          "description": {
            "type": "string",
            "minLength": 1,
            "description": "Одно предложение о том, когда передавать диалог этому сценарию. Читается логикой маршрутизации агента по умолчанию, клиенту никогда не показывается."
          },
          "prompt": {
            "type": "string",
            "minLength": 1
          },
          "dryRun": {
            "type": "boolean",
            "default": false
          }
        }
      },
      "Agent": {
        "type": "object",
        "required": [
          "id",
          "name",
          "type",
          "enabled",
          "prompt",
          "options"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "`0` означает, что у служебного агента ещё нет строки в БД — она создаётся при первой записи."
          },
          "name": {
            "type": "string",
            "example": "scenario_refunds"
          },
          "type": {
            "$ref": "#/components/schemas/AgentType"
          },
          "enabled": {
            "type": "boolean",
            "nullable": true
          },
          "prompt": {
            "type": "string",
            "nullable": true
          },
          "options": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "Flow": {
        "type": "object",
        "required": [
          "id",
          "name",
          "enabled"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean",
            "nullable": true
          }
        }
      },
      "SettingSpec": {
        "type": "object",
        "required": [
          "path",
          "kind",
          "description"
        ],
        "properties": {
          "path": {
            "type": "string",
            "example": "templates.greeting.initial"
          },
          "kind": {
            "type": "string",
            "enum": [
              "text",
              "boolean",
              "number",
              "patterns"
            ],
            "description": "Что передавать: `text`/`boolean`/`number` кладутся в `value` (текст ограничен 2000 символами, `answerDelayMs` — диапазоном 0..60000), `patterns` кладётся в `values` как массив строк."
          },
          "description": {
            "type": "string"
          },
          "effect": {
            "type": "object",
            "description": "Присутствует у булевых настроек, чьё имя читается как отрицание. Является авторитетным источником того, что делает каждое состояние.",
            "properties": {
              "onTrue": {
                "type": "string"
              },
              "onFalse": {
                "type": "string"
              }
            }
          }
        }
      },
      "PatchConfigResponse": {
        "type": "object",
        "required": [
          "applied",
          "changes"
        ],
        "properties": {
          "applied": {
            "type": "boolean",
            "description": "`false` для `dryRun`, а также если ни одно из запрошенных изменений фактически ничего не поменяло."
          },
          "changes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConfigDiff"
            }
          }
        }
      },
      "ConfigDiff": {
        "type": "object",
        "required": [
          "path"
        ],
        "properties": {
          "path": {
            "type": "string"
          },
          "oldValue": {},
          "newValue": {}
        }
      },
      "PatchConfigRequest": {
        "type": "object",
        "required": [
          "changes"
        ],
        "properties": {
          "changes": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/ConfigChange"
            }
          },
          "dryRun": {
            "type": "boolean",
            "default": false
          }
        },
        "example": {
          "dryRun": true,
          "changes": [
            {
              "path": "templates.greeting.initial",
              "value": "Здравствуйте!"
            },
            {
              "path": "filters.ignore.patterns",
              "values": [
                "спам",
                "реклама"
              ]
            }
          ]
        }
      },
      "ConfigChange": {
        "type": "object",
        "required": [
          "path"
        ],
        "properties": {
          "path": {
            "type": "string",
            "description": "Должен быть одним из путей из `GET /api/bot/config/schema`."
          },
          "value": {
            "description": "Для настроек типа `text`, `boolean` и `number`.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "number"
              },
              {
                "type": "boolean"
              }
            ]
          },
          "values": {
            "type": "array",
            "nullable": true,
            "items": {
              "type": "string"
            },
            "description": "Для настроек типа `patterns`."
          }
        }
      },
      "BotConfigView": {
        "type": "object",
        "description": "Разрешённый к чтению срез конфигурации бота. `templates` и `filters` — вложенные объекты, листья которых адресуются по значениям `path` через точку из `GET /api/bot/config/schema`.",
        "properties": {
          "templates": {
            "type": "object",
            "additionalProperties": true
          },
          "filters": {
            "type": "object",
            "additionalProperties": true
          },
          "disableMediaMiddleware": {
            "type": "boolean"
          },
          "disabledOperatorRegexp": {
            "type": "boolean"
          },
          "answerDelayMs": {
            "type": "integer",
            "nullable": true
          },
          "workingHours": {
            "description": "Рабочие часы оператора, либо null."
          },
          "botWorkingHours": {
            "description": "Собственные рабочие часы бота — отдельное от `workingHours` расписание."
          },
          "language": {
            "type": "string",
            "nullable": true,
            "example": "ru"
          },
          "glossary": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "term": {
                  "type": "string"
                },
                "definition": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "parameters": {
      "ArticleId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "integer"
        }
      },
      "ToolId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "integer"
        }
      },
      "AgentId": {
        "name": "agentId",
        "in": "path",
        "required": true,
        "description": "Числовой id агента из `GET /api/bot/agents/assignable`.",
        "schema": {
          "type": "integer"
        }
      },
      "DatatableId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "UUID таблицы или её `name` — подходит и то, и другое.",
        "schema": {
          "type": "string"
        },
        "example": "orders"
      },
      "FlowId": {
        "name": "flowId",
        "in": "path",
        "required": true,
        "description": "Id агента из `GET /api/bot/flows`.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Валидация не пройдена; ничего не записано",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ManageApiError"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Превышен лимит запросов для этого бота",
        "headers": {
          "Retry-After": {
            "description": "Сколько секунд осталось до конца текущего окна.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ManageApiError"
            }
          }
        }
      },
      "NotFound": {
        "description": "Бот, агент или адресуемый ресурс не существует для этого ключа",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ManageApiError"
            }
          }
        }
      },
      "Forbidden": {
        "description": "У ключа нет права доступа, необходимого для этого метода",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ManageApiError"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "API-ключ отсутствует, неизвестен или отключён",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ManageApiError"
            }
          }
        }
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "Authorization",
        "description": "API-ключ интеграции со страницы бота Settings → API Keys, передаётся как есть, без префикса `Bearer`."
      }
    }
  }
}
