API

API Pastukhov Agent — это способ отдавать команды агенту и получать ответы без браузера. Другие программы могут создавать чаты, отправлять промпты и забирать результаты по набору адресов (эндпоинтов): из сборочных конвейеров, внешних инструментов и обёрток. Тот же ключ открывает и расширенные возможности (чаты, навыки, модели, настройки, git, аналитику и распознавание речи) и подписку на живые события — именно так Research Agent подключается к своему полю ввода, микрофону и аналитике. Раздел ниже — справочник этих адресов для разработчиков; если вы впервые слышите об API, достаточно понять общую идею: программа общается с агентом по тем же правилам, что и человек в интерфейсе.


Аутентификация

API включается установкой переменной окружения AGENT_API_KEY. В ней хранится не сам ключ, а его «отпечаток» (SHA-256 хеш — короткая строка, по которой ключ восстановить нельзя); при каждом запросе сервер вычисляет отпечаток присланного ключа и сравнивает с сохранённым.

Ключ передаётся в заголовке X-API-Key — служебном поле запроса, где программа указывает свой ключ доступа:

X-API-Key: my-secret-api-key

Для живых подключений (WebSocket/SignalR) заголовки задать нельзя, поэтому тот же ключ принимается и как параметр адреса api_key:

/svelteChatHub?api_key=my-secret-api-key

Как получить хеш ключа

Вычислить отпечаток ключа можно на странице входа приложения (кнопка «SHA256 generator» под формой входа) или вручную:

# Linux / macOS
echo -n "my-secret-api-key" | sha256sum | awk '{print $1}'

# Windows PowerShell
$hash = [System.BitConverter]::ToString([System.Security.Cryptography.SHA256]::Create().ComputeHash([System.Text.Encoding]::UTF8.GetBytes("my-secret-api-key"))).Replace("-", "").ToLower()
$hash

Полученный отпечаток укажите в AGENT_API_KEY (переменная уровня приложения, задаётся до запуска — системной переменной, аргументом командной строки или через .env).

Коды при ошибках аутентификации

  • 401 Unauthorized — ключ не передан или неверный;
  • 503 Service Unavailable — переменная AGENT_API_KEY не задана: «API disabled», API отключён целиком.

Команды /api/remote

Эндпоинт — это адрес, по которому программа обращается к серверу, чтобы что-то получить или сделать. Все команды /api/remote описаны ниже.

Создание чата

POST /api/remote/chats

Создаёт новый чат. Промпт, если он передан, ставится в очередь и обрабатывается автоматически — напрямую он не отправляется. Чат можно дополнительно привязать к боту (botId); привязка «молчаливая»: она никогда не спамит в Telegram и не может помешать созданию чата, а её исход возвращается флагами botLinked и botLinkNote.

Тело запроса

  • prompt (опц.) — начальное сообщение; при наличии чат создаётся со статусом queued, без него — created;
  • title (опц.) — название чата (по умолчанию «Remote API Chat»);
  • model (опц.) — модель для чата (см. Модели);
  • skill (опц.) — навык; отсутствие = «none» (без наследования);
  • systemPrompt (опц.) — системный промпт, сохраняется на чате;
  • botId (опц.) — id бота для молчаливой привязки чата.

Ответ (201 Created)

{
  "chatId": 123,
  "url": "/123",
  "status": "queued",
  "createdAt": "2026-09-04T10:00:00Z",
  "botLinked": null,
  "botLinkNote": ""
}

Пример

curl -X POST http://localhost:5173/api/remote/chats \
  -H "X-API-Key: my-secret-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Explain what this code does",
    "title": "Code Analysis",
    "model": "claude-sonnet"
  }'

Статус чата

GET /api/remote/chats/{chatId}/status

Возвращает текущий статус обработки: processing — агент работает, queued — чат свободен, но в его очереди есть сообщения, иначе completed. Вместе со статусом приходят заголовок, метки времени, количество сообщений и накопленная стоимость. Поле error в ответе всегда пустое.

{
  "chatId": 123,
  "status": "processing",
  "title": "Code Analysis",
  "createdAt": "2026-09-04T10:00:00Z",
  "updatedAt": "2026-09-04T10:00:15Z",
  "messageCount": 2,
  "totalCost": 0.005
}

Пример:

curl http://localhost:5173/api/remote/chats/123/status \
  -H "X-API-Key: my-secret-api-key"

Сообщения чата

GET /api/remote/chats/{chatId}/messages?since={timestamp}

Инкрементальное чтение сообщений — программа забирает только то, что появилось после указанной метки, и не разбирает всю историю заново. Параметр since (ISO 8601) возвращает сообщения от указанной метки включительно и сам отражается в ответе; неверное значение since молча игнорируется — возвращается полная история. Содержимое упрощается по типу: сообщение пользователя — его текст; ответ агента — текст без разметки кнопок; ошибка — текст ошибки; использование инструмента — Tool: <имя>.

{
  "chatId": 123,
  "messages": [
    {
      "id": 456,
      "role": "user",
      "content": "Explain what this code does",
      "createdAt": "2026-09-04T10:00:00Z",
      "tokens": { "input": 120, "output": 0 },
      "cost": 0.0001
    },
    {
      "id": 457,
      "role": "assistant",
      "content": "This module handles ...",
      "createdAt": "2026-09-04T10:00:20Z",
      "tokens": { "input": 140, "output": 320 },
      "cost": 0.003
    }
  ],
  "since": "2026-09-04T10:00:00Z"
}

Примеры:

# Все сообщения
curl http://localhost:5173/api/remote/chats/123/messages \
  -H "X-API-Key: my-secret-api-key"

# Только новые с указанной метки
curl "http://localhost:5173/api/remote/chats/123/messages?since=2026-09-04T10:00:10Z" \
  -H "X-API-Key: my-secret-api-key"

Очередь промптов

Пять команд под /api/remote/chats/{chatId}/queue управляют промптами, ожидающими отправки агенту. В отличие от внутренних команд очереди, удалённые строже: неизвестный чат или промпт, которого нет в очереди (никогда не было, принадлежит другому чату или уже отправлен), — ошибка 404; пустой или состоящий из пробелов текст — 400.

Список очереди

GET /api/remote/chats/{chatId}/queue

Очередь в порядке отправки: id, prompt, isPaused, model (переопределение для сообщения), createdAt. Приостановленные промпты включены и помечены isPaused.

{
  "chatId": 123,
  "prompts": [
    {
      "id": 456,
      "prompt": "Refactor the auth module",
      "isPaused": false,
      "model": "",
      "createdAt": "2026-09-04T10:00:00Z"
    },
    {
      "id": 457,
      "prompt": "Then update the README",
      "isPaused": true,
      "model": "claude-sonnet",
      "createdAt": "2026-09-04T10:01:00Z"
    }
  ]
}

Добавить промпт в конец очереди

POST /api/remote/chats/{chatId}/queue

Дописывает промпт в конец очереди (как любой другой источник сообщений). prompt обязателен и непустой; model — необязательное переопределение модели для этого сообщения. Ответ 201 — добавленный промпт с его id.

curl -X POST http://localhost:5173/api/remote/chats/123/queue \
  -H "X-API-Key: my-secret-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": "Now add unit tests for the refactored module"
  }'

Переписать текст одного промпта

PUT /api/remote/chats/{chatId}/queue/{promptId}

Изменяет только текст: id, позиция, модель и пауза строки сохраняются. 400 при пустом тексте; 404, если промпта нет в очереди этого чата или он уже отправлен.

curl -X PUT http://localhost:5173/api/remote/chats/123/queue/456 \
  -H "X-API-Key: my-secret-api-key" \
  -H "Content-Type: application/json" \
  -d '{ "prompt": "Refactor the auth module and update the docs" }'

Удалить один промпт

DELETE /api/remote/chats/{chatId}/queue/{promptId}

Удаляет промпт из очереди; успех — 204 No Content.

curl -X DELETE http://localhost:5173/api/remote/chats/123/queue/456 \
  -H "X-API-Key: my-secret-api-key"

Заменить очередь целиком

PUT /api/remote/chats/{chatId}/queue

Переданный массив становится всей очередью. Правила замены:

  • элемент с id, совпавшим с существующим, сохраняет идентичность — ту же строку, модель и паузу (если isPaused не задан явно);
  • элемент без id (или с id: 0) создаёт новый промпт;
  • существующие промпты, которых нет в массиве, удаляются — включая добавленные другими источниками после вашего последнего чтения (last writer wins);
  • порядок массива становится порядком диспетчеризации — перестановка массива меняет очередь;
  • пустой массив ([]) очищает очередь;
  • id уже отправленного промпта пропускается (не воскрешается) — повторная отправка продублировала бы сообщение, которое агент уже обрабатывает.

Ошибки 400: массив не передан; любой элемент с пустым текстом; один и тот же положительный id встречается дважды.

curl -X PUT http://localhost:5173/api/remote/chats/123/queue \
  -H "X-API-Key: my-secret-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "prompts": [
      { "id": 457, "prompt": "Refactor the auth module (edited)" },
      { "prompt": "Then update the README" }
    ]
  }'

Список ботов

GET /api/remote/bots

Только чтение: каждый настроенный бот возвращается с id, title, enabled, running и pollingError — достаточно, чтобы внешняя программа показала ботов со статусными точками. Никаких токенов, промптов и управления: боты настраиваются только в интерфейсе Pastukhov Agent.

{
  "bots": [
    {
      "id": "release-bot",
      "title": "Release Bot",
      "enabled": true,
      "running": true,
      "pollingError": null
    }
  ]
}

Статусы чата

createdчат создан без начального промпта, ждёт ввода
queuedчат создан с промптом, сообщение в очереди
processingагент обрабатывает сообщение
completedвсе сообщения обработаны, чат бездействует
errorошибка обработки (в удалённом API поле error в ответе всегда пустое)

Расширенная поверхность по API-ключу

Тот же ключ открывает гораздо больше, чем /api/remote. Передав его на эти пути, вы действуете под ролью Manager — как при входе менеджером:

  • /api/chats, /api/chat — чтение чатов и работа с ними;
  • /svelteChatHub — живой SignalR-хаб (см. ниже);
  • /api/skills — навыки (список, выбор);
  • /api/models — модели (список, выбор);
  • /api/autofix — статус и переключение автоисправления;
  • /api/settings — настройки (settings/all, settings/set);
  • /api/skills-settings — цвета навыков из skills.yml;
  • /api/git — статус и коммит рабочего репозитория;
  • /api/MessageQueue — правка, удаление, пауза очереди сообщений;
  • /api/analytics — аналитика чатов (только чтение);
  • /api/speechrecognition — транскрибация аудио движками распознавания речи.

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


Подписка на живые события (SignalR)

Вместо постоянных опросов статуса внешняя система может подписаться на живые события — получать уведомления, когда в чатах что-то происходит. Подключитесь к /svelteChatHub, передав ключ заголовком X-API-Key или параметром адреса api_key (для WebSocket обязателен параметр адреса — заголовки задать нельзя):

const connection = new signalR.HubConnectionBuilder()
    .withUrl("http://localhost:5173/svelteChatHub?api_key=my-secret-api-key")
    .build();

connection.on("messageUpdate", (chatId, message) => {
    console.log("Новое сообщение в чате", chatId, message.content);
});

connection.start();

События приходят при изменениях: новые сообщения, обновления чатов (включая статусы обработки), изменения очереди сообщений и промптов, смена настроек, навыков и моделей. Для соединений включено аккуратное переподключение — разрыв не теряет подписку. Именно так Research Agent показывает интерфейс Pastukhov Agent.


Пример рабочего потока

Типичный сценарий: создать чат → опрашивать статус → забрать сообщения → дописать промпт в очередь → заменить очередь. Полный цикл на Python:

import time, requests

API_KEY = "my-secret-api-key"
BASE = "http://localhost:5173/api/remote"
H = {"X-API-Key": API_KEY}

# 1. Создать чат — промпт встанет в очередь
resp = requests.post(f"{BASE}/chats", headers=H, json={
    "prompt": "Add error handling to the login endpoint",
    "title": "Error Handling",
    "model": "claude-sonnet"
})
chat_id = resp.json()["chatId"]

# 2. Опрашивать статус до завершения
while True:
    status = requests.get(f"{BASE}/chats/{chat_id}/status", headers=H).json()["status"]
    if status in ("completed", "error"):
        break
    time.sleep(2)

# 3. Забрать ответы (инкрементально через since)
messages = requests.get(f"{BASE}/chats/{chat_id}/messages", headers=H).json()["messages"]
for msg in messages:
    if msg["role"] == "assistant":
        print(msg["content"])

# 4. Дописать follow-up в конец очереди
requests.post(f"{BASE}/chats/{chat_id}/queue", headers=H,
    json={"prompt": "Now add unit tests"})

# 5. Заменить очередь целиком
requests.put(f"{BASE}/chats/{chat_id}/queue", headers=H, json={
    "prompts": [
        {"prompt": "Fix the auth flow"},
        {"prompt": "Update the README"}
    ]
})

Формат ошибок

Все ошибки следуют единому формату {error, message, details}:

{
  "error": "NotFound",
  "message": "Chat 123 not found",
  "details": ""
}
  • Unauthorized — 401, ключ не передан или неверный;
  • BadRequest — 400, неверное тело запроса (пустой промпт, дубль id, нет массива);
  • NotFound — 404, чат или промпт не найден;
  • Remote API is disabled — 503, переменная AGENT_API_KEY не задана;
  • InternalServerError — 500, неожиданная ошибка сервера (details содержит текст исключения).