{
  "openapi": "3.0.3",
  "info": {
    "title": "MasterTeam API",
    "version": "1.0.0",
    "description": "Внешний API MasterTeam. Ключ создаётся в настройках пространства (только владелец) и передаётся в заголовке X-Api-Key. Ключ действует от имени владельца, только внутри своего пространства и только на методах, на которые у него есть право. Все ответы — JSON в UTF-8. Ошибки: поле error (текст), code (что делать), request_id (для поддержки)."
  },
  "servers": [{ "url": "https://masterteam.pro/api/v1" }],
  "security": [{ "apiKey": [] }],
  "components": {
    "securitySchemes": {
      "apiKey": { "type": "apiKey", "in": "header", "name": "X-Api-Key" }
    },
    "parameters": {
      "Limit": { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 }, "description": "Сколько записей вернуть. Работает, если указан limit или offset." },
      "Offset": { "name": "offset", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 0, "default": 0 }, "description": "С какой записи начинать." },
      "IfMatch": { "name": "If-Match", "in": "header", "required": false, "schema": { "type": "string" }, "description": "ETag, полученный при чтении объекта. Если объект изменился, ответ 412." },
      "IdempotencyKey": { "name": "Idempotency-Key", "in": "header", "required": false, "schema": { "type": "string", "minLength": 8, "maxLength": 100, "pattern": "^[A-Za-z0-9._:-]+$" }, "description": "Для создающих запросов. Повтор с тем же ключом и тем же запросом вернёт первый ответ, новая запись не создастся. Ключ помнится 24 часа." }
    },
    "responses": {
      "Error": {
        "description": "Ошибка",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "description": "Что случилось, по-русски" },
          "code": { "type": "string", "enum": ["unauthorized", "forbidden", "not_found", "conflict", "too_large", "validation_failed", "rate_limited", "internal_error", "error"] },
          "request_id": { "type": "string", "description": "Тот же номер, что в заголовке X-Request-Id" }
        },
        "required": ["error"]
      },
      "Project": {
        "type": "object",
        "description": "Проект. Набор полей — как в приложении (см. GET /projects/{id})."
      },
      "Task": {
        "type": "object",
        "description": "Задача. Включает subtasks (подзадачи), attachments (вложения), section, parent."
      },
      "NewProject": {
        "type": "object",
        "required": ["name"],
        "properties": {
          "name": { "type": "string", "maxLength": 160 },
          "description": { "type": "string", "maxLength": 5000 },
          "color": { "type": "string", "description": "Цвет проекта, например #3b82f6" },
          "privacy": { "type": "string", "enum": ["public", "private"], "default": "public" },
          "template": { "type": "string", "enum": ["kanban", "simple"], "default": "kanban", "description": "kanban — три раздела, simple — один" }
        }
      },
      "Section": {
        "type": "object",
        "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "position": { "type": "integer" } }
      },
      "NewTask": {
        "type": "object",
        "required": ["title"],
        "properties": {
          "title": { "type": "string" },
          "project_id": { "type": "integer" },
          "section_id": { "type": "integer", "description": "Раздел доски. Для подзадачи не нужен" },
          "parent_id": { "type": "integer", "description": "Задача-родитель: тогда создаётся подзадача. Вложенность не глубже двух уровней" },
          "description": { "type": "string" },
          "assignee_id": { "type": "integer" },
          "due_date": { "type": "string", "format": "date", "description": "YYYY-MM-DD" },
          "priority": { "type": "string", "enum": ["none", "low", "medium", "high"] },
          "recurrence": { "type": "string", "enum": ["none", "daily", "weekdays", "weekly", "monthly", "yearly"] }
        }
      },
      "Invite": {
        "type": "object",
        "properties": {
          "id": { "type": "integer" },
          "email": { "type": "string", "format": "email" },
          "role": { "type": "string", "description": "guest — приглашённые в проект входят гостями" },
          "expires_at": { "type": "string", "format": "date-time" }
        }
      }
    }
  },
  "paths": {
    "/projects": {
      "get": {
        "summary": "Проекты пространства",
        "tags": ["Проекты"],
        "security": [{ "apiKey": [] }],
        "parameters": [{ "$ref": "#/components/parameters/Limit" }, { "$ref": "#/components/parameters/Offset" }],
        "responses": { "200": { "description": "Список проектов. В X-Total-Count — всего записей", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Project" } } } } }, "401": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" } }
      },
      "post": {
        "summary": "Создать проект",
        "description": "Право «Создавать и менять проекты и разделы». В ответе — id, url и разделы.",
        "tags": ["Проекты"],
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NewProject" } } } },
        "responses": { "200": { "description": "Проект создан", "content": { "application/json": { "schema": { "type": "object", "properties": { "id": { "type": "integer" }, "url": { "type": "string" }, "sections": { "type": "array", "items": { "$ref": "#/components/schemas/Section" } } } } } } }, "422": { "$ref": "#/components/responses/Error" }, "403": { "$ref": "#/components/responses/Error" }, "409": { "$ref": "#/components/responses/Error" } }
      }
    },
    "/projects/{id}": {
      "get": {
        "summary": "Проект",
        "tags": ["Проекты"],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "Проект. Заголовок ETag — версия для If-Match", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Project" } } } }, "404": { "$ref": "#/components/responses/Error" } }
      },
      "patch": {
        "summary": "Изменить проект",
        "description": "Название, описание, цвет, статус, архив (archived: true/false). Доступ проекта (privacy) через API не меняется — ответ 403.",
        "tags": ["Проекты"],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }, { "$ref": "#/components/parameters/IfMatch" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "color": { "type": "string" }, "archived": { "type": "boolean" } } } } } },
        "responses": { "200": { "description": "Проект изменён" }, "403": { "$ref": "#/components/responses/Error" }, "412": { "$ref": "#/components/responses/Error" } }
      }
    },
    "/projects/{id}/fields": {
      "get": { "summary": "Поля проекта", "tags": ["Проекты"], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }], "responses": { "200": { "description": "Поля проекта" } } }
    },
    "/projects/{id}/team": {
      "get": { "summary": "Команда проекта и доступные исполнители", "tags": ["Участники"], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }], "responses": { "200": { "description": "Участники проекта с ролями" } } }
    },
    "/projects/{id}/activity": {
      "get": { "summary": "Лента проекта", "tags": ["Проекты"], "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }], "responses": { "200": { "description": "Последние события" } } }
    },
    "/projects/{id}/sections": {
      "post": {
        "summary": "Создать раздел доски",
        "tags": ["Проекты"],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["name"], "properties": { "name": { "type": "string" } } } } } },
        "responses": { "200": { "description": "Раздел создан", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Section" } } } } }
      }
    },
    "/sections/{id}": {
      "patch": {
        "summary": "Переименовать раздел",
        "tags": ["Проекты"],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string" } } } } } },
        "responses": { "200": { "description": "Раздел изменён" } }
      }
    },
    "/projects/{id}/invites": {
      "get": {
        "summary": "Приглашения в проект и их статус",
        "tags": ["Участники"],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "Приглашения", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Invite" } } } } } }
      }
    },
    "/projects/{id}/members": {
      "post": {
        "summary": "Добавить участника пространства в проект",
        "description": "Право «Участники». Человек должен уже состоять в пространстве. Удалять из проекта через API нельзя.",
        "tags": ["Участники"],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["user_id"], "properties": { "user_id": { "type": "integer" } } } } } },
        "responses": { "200": { "description": "Участник добавлен" }, "422": { "$ref": "#/components/responses/Error" } }
      }
    },
    "/projects/{id}/members/{userId}/role": {
      "put": {
        "summary": "Роль участника в проекте",
        "tags": ["Участники"],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }, { "name": "userId", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["role"], "properties": { "role": { "type": "string", "enum": ["admin", "edit", "comment", "view"] } } } } } },
        "responses": { "200": { "description": "Роль изменена" } }
      }
    },
    "/projects/{id}/invite": {
      "post": {
        "summary": "Пригласить по email в проект",
        "description": "Если человек уже в пространстве, его добавляют сразу. Новые приглашённые входят гостями, без платного места.",
        "tags": ["Участники"],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["email"], "properties": { "email": { "type": "string", "format": "email" } } } } } },
        "responses": { "200": { "description": "Приглашение отправлено или человек добавлен" }, "409": { "$ref": "#/components/responses/Error" } }
      }
    },
    "/invites/{id}": {
      "delete": {
        "summary": "Отозвать приглашение",
        "tags": ["Участники"],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "Приглашение отозвано" }, "404": { "$ref": "#/components/responses/Error" } }
      }
    },
    "/members": {
      "get": {
        "summary": "Участники пространства",
        "tags": ["Участники"],
        "parameters": [
          { "name": "q", "in": "query", "required": false, "schema": { "type": "string" }, "description": "Поиск по имени или email" },
          { "$ref": "#/components/parameters/Limit" },
          { "$ref": "#/components/parameters/Offset" }
        ],
        "responses": { "200": { "description": "Участники" } }
      }
    },
    "/tasks": {
      "post": {
        "summary": "Создать задачу или подзадачу",
        "tags": ["Задачи"],
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NewTask" } } } },
        "responses": { "200": { "description": "Задача создана", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Task" } } } }, "422": { "$ref": "#/components/responses/Error" } }
      }
    },
    "/tasks/{id}": {
      "get": {
        "summary": "Задача с подзадачами и вложениями",
        "tags": ["Задачи"],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "Задача. Заголовок ETag — версия для If-Match", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Task" } } } }, "404": { "$ref": "#/components/responses/Error" } }
      },
      "patch": {
        "summary": "Изменить задачу или подзадачу",
        "description": "Название, описание, исполнитель, срок, приоритет, выполнение, раздел (section_id). Для подзадачи — тот же метод.",
        "tags": ["Задачи"],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }, { "$ref": "#/components/parameters/IfMatch" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/NewTask" } } } },
        "responses": { "200": { "description": "Задача изменена" }, "412": { "$ref": "#/components/responses/Error" } }
      }
    },
    "/tasks/{id}/fields/{fid}": {
      "put": {
        "summary": "Значение дополнительного поля задачи",
        "tags": ["Задачи"],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }, { "name": "fid", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "value": {} } } } } },
        "responses": { "200": { "description": "Значение сохранено" } }
      }
    },
    "/tasks/{id}/comments": {
      "post": {
        "summary": "Комментарий к задаче",
        "tags": ["Задачи"],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["body"], "properties": { "body": { "type": "string" } } } } } },
        "responses": { "200": { "description": "Комментарий добавлен" } }
      }
    },
    "/tasks/{id}/attachments": {
      "post": {
        "summary": "Загрузить файл к задаче",
        "description": "multipart/form-data, поле files (один или несколько файлов). Допустимые типы и размер — как в приложении.",
        "tags": ["Файлы"],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "multipart/form-data": { "schema": { "type": "object", "properties": { "files": { "type": "array", "items": { "type": "string", "format": "binary" } } }, "required": ["files"] } } } },
        "responses": { "200": { "description": "Файлы загружены" }, "413": { "$ref": "#/components/responses/Error" } }
      }
    },
    "/attachments/{id}": {
      "get": {
        "summary": "Скачать вложение",
        "tags": ["Файлы"],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }],
        "responses": { "200": { "description": "Файл" }, "404": { "$ref": "#/components/responses/Error" } }
      }
    },
    "/projects/{id}/notes": {
      "post": {
        "summary": "Заметка в проект без задачи",
        "tags": ["Проекты"],
        "parameters": [{ "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } }, { "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["text"], "properties": { "text": { "type": "string", "maxLength": 2000 } } } } } },
        "responses": { "200": { "description": "Заметка создана" } }
      }
    }
  }
}
