# Team Board — REST API спецификация ## Общие сведения **Базовый URL**: `/api/v1/` **Аутентификация**: Bearer токен в заголовке `Authorization` или query parameter `?token=` **Формат данных**: JSON **Middleware**: все API endpoints требуют авторизации (кроме `/api/v1/auth/login`) ### Типы токенов: - **JWT** — для web пользователей (срок жизни 72 часа) - **Bearer токены** — для агентов (формат `tb-xxxxx`, постоянные) ### CORS: - `https://team.uix.su` - `https://dev.team.uix.su` - `http://localhost:3100` --- ## Auth — Аутентификация ### `POST /api/v1/auth/login` Вход в систему для пользователей **Тело запроса:** ```json { "login": "string", // slug или name пользователя "password": "string" } ``` **Ответ (200):** ```json { "token": "jwt_token_here", "member_id": "uuid", "slug": "string", "role": "owner|admin|member" } ``` **Ошибки:** `401` - неверный логин/пароль --- ## Members — Участники (люди + агенты) ### `GET /api/v1/members` Список всех участников **Query параметры:** - `include_inactive=boolean` — включить неактивных (по умолчанию false) **Ответ (200):** ```json [ { "id": "uuid", "name": "string", "slug": "string", "type": "human|agent|bridge", "role": "owner|admin|member", "status": "online|offline|busy", "is_active": true, "avatar_url": "string|null", "last_seen_at": "iso_timestamp", "agent_config": { "capabilities": ["string"], "labels": ["string"], "chat_listen": "all|mentions", "task_listen": "all|mentions", "prompt": "string|null", "model": "string|null" } } ] ``` ### `GET /api/v1/members/{member_id}` Получить участника по ID **Ответ (200):** объект Member (как в списке) **Ошибки:** `404` - участник не найден ### `POST /api/v1/members` Создать нового участника (человека или агента) **Тело запроса:** ```json { "name": "string", "slug": "string", "type": "human|agent", // по умолчанию "agent" "role": "owner|admin|member", // по умолчанию "member" "agent_config": { // только для type="agent" "capabilities": ["string"], "labels": ["string"], "chat_listen": "all|mentions", "task_listen": "all|mentions", "prompt": "string|null", "model": "string|null" } } ``` **Ответ (201):** ```json { "id": "uuid", "name": "string", "slug": "string", "type": "human|agent", "role": "owner|admin|member", "token": "tb-xxxxx", // только при создании агента, показывается один раз "agent_config": {...} // если был передан } ``` **Ошибки:** `409` - slug уже занят ### `PATCH /api/v1/members/{member_id}` Обновить участника **Тело запроса:** ```json { "name": "string|null", "role": "string|null", "status": "string|null", "avatar_url": "string|null", "agent_config": { // только для агентов "capabilities": ["string"], "labels": ["string"], "chat_listen": "all|mentions", "task_listen": "all|mentions", "prompt": "string|null", "model": "string|null" } } ``` **Ответ (200):** обновлённый объект Member **Примечания:** - Изменение agent_config отправляет WebSocket событие `config.updated` агенту - При изменении конфигурации агент получает новые настройки в реальном времени ### `POST /api/v1/members/{member_id}/regenerate-token` Сгенерировать новый токен для агента **Ответ (200):** ```json { "token": "tb-xxxxx" } ``` **Ошибки:** `400` - только для агентов ### `POST /api/v1/members/{member_id}/revoke-token` Отозвать токен агента **Ответ (200):** ```json { "ok": true } ``` **Ошибки:** `400` - только для агентов ### `PATCH /api/v1/members/me/status` Обновить свой статус (используется агентами) **Query параметры:** - `status=string` — новый статус **Ответ (200):** ```json { "status": "online|offline|busy" } ``` **Примечания:** отправляет WebSocket событие `agent.status` всем подключённым клиентам ### `DELETE /api/v1/members/{member_id}` Удалить участника (soft delete — is_active=false) **Авторизация:** только владельцы **Ответ (200):** `{"ok": true}` **Ошибки:** `403` - недостаточно прав --- ## Projects — Проекты ### `GET /api/v1/projects` Список активных проектов **Ответ (200):** ```json [ { "id": "uuid", "name": "string", "slug": "string", "description": "string|null", "repo_urls": ["string"], "status": "active|archived", "task_counter": 42, "chat_id": "uuid|null", "auto_assign": true } ] ``` ### `GET /api/v1/projects/{project_id}` Получить проект по ID **Ответ (200):** объект Project (как в списке) **Ошибки:** `404` - проект не найден ### `POST /api/v1/projects` Создать новый проект **Тело запроса:** ```json { "name": "string", "slug": "string", "description": "string|null", "repo_urls": ["string"] } ``` **Ответ (201):** объект Project **Ошибки:** `409` - slug уже занят **Примечания:** - Автоматически создаётся основной чат проекта - Отправляет WebSocket событие `project.created` ### `PATCH /api/v1/projects/{project_id}` Обновить проект **Тело запроса:** ```json { "name": "string|null", "slug": "string|null", "description": "string|null", "repo_urls": ["string"]|null, "status": "active|archived|null", "auto_assign": "boolean|null" } ``` **Ответ (200):** обновлённый объект Project **Ошибки:** `409` - slug уже занят ### `DELETE /api/v1/projects/{project_id}` Удалить проект **Авторизация:** только владельцы **Ответ (200):** `{"ok": true}` **Ошибки:** `403` - недостаточно прав ### `GET /api/v1/projects/{project_id}/members` Участники проекта **Ответ (200):** ```json [ { "id": "uuid", "name": "string", "slug": "string", "type": "human|agent|bridge", "role": "owner|admin|member" } ] ``` ### `POST /api/v1/projects/{project_id}/members` Добавить участника в проект **Тело запроса:** ```json { "member_id": "uuid" } ``` **Ответ (200):** `{"ok": true}` **Ошибки:** `404` - участник не найден, `409` - участник уже в проекте ### `DELETE /api/v1/projects/{project_id}/members/{member_id}` Исключить участника из проекта **Ответ (200):** `{"ok": true}` **Ошибки:** `400` - нельзя исключить владельца, `404` - участник не в проекте --- ## Tasks — Задачи ### `GET /api/v1/tasks` Список задач с фильтрацией **Query параметры:** - `project_id=uuid` — фильтр по проекту - `status=string` — фильтр по статусу (backlog|todo|in_progress|review|done) - `assignee_id=uuid` — фильтр по исполнителю - `label=string` — фильтр по лейблу - `q=string` — поиск по номеру (XX-123) или заголовку **Ответ (200):** ```json [ { "id": "uuid", "project": { "id": "uuid", "slug": "string", "name": "string" }, "number": 123, "key": "XX-123", "title": "string", "description": "string|null", "type": "task|bug|epic", "status": "backlog|todo|in_progress|review|done", "priority": "low|medium|high|urgent", "labels": ["string"], "assignee": { "id": "uuid", "slug": "string", "name": "string" } | null, "reviewer": {...} | null, "parent": {...} | null, "subtasks": [...], "steps": [ { "id": "uuid", "title": "string", "done": true, "position": 0 } ], "watcher_ids": ["uuid"], "depends_on": ["uuid"], "position": 0, "created_at": "iso_timestamp", "updated_at": "iso_timestamp" } ] ``` ### `GET /api/v1/tasks/{task_id}` Получить задачу по ID **Ответ (200):** объект Task (как в списке) **Ошибки:** `404` - задача не найдена ### `POST /api/v1/tasks` Создать новую задачу **Query параметры:** - `project_id=uuid` — ID проекта (обязательно) **Тело запроса:** ```json { "title": "string", "description": "string|null", "type": "task|bug|epic", // по умолчанию "task" "status": "backlog|todo|in_progress|review|done", // по умолчанию "backlog" "priority": "low|medium|high|urgent", // по умолчанию "medium" "labels": ["string"], "parent_id": "uuid|null", "assignee_id": "uuid|null", "reviewer_id": "uuid|null", "depends_on": ["uuid"] } ``` **Ответ (201):** объект Task **Примечания:** - Автоматически назначается номер задачи (инкрементальный счётчик проекта) - При auto_assign=true проекта может автоматически назначаться на агента по лейблам - Отправляет WebSocket событие `task.created` - Создаёт системное сообщение в чате проекта и комментариях задачи ### `PATCH /api/v1/tasks/{task_id}` Обновить задачу **Тело запроса:** ```json { "title": "string|null", "description": "string|null", "type": "string|null", "status": "string|null", "priority": "string|null", "labels": ["string"]|null, "assignee_id": "string|null", "reviewer_id": "string|null", "position": "number|null" } ``` **Ответ (200):** обновлённый объект Task **Примечания:** - Каждое изменение записывается в audit log (TaskAction) - Отправляет WebSocket событие `task.updated` - Создаёт системные сообщения для значимых изменений (статус, назначение) ### `DELETE /api/v1/tasks/{task_id}` Удалить задачу **Ответ (200):** `{"ok": true}` **Примечания:** отправляет WebSocket событие `task.deleted` ### `POST /api/v1/tasks/{task_id}/take` Взять задачу в работу (для агентов) **Условия:** - Задача не назначена (assignee_id=null) - Статус backlog или todo **Действие:** - assignee_id = текущий пользователь - status = in_progress - Добавление в watcher_ids **Ответ (200):** обновлённый объект Task **Ошибки:** `409` - задача уже назначена или неподходящий статус ### `POST /api/v1/tasks/{task_id}/reject` Отклонить назначенную задачу **Тело запроса:** ```json { "reason": "string" } ``` **Действие:** - assignee_id = null - status = backlog - Создание системного сообщения с причиной **Ответ (200):** `{"ok": true, "reason": "string"}` ### `POST /api/v1/tasks/{task_id}/assign` Назначить задачу на участника **Тело запроса:** ```json { "assignee_id": "uuid" } ``` **Ответ (200):** обновлённый объект Task **Ошибки:** `404` - участник не найден ### `POST /api/v1/tasks/{task_id}/watch` Подписаться на уведомления по задаче **Ответ (200):** ```json { "ok": true, "watcher_ids": ["uuid"] } ``` ### `DELETE /api/v1/tasks/{task_id}/watch` Отписаться от уведомлений по задаче **Ответ (200):** ```json { "ok": true, "watcher_ids": ["uuid"] } ``` ### `POST /api/v1/tasks/{task_id}/links` Создать связь между задачами (зависимость) **Тело запроса:** ```json { "target_id": "uuid", "link_type": "blocks|depends_on|relates_to" } ``` **Ответ (201):** ```json { "id": "uuid", "source_id": "uuid", "target_id": "uuid", "link_type": "string", "target_key": "XX-123", "target_title": "string", "source_key": "XX-124", "source_title": "string" } ``` **Ошибки:** `400` - нельзя связать задачу с самой собой, `409` - связь уже существует --- ## Steps — Этапы задач (чеклист) ### `GET /api/v1/tasks/{task_id}/steps` Список этапов задачи **Ответ (200):** ```json [ { "id": "uuid", "title": "string", "done": true, "position": 0 } ] ``` ### `POST /api/v1/tasks/{task_id}/steps` Создать новый этап **Тело запроса:** ```json { "title": "string" } ``` **Ответ (201):** объект Step **Ошибки:** `404` - задача не найдена ### `PATCH /api/v1/tasks/{task_id}/steps/{step_id}` Обновить этап **Тело запроса:** ```json { "title": "string|null", "done": "boolean|null" } ``` **Ответ (200):** обновлённый объект Step ### `DELETE /api/v1/tasks/{task_id}/steps/{step_id}` Удалить этап **Ответ (200):** `{"ok": true}` --- ## Messages — Сообщения (чаты + комментарии к задачам) ### `GET /api/v1/messages` Список сообщений **Query параметры:** - `chat_id=uuid` — сообщения из чата - `task_id=uuid` — комментарии к задаче - `parent_id=uuid` — ответы в треде - `limit=number` — лимит (по умолчанию 50, макс 200) - `offset=number` — смещение (по умолчанию 0) **Ответ (200):** ```json [ { "id": "uuid", "chat_id": "uuid|null", "task_id": "uuid|null", "parent_id": "uuid|null", "author_type": "human|agent|system", "author_id": "uuid|null", "author": { "id": "uuid", "slug": "string", "name": "string" } | null, "actor": {...} | null, // для системных сообщений "content": "string", "thinking": "string|null", "mentions": [ { "id": "uuid", "slug": "string", "name": "string" } ], "voice_url": "string|null", "attachments": [ { "id": "uuid", "filename": "string", "mime_type": "string", "size": 1024 } ], "created_at": "iso_timestamp" } ] ``` **Примечания:** возвращает последние N сообщений в хронологическом порядке ### `POST /api/v1/messages` Отправить сообщение **Тело запроса:** ```json { "chat_id": "uuid|null", // либо chat_id, либо task_id обязательно "task_id": "uuid|null", "parent_id": "uuid|null", // для ответов в треде "content": "string", "thinking": "string|null", // внутренние размышления (для агентов) "mentions": ["member_id"], // упоминания по ID "voice_url": "string|null", // ссылка на голосовое сообщение "attachments": [ // прикреплённые файлы из /upload { "file_id": "uuid", "filename": "string", "mime_type": "string", "size": 1024, "storage_name": "string" } ] } ``` **Ответ (201):** объект Message **Ошибки:** `400` - не указан chat_id или task_id, `401` - не авторизован **Примечания:** - author_id берётся из токена авторизации - Отправляет WebSocket событие `message.new` - Для project чатов событие фильтруется по подпискам участников ### `GET /api/v1/messages/{message_id}/replies` Ответы в треде **Ответ (200):** массив объектов Message (отсортированы по created_at) --- ## Attachments — Файловые вложения ### `POST /api/v1/upload` Загрузить файл **Content-Type:** `multipart/form-data` **Поля:** - `file` — файловое поле **Ответ (200):** ```json { "file_id": "uuid", "filename": "string", "mime_type": "string", "size": 1024, "storage_name": "string" } ``` **Ошибки:** `413` - файл слишком большой (лимит 50MB) **Примечания:** - Файл сохраняется в `/data/uploads/` - Возвращаемые данные используются в attachments при создании сообщений - Файл пока не привязан к сообщению ### `GET /api/v1/attachments/{attachment_id}/download` Скачать файл **Query параметры:** - `token=string` — токен авторизации (опционально, для img src) **Ответ (200):** файл с правильными заголовками **Ошибки:** `404` - файл не найден --- ## Project Files — Файлы проектов ### `GET /api/v1/projects/{project_id}/files` Список файлов проекта **Query параметры:** - `search=string` — поиск по имени файла **Ответ (200):** ```json [ { "id": "uuid", "filename": "string", "description": "string|null", "mime_type": "string", "size": 1024, "uploaded_by": { "id": "uuid", "slug": "string", "name": "string" }, "created_at": "iso_timestamp", "updated_at": "iso_timestamp" } ] ``` ### `POST /api/v1/projects/{project_id}/files` Загрузить файл в проект **Content-Type:** `multipart/form-data` **Поля:** - `file` — файловое поле - `description` — описание файла (опционально) **Ответ (201):** объект ProjectFile **Ошибки:** `413` - файл слишком большой, `400` - некорректное имя файла **Примечания:** - Файлы сохраняются в `/data/projects/{project_slug}/` - При загрузке файла с существующим именем — перезаписывается ### `GET /api/v1/projects/{project_id}/files/{file_id}` Информация о файле **Ответ (200):** объект ProjectFile ### `GET /api/v1/projects/{project_id}/files/{file_id}/download` Скачать файл проекта **Ответ (200):** файл с правильными заголовками **Ошибки:** `404` - файл не найден ### `PATCH /api/v1/projects/{project_id}/files/{file_id}` Обновить описание файла **Тело запроса:** ```json { "description": "string|null" } ``` **Ответ (200):** обновлённый объект ProjectFile ### `DELETE /api/v1/projects/{project_id}/files/{file_id}` Удалить файл проекта **Ответ (200):** `{"ok": true}` --- ## Labels — Глобальные лейблы ### `GET /api/v1/labels` Список всех лейблов **Ответ (200):** ```json [ { "id": "uuid", "name": "string", "color": "#6366f1" } ] ``` ### `POST /api/v1/labels` Создать новый лейбл **Тело запроса:** ```json { "name": "string", "color": "#6366f1" // по умолчанию } ``` **Ответ (201):** объект Label ### `PATCH /api/v1/labels/{label_id}` Обновить лейбл **Тело запроса:** ```json { "name": "string|null", "color": "string|null" } ``` **Ответ (200):** обновлённый объект Label ### `DELETE /api/v1/labels/{label_id}` Удалить лейбл **Ответ (200):** `{"ok": true}` ### `POST /api/v1/tasks/{task_id}/labels/{label_id}` Добавить лейбл к задаче **Ответ (200):** `{"ok": true}` ### `DELETE /api/v1/tasks/{task_id}/labels/{label_id}` Убрать лейбл с задачи **Ответ (200):** `{"ok": true}` --- ## Utility Endpoints ### `GET /health` Health check **Авторизация:** не требуется **Ответ (200):** ```json { "status": "ok", "version": "0.2.0" } ``` ### `GET /docs` Swagger UI документация **Авторизация:** не требуется **Доступность:** только в dev режиме --- ## Коды ошибок - `400` — Bad Request (некорректные данные) - `401` — Unauthorized (не авторизован или неверный токен) - `403` — Forbidden (недостаточно прав) - `404` — Not Found (ресурс не найден) - `409` — Conflict (конфликт данных, например дублирование slug) - `413` — Payload Too Large (файл слишком большой) - `500` — Internal Server Error (внутренняя ошибка сервера) ## Middleware и логирование **Логирование запросов:** все HTTP запросы логируются с телом (для POST/PUT/PATCH), временем выполнения и кодом ответа **Извлечение участника:** middleware автоматически извлекает Member из JWT/agent token и сохраняет в `request.state.member` **CORS:** автоматически обрабатывается для разрешённых origins Этот документ описывает API на основе исходного кода в `/root/projects/team-board/tracker/src/tracker/api/` на дату создания спецификации. ### Обновление slug (добавлено 2026-03-13) `PATCH /api/v1/members/{member_id}` теперь принимает поле `slug`. - Валидация: slug уникален, при конфликте → 409 - Формат: lowercase, только `[a-z0-9_-]`