АПИ AI-чата: диалоги, сообщения и файлы по ключу СУДиДЕЛО
Методы AiChat позволяют встроить AI-чат СУДиДЕЛО во внешнее приложение: создать отдельный диалог, отправлять сообщения, получать полный ответ или поток событий и скачивать сформированные файлы. Для подключения используется существующий пользовательский ключ СУДиДЕЛО в заголовке X-API-Key.
Что нужно для подключения
Базовый адрес — https://api-sudodelo.torkndgov.ru. Актуальную доступность проверяйте в Swagger PublicApi, группа AiChat. Как получить обычный пользовательский ключ, описано в общей справке по открытому API.
- В каждом запросе передавайте
X-API-Key: <ваш пользовательский ключ>. Используйте HTTPS. Ключ не должен попадать в URL, скриншоты, журналы или общедоступный код. - Сотрудник должен быть активным, принадлежать действующей организации и иметь право AI-чат (
menu.aichat). Проверки выполняются заново при каждом запросе. Отозванный или заменённый ключ больше не предоставляет доступ. - Внешнему приложению не нужны логин, пароль, cookie WebApp, launch-token, Bearer Python-чата или общий серверный секрет. Их не следует добавлять к запросам этих методов.
- Передавайте ровно один X-API-Key. Несколько значений, смешение с query-параметром
apiKeyлибо заголовкомAuthorizationотклоняются. Cookie не используются как credential. AccountIdиOrganizationIdклиент не выбирает. Сервер определяет сотрудника по проверенному ключу, а доступ к диалогу проверяет по сотруднику, а не только по организации.
Последовательность работы
- Создать диалог с одним X-API-Key и сохранить
ИдентификаторДиалога. - Отправлять этот идентификатор и тот же действующий ключ при запросе состояния, сообщениях JSON/SSE и скачивании файлов.
- Для продолжения переписки использовать прежний идентификатор. Для новой независимой переписки явно создать новый диалог.
ИдентификаторДиалога — несекретная строка из 32 строчных шестнадцатеричных символов. Она не является токеном доступа: без действующего ключа владельца диалог недоступен. У одного сотрудника может быть несколько диалогов, каждый со своей историей и файлами.
1. Создать диалог
POST /api/AiChat/sessions
X-API-Key: <пользовательский apiKey>
Тело запроса отсутствует. Не отправляйте даже пустой JSON {}, launch-token или идентификаторы сотрудника. Сервер сам создаёт и обменивает одноразовый токен, а секрет доступа к Python-чату хранит у себя.
curl --fail-with-body --request POST \
"https://api-sudodelo.torkndgov.ru/api/AiChat/sessions" \
--header "X-API-Key: $SUDODELO_API_KEY"
Ответ 201 Created, Cache-Control: no-store:
{
"ИдентификаторДиалога": "0123456789abcdef0123456789abcdef",
"ВремяНеактивностиВСекундах": 28800
}
Идентификатор и срок в примере условные. Используйте значения фактического ответа; срок приходит от чат-сервиса. В ответе нет apiKey, launch-token, Bearer и create-secret.
ИдентификаторДиалога— Несекретный идентификатор независимого диалога. Сам по себе не предоставляет доступ: требуется X-API-Key владельца.ВремяНеактивностиВСекундах— Скользящий срок сессии в секундах; авторизованные запросы продлевают его.
2. Получить сведения о диалоге
GET /api/AiChat/session?ИдентификаторДиалога=<идентификатор>
X-API-Key: <пользовательский apiKey>
Обязательный query-параметр ИдентификаторДиалога берётся из создания диалога. Пустой или неверно оформленный параметр отклоняется; неизвестный или чужой корректный идентификатор возвращает 404.
curl --fail-with-body --get \
"https://api-sudodelo.torkndgov.ru/api/AiChat/session" \
--header "X-API-Key: $SUDODELO_API_KEY" \
--data-urlencode "ИдентификаторДиалога=$DIALOG_ID"
Ответ 200 OK:
{
"Авторизован": true,
"Модель": "<модель из конфигурации>",
"НазваниеМодели": "<название для отображения>",
"ВремяНеактивностиВСекундах": 28800
}
Авторизован— Признак действующей авторизации диалога.Модель— Идентификатор модели, настроенной на сервере чата.НазваниеМодели— Название модели для отображения.ВремяНеактивностиВСекундах— Скользящий срок сессии в секундах; авторизованные запросы продлевают его.
Авторизованные обращения поддерживают скользящий срок неактивности. Фиксированная дата истечения не возвращается. Регулярный запрос состояния также является обращением к сессии; он не восстанавливает уже истёкший диалог.
3. Отправить сообщение: JSON или SSE
POST /api/AiChat/chat
X-API-Key: <пользовательский apiKey>
Content-Type: application/json
{
"ИдентификаторДиалога": "0123456789abcdef0123456789abcdef",
"Сообщение": "Какие события есть в моём календаре?",
"ПотоковыйОтвет": false
}
ИдентификаторДиалога— Несекретный идентификатор диалога из ответа создания; доступ требует действующего ключа владельца.Сообщение— Текст от 1 до 16000 символов; внешние пробелы удаляются, пустой текст запрещён.ПотоковыйОтвет— Истина — поток SSE, ложь (по умолчанию) — полный JSON-ответ. Только JSON boolean.
Укажите сообщение от 1 до 16000 символов. Внешние пробелы удаляются; пустой после удаления текст запрещён. ПотоковыйОтвет — настоящий JSON boolean (true/false), по умолчанию false. Лишние поля запрещены: клиент не присылает историю, системный промпт, модель, инструменты или AccountId.
При ПотоковыйОтвет: false возвращается 200 application/json:
{
"Текст": "В календаре событий нет.",
"Файлы": [],
"События": [
{
"Тип": "Текст",
"Текст": "В календаре событий нет."
},
{
"Тип": "Завершение"
}
]
}
Текст— Проверенный текст ответа ассистента.Файлы— Файлы, полученные в том же ходе диалога.События— Упорядоченные события одного запуска; последнее событие успешного ответа — Завершение.
Содержимое и число событий зависят от запроса. Текст — проверенный ответ того же запуска чата, а не дополнительное резюме обёртки. Для следующего сообщения используйте тот же идентификатор: так сохраняется история диалога.
Как читать поток событий
При ПотоковыйОтвет: true ответ имеет тип text/event-stream, заголовки Cache-Control: no-store и X-Accel-Buffering: no. Каждая запись начинается с data: , затем следует JSON и пустая строка. Например:
data: {"Тип":"Текст","Текст":"В календаре событий нет."}
data: {"Тип":"Завершение"}
СлужебнаяИнформация: поляКоличествоИнструментов,СессияИнструментов.ВызовИнструмента:ИмяИнструмента,АргументыИнструмента. Аргументы — JSON-строка: исходные имена сохраняются внутри значения, а не становятся полями внешнего контракта.РезультатИнструмента:ИмяИнструмента,ПредварительныйРезультат.Файл: вложенный объектФайлс метаданными скачивания.Текст: полеТекстответа ассистента.Завершение: подтверждение успешного окончания хода.Ошибка: полеКод, напримерturn_failed. После ошибки событие «Завершение» не выдаётся.
Успех SSE подтверждает только событие «Завершение». После начала потока HTTP-код уже не меняется: сбой сообщается событием «Ошибка». Закрытие соединения без «Завершение» означает неизвестный исход. Отказ grounding, оформленный самим чатом как ответ ассистента, может быть обычным успешным ответом; HTTP-успех не определяют по формулировке текста.
4. Скачать файл диалога
GET /api/AiChat/files/{ИдентификаторФайла}?ИдентификаторДиалога=<идентификатор>
X-API-Key: <пользовательский apiKey>
Параметр маршрута ИдентификаторФайла берётся из массива Файлы либо события Файл. Допустимы латинские буквы, цифры, дефис и подчёркивание, длина — до 256 символов. Обязательный query-параметр ИдентификаторДиалога указывает диалог, в котором сформирован файл.
ИдентификаторФайла— Идентификатор файла, доступного только владельцу диалога.Имя— Исходное имя файла.ТипСодержимого— Тип содержимого файла (MIME).РазмерВБайтах— Размер файла в байтах.АдресСкачивания— Локальный путь через обёртку с идентификатором диалога; требует X-API-Key владельца.Вид— Категория файла из сервиса чата.
АдресСкачивания — путь PublicApi с несекретным идентификатором диалога в query; в нём нет ключа или Bearer. Соединяйте такой путь с origin PublicApi. Даже готовую ссылку нужно открывать HTTP-запросом с X-API-Key; обычный переход браузера заголовок не добавляет.
curl --fail-with-body --get \
"https://api-sudodelo.torkndgov.ru/api/AiChat/files/$FILE_ID" \
--header "X-API-Key: $SUDODELO_API_KEY" \
--data-urlencode "ИдентификаторДиалога=$DIALOG_ID" \
--output report.xlsx
Ответ передаётся потоком с исходными Content-Type, Content-Disposition и именем файла, включая RFC 5987. Изображения могут выдаваться inline, другие файлы — attachment. Неизвестный файл или файл другого диалога возвращает 404, в том числе между двумя диалогами одного сотрудника. Собственный истёкший файл возвращает 410 file_expired, пока запись различима в хранилище; после очистки возможен 404. Скачивание не продлевает срок хранения файла.
Пример внешнего клиента: весь сценарий
Bash, curl и jq. Перед запуском задайте SUDODELO_API_KEY в окружении, не сохраняйте его в скрипте и не включайте set -x. Пример сначала получает JSON-ответ и файлы, затем отправляет SSE-сообщение в тот же диалог. Первый вопрос можно задать через SUDODELO_CHAT_MESSAGE; файлы будут скачаны, если чат их сформировал.
set -euo pipefail
: "${SUDODELO_API_KEY:?Задайте API-ключ в окружении}"
BASE="https://api-sudodelo.torkndgov.ru"
MESSAGE="${SUDODELO_CHAT_MESSAGE:-Привет!}"
created=$(curl --fail-with-body --silent --show-error \
--request POST "$BASE/api/AiChat/sessions" \
--header "X-API-Key: $SUDODELO_API_KEY")
DIALOG_ID=$(printf '%s' "$created" | jq -er '."ИдентификаторДиалога"')
curl --fail-with-body --silent --show-error --get \
"$BASE/api/AiChat/session" \
--header "X-API-Key: $SUDODELO_API_KEY" \
--data-urlencode "ИдентификаторДиалога=$DIALOG_ID" >/dev/null
reply=$(jq -n --arg id "$DIALOG_ID" --arg message "$MESSAGE" \
'{"ИдентификаторДиалога":$id,"Сообщение":$message,"ПотоковыйОтвет":false}' | \
curl --fail-with-body --silent --show-error --max-time 950 \
"$BASE/api/AiChat/chat" \
--header "X-API-Key: $SUDODELO_API_KEY" \
--header "Content-Type: application/json" --data-binary @-)
printf '%s' "$reply" | jq -er '."События"[-1]."Тип" == "Завершение"' >/dev/null
printf '%s' "$reply" | jq -r '."Текст"'
# Идентификатор файла берём только из ответа. Скачиваем поток в отдельный файл.
while IFS= read -r FILE_ID; do
[[ "$FILE_ID" =~ ^[A-Za-z0-9_-]{1,256}$ ]] || exit 1
curl --fail-with-body --silent --show-error --get \
"$BASE/api/AiChat/files/$FILE_ID" \
--header "X-API-Key: $SUDODELO_API_KEY" \
--data-urlencode "ИдентификаторДиалога=$DIALOG_ID" \
--output "download-$FILE_ID.bin"
done < <(printf '%s' "$reply" | jq -r '."Файлы"[]."ИдентификаторФайла"')
# Читайте события до «Завершение»; один HTTP 200 не подтверждает успех SSE.
jq -n --arg id "$DIALOG_ID" \
'{"ИдентификаторДиалога":$id,"Сообщение":"Повтори мой предыдущий вопрос.","ПотоковыйОтвет":true}' | \
curl --fail-with-body --silent --show-error --no-buffer --max-time 950 \
"$BASE/api/AiChat/chat" \
--header "X-API-Key: $SUDODELO_API_KEY" \
--header "Content-Type: application/json" --data-binary @-
Пример не повторяет POST автоматически. Для прикладного клиента реализуйте разбор SSE, проверку терминального события и обработку ошибок по следующему разделу. Не записывайте ответы с персональными данными в общедоступные журналы.
Ошибки и повторные запросы
{
"Код": "session_expired",
"Сообщение": "Сессия диалога истекла. Создайте новый диалог явно."
}
- 401 invalid_api_key — ключ отсутствует, неоднозначен, неверен, отозван; сотрудник неактивен или организация недоступна.
- 403 permission_denied — проверенному сотруднику не предоставлено право menu.aichat.
- 404 dialogue_not_found — диалог отсутствует или принадлежит другому сотруднику.
- 410 session_expired — срок диалога истёк либо Python-сессия больше не действует. Создайте новый диалог явным действием клиента.
- 404 not_found, 410 file_expired — файл неизвестен/чужой либо истёк.
- 422 validation_error — неверные параметры, лишнее поле, неподходящий тип или тело создания диалога.
- 502 invalid_upstream_response — некорректный ответ чат-сервиса; 502 turn_failed — ход не завершён успешно.
- 503 upstream_unavailable — backend недоступен или интеграция не настроена; 503 dialogue_capacity — достигнут серверный предел записей диалогов.
- 504 upstream_timeout — истекло время ожидания backend.
Все ответы с данными и ошибками содержат Cache-Control: no-store. Ошибки не возвращают секреты и внутренние исключения. Не повторяйте автоматически POST создания диалога и сообщения: одноразовый токен мог уже быть погашен, а MCP-инструменты могли выполнить изменение CRM до сетевого обрыва. Отмена запроса останавливает дальнейшее чтение, но уже выполненные действия не откатываются. Тайм-аут клиента и reverse proxy должен учитывать несколько раундов работы модели и инструментов; стандартный серверный тайм-аут хода — 900 секунд.
История, срок жизни и перезапуски
Один идентификатор связан с одним диалогом и владельцем. История не объединяется автоматически с открытой web-вкладкой или другими диалогами сотрудника. Сервер проверяет владельца до срока и обращения к Python API.
Соответствие идентификатора и внутреннего токена хранится в памяти одного процесса PublicApi. После его перезапуска диалог недоступен, клиент должен явно создать новый. После перезапуска Python-чата сохранённая запись PublicApi не восстанавливает утраченную сессию и возвращает контролируемое истечение. Несколько экземпляров PublicApi требуют привязки диалога к одному экземпляру либо общего защищённого хранилища; архитектуру нескольких Python workers также нужно согласовать отдельно.
Методы не добавляют постоянный список диалогов, удаление диалога, загрузку вложений пользователя или возобновление оборванного SSE. Существующий запуск AI-чата из WebApp сохраняется. Подключение произвольного браузерного frontend с другого origin требует отдельно согласованной CORS-политики.