Vortholm — документация
Личный ключ

Чаты

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

Личный ключ действует от имени сотрудника, который его создал. Внешняя система по этому ключу создаёт чаты сотрудника, читает и отправляет в них сообщения, останавливает ход и удаляет чат. Здесь — шесть запросов, которые ключ открывает. Как получить ключ, описано на странице «Личный ключ».

Все запросы идут на адрес портала, по которому вы работаете в браузере. В примерах он обозначен как https://portal.example.com. Ключ передаётся в заголовке Authorization:

Authorization: Bearer sk_forge_...

Заголовок обязателен в каждом запросе.

Типичный сценарий

Бот заводит чат по типу задачи, задаёт вопрос и дожидается ответа:

  1. Создайте чат запросом POST /api/chat-sessions, передав taskTypeId. Портал вернёт id нового чата.
  2. Отправьте сообщение запросом POST /api/chat-sessions/<id>/messages с текстом в text и своим clientMessageId.
  3. Держите соединение с потоком открытым, пока портал не завершит ход: поток закроется, когда ответ готов. Подробности — в разделе «Ожидание ответа».
  4. Прочитайте ответ. Это последнее сообщение с role: "assistant" в data.messages.
  5. Если ход пошёл не так, остановите его запросом POST /api/chat-sessions/<id>/stop.
  6. Когда чат больше не нужен, удалите его запросом DELETE /api/chat-sessions/<id>.

Ожидание ответа

Запрос POST /api/chat-sessions/<id>/messages открывает поток text/event-stream. Поток сам говорит, когда ответ готов: он закрывается после события message_complete (ход завершился), queued (чат был занят, ход выполнится позже) или error (ход завершился ошибкой). Разбирать другие события не нужно.

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

Когда поток закрылся после message_complete, прочитайте ответ запросом GET /api/chat-sessions/<id>: это последнее сообщение с role: "assistant" в data.messages. Если поток закрылся после queued, опрашивайте GET /api/chat-sessions/<id> с интервалом около трёх секунд, пока последнее сообщение не станет ответом ассистента с финальным статусом.

Частичный текст ответа виден только в потоке (события text_delta); в ответе GET /:id сообщение ассистента пустое, пока ход не завершён. Если хотите видеть ответ по мере написания, читайте поток и держите его открытым.

Если ход остановили, последнее сообщение ассистента будет со статусом stopped. Если он завершился ошибкой, со статусом failed и кодом в errorCode.

Лимиты

На каждый запрос по ключу действуют два счётчика. Первый общий для всех шести запросов, 300 в минуту на пользователя. Второй отдельный для каждого запроса, считается по адресу и общий с браузером, который ходит в портал с того же адреса.

ЗапросЛимит по адресу
POST /api/chat-sessions20 в час
GET /api/chat-sessions600 в час
POST /api/chat-sessions/<id>/messages60 в час

Остальные три запроса (GET /:id, POST /:id/stop, DELETE /:id) ограничены только общим счётчиком 300 в минуту на пользователя.

Превышение отвечает кодом 429:

{ "error": { "code": "RATE_LIMITED", "message": "Слишком много попыток. Повторите через 1 минут." } }

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

Создать чат

Создаёт чат по типу задачи. Чат можно привязать к конкретной задаче, тогда ассистент отвечает с учётом её файлов и результата.

POST /api/chat-sessions

Поля запроса

Тело запроса — JSON.

ПолеТипОбязательноЗначениеДопустимые значения
taskTypeIdстрока (uuid)даТип задачи, по которому ведётся чатuuid типа задачи
taskIdстрока (uuid)нетЗадача, о которой идёт чатuuid задачи или null
sourceFileIdстрока (uuid)нетФайл задачи, с которого начался чатuuid файла или null
userFolderIdsмассив строк (uuid)нетПапки личной библиотеки, приложенные к чатудо 10, без повторов; по умолчанию пусто

Пример

curl -X POST "https://portal.example.com/api/chat-sessions" \
  -H "Authorization: Bearer sk_forge_..." \
  -H "Content-Type: application/json" \
  -d '{"taskTypeId": "7f2c9a1e-4b3d-4e5f-8a6b-1c2d3e4f5a6b"}'

Ответ

Код 201.

{
  "data": {
    "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "taskTypeId": "7f2c9a1e-4b3d-4e5f-8a6b-1c2d3e4f5a6b",
    "taskId": null,
    "title": null,
    "status": "active",
    "visibility": "private",
    "messageCount": 0,
    "lastMessageAt": null,
    "createdAt": "2026-09-28T09:15:00.000Z",
    "updatedAt": "2026-09-28T09:15:00.000Z"
  }
}

Поля ответа

ПолеЗначение
idИдентификатор чата. Подставляется во все остальные запросы
statusСостояние чата. У нового — active
titleНазвание чата. У нового — null, появится после первого сообщения
messageCountЧисло сообщений. У нового — 0
lastMessageAtВремя последнего сообщения. У нового — null

Ошибки

КодКогда
400taskTypeId или taskId не uuid; либо у типа задачи не настроен чат; либо чат настроен на выведенную из обращения конфигурацию. Текст ошибки назовёт причину; выберите другой тип задачи или обратитесь к администратору
404Типа задачи нет, он скрыт или доступен не всем, либо задача не найдена
403Чат у типа задачи отключён, тип задачи системный, чат настроен на недоступную для типа конфигурацию, или чат открывается только после выполнения задачи
429Превышен лимит 20 созданий в час с одного адреса или 300 запросов в минуту на пользователя

Список чатов

Отдаёт список чатов. По умолчанию — чаты владельца ключа, которые он ещё не удалил.

GET /api/chat-sessions

Параметры запроса

Передаются в строке запроса.

ПараметрТипЗначениеДопустимые значения
scopeстрокаЧей список: свои, общие со мной или открытыеmy (по умолчанию), shared-with-me, shared
statusстрокаОтбор по состоянию чатаactive, closed, archived
taskTypeIdстрока (uuid)Только чаты этого типа задачиuuid типа задачи
taskIdстрока (uuid)Только чаты этой задачиuuid задачи
taskQueryстрокаПодстрока в названии задачи или типа задачи1–200 символов
qстрокаПолнотекстовый поиск по чатам и сообщениям владельца2–200 символов
cursorстрока (uuid)Продолжить список с этого чатаid чата из meta.nextCursor
limitчислоРазмер страницы1–50, по умолчанию 20

Пример

curl "https://portal.example.com/api/chat-sessions?limit=20" \
  -H "Authorization: Bearer sk_forge_..."

Ответ

Код 200.

{
  "data": [
    {
      "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
      "title": "Сколько продаж за сентябрь",
      "status": "active",
      "messageCount": 2,
      "lastMessageAt": "2026-09-28T09:16:04.000Z",
      "createdAt": "2026-09-28T09:15:00.000Z",
      "taskType": {
        "id": "7f2c9a1e-4b3d-4e5f-8a6b-1c2d3e4f5a6b",
        "slug": "sales-summary",
        "name": "Сводка по продажам",
        "icon": "clipboard-list",
        "browserControlEnabled": false
      },
      "task": {
        "id": "3d4e5f6a-0000-0000-0000-000000000001",
        "title": "Продажи за сентябрь 2026",
        "status": "done"
      },
      "browserControlEnabled": false
    }
  ],
  "meta": { "nextCursor": null }
}

Поля ответа

ПолеЗначение
data[].idИдентификатор чата
data[].titleНазвание чата
data[].statusСостояние чата
data[].messageCountЧисло сообщений
data[].lastMessageAtВремя последнего сообщения
data[].taskType.nameНазвание типа задачи
data[].task.titleНазвание связанной задачи, если чат привязан к задаче
meta.nextCursorid последнего чата страницы. Передайте его в cursor, чтобы получить следующую страницу. null, если страниц больше нет

С параметром q ответ другой: список совпадений с фрагментом текста.

{
  "data": [
    {
      "sessionId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
      "source": "message",
      "snippet": "…за сентябрь 2026 было <em>148 продаж</em>…",
      "sortTs": "2026-09-28T09:16:04.000Z"
    }
  ]
}

source — откуда совпадение, meta или message. snippet — фрагмент текста вокруг совпадения.

Ошибки

КодКогда
400Неверный cursor, taskTypeId или taskId (не uuid), limit больше 50, q короче двух символов
429Превышен лимит 600 запросов в час с одного адреса или 300 запросов в минуту на пользователя

Чат и его сообщения

Отдаёт чат и его сообщения. Это запрос, по которому клиент дожидается ответа.

GET /api/chat-sessions/<id>

Параметры запроса

Передаются в строке запроса.

ПараметрТипЗначениеДопустимые значения
idстрока (uuid)Идентификатор чатаuuid чата
beforeстрока (uuid)Показать сообщения раньше этогоid сообщения
limitчислоЧисло сообщенийпо умолчанию 50, потолок 100

Пример

curl "https://portal.example.com/api/chat-sessions/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" \
  -H "Authorization: Bearer sk_forge_..."

Ответ

Код 200.

{
  "data": {
    "id": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d",
    "title": "Сколько продаж за сентябрь",
    "status": "active",
    "canWrite": true,
    "isStreaming": false,
    "messages": [
      {
        "id": "1b2c3d4e-5f6a-4b7c-8d9e-0f1a2b3c4d5e",
        "role": "user",
        "content": "Сколько продаж за сентябрь и сколько из них закрыто?",
        "status": "completed",
        "isError": false,
        "authorName": "Анна Смирнова",
        "createdAt": "2026-09-28T09:16:00.000Z"
      },
      {
        "id": "2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f",
        "role": "assistant",
        "content": "За сентябрь 2026 было 148 продаж, 121 закрыто, 27 в работе.",
        "status": "completed",
        "isError": false,
        "authorName": null,
        "createdAt": "2026-09-28T09:16:04.000Z"
      }
    ]
  },
  "meta": { "hasMore": false }
}

Поля ответа

ПолеЗначение
data.isStreamingtrue, пока ассистент составляет ответ; false, когда ответ готов
data.canWriteМожет ли владелец писать в этот чат
data.messagesСообщения чата, от старых к новым
data.messages[].roleuser — сообщение владельца, assistant — ответ
data.messages[].contentТекст сообщения
data.messages[].statuscompleted, stopped или failed
data.messages[].isErrortrue, если ход завершился ошибкой
data.messages[].errorCodeКод ошибки, если isError — true
data.messages[].authorNameИмя автора сообщения. У ответа ассистента — null
meta.hasMoreЕсть ли ещё более старые сообщения. Если true, передайте id первого сообщения в before

Ошибки

КодКогда
400id не uuid
404Чата с таким id нет или владелец его удалил
403Чат не принадлежит владельцу ключа, не открыт и не общий с ним
429Превышен лимит 300 запросов в минуту на пользователя

Отправить сообщение

Отправляет сообщение в чат и запускает ход. Ответ на запрос — поток text/event-stream, который закрывается, когда ход завершён. Держите соединение открытым, пока портал не завершит ход (если закрыть его раньше, ход остановится). Что делать после закрытия потока — в разделе «Ожидание ответа».

POST /api/chat-sessions/<id>/messages

Поля запроса

Тело запроса — JSON.

ПолеТипОбязательноЗначениеДопустимые значения
idстрока (uuid)даИдентификатор чатаuuid чата
textстрокадаТекст сообщения1–32000 символов; не может начинаться со слеш-команды
clientMessageIdстрока (uuid)даСвой идентификатор сообщенияuuid, который генерирует клиент
attachedFileIdsмассив строк (uuid)нетФайлы чата, приложенные к сообщениюдо 20; по умолчанию пусто

clientMessageId нужен для повторной отправки. Если запрос оборвался и вы отправили то же сообщение с тем же clientMessageId, портал не создаст дубль, а повторит ход по уже сохранённому сообщению.

Личный ключ файлы чата не загружает и не перечисляет — эти запросы доступны только в портале. Поле attachedFileIds принимает идентификаторы файлов, которые уже загружены в чат в портале.

Пример

curl -sN --max-time 600 -X POST "https://portal.example.com/api/chat-sessions/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/messages" \
  -H "Authorization: Bearer sk_forge_..." \
  -H "Content-Type: application/json" \
  -d '{"text": "Сколько продаж за сентябрь и сколько из них закрыто?", "clientMessageId": "c0ffee00-0000-0000-0000-000000000001"}' \
  > /dev/null

Ответ

Код 200, тип text/event-stream. Поток несёт события, из которых клиенту по ключу нужно одно — message_complete, он означает, что ход завершился:

event: message_start
data: {"role":"assistant"}

event: text_delta
data: {"content":"За сентябрь "}

event: message_complete
data: {"messageId":"2c3d4e5f-6a7b-4c8d-9e0f-1a2b3c4d5e6f","totalDurationMs":4180,"generatedFiles":[]}

Если ход уже выполняется в этом чате, поток закроется событием queued ({"reason":"busy"}) без message_complete. Сообщение сохранится, ход выполнится позже, и ответ появится при следующем опросе.

Поля ответа

ПолеЗначение
messageIdИдентификатор сообщения ассистента. Его можно найти в data.messages при опросе
totalDurationMsСколько миллисекунд занял ход
generatedFilesФайлы, которые ассистент создал в ходе ответа

Ошибки

До начала потока запрос может ответить обычной ошибкой.

КодКогда
400text пустой или длиннее 32000 символов, clientMessageId не uuid, текст начинается со слеш-команды
404Чата с таким id нет или владелец его удалил
403В чат нельзя писать: он архивный, файл не из этого чата, или у владельца нет права на запись
409Чат закрыт. Личный ключ чат не возобновляет — создайте новый чат запросом POST /api/chat-sessions; закрытый остаётся в списке
429Превышен лимит 60 сообщений в час с одного адреса или 300 запросов в минуту на пользователя

Остановить ход

Останавливает ход, который выполняется прямо сейчас. Запрос идемпотентен: если активного хода нет, портал ответит stopped: false и ничего не сломает.

POST /api/chat-sessions/<id>/stop

Поля запроса

Тело запроса не нужно.

ПолеТипОбязательноЗначениеДопустимые значения
idстрока (uuid)даИдентификатор чатаuuid чата

Пример

curl -X POST "https://portal.example.com/api/chat-sessions/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d/stop" \
  -H "Authorization: Bearer sk_forge_..."

Ответ

Код 200.

{ "data": { "stopped": true } }

Поля ответа

ПолеЗначение
stoppedtrue, если ход был остановлен; false, если активного хода не было

Ошибки

КодКогда
400id не uuid
404Чата с таким id нет или владелец его удалил
403У владельца нет права на запись в этот чат
429Превышен лимит 300 запросов в минуту на пользователя

Удалить чат

Удаляет чат. На портале это та же операция, что кнопка «Удалить чат» в интерфейсе. После удаления чат исчезает из списка, а запрос GET /api/chat-sessions/<id> отвечает 404.

DELETE /api/chat-sessions/<id>

Поля запроса

Тело запроса не нужно.

ПолеТипОбязательноЗначениеДопустимые значения
idстрока (uuid)даИдентификатор чатаuuid чата

Пример

curl -X DELETE "https://portal.example.com/api/chat-sessions/9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" \
  -H "Authorization: Bearer sk_forge_..."

Ответ

Код 204, тело ответа пустое.

Поля ответа

Полей нет. Успех означает код 204.

Ошибки

КодКогда
400id не uuid
404Чата с таким id нет или он принадлежит другому сотруднику
429Превышен лимит 300 запросов в минуту на пользователя

На этой странице