docs/specs/API.md

879 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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/` на дату создания спецификации.