openapi: 3.1.0
info:
  title: Kooly API
  version: 1.0.0
  summary: API для интеграций с рабочими пространствами Kooly
  description: |
    Публичный контракт Kooly API v1. API работает от имени пользователя,
    выпустившего персональный токен, и соблюдает его права в текущем рабочем
    пространстве.

    Токен создаётся в Kooly в разделе «Профиль → Интеграции → API-токены».
    Не передавайте токен в query-параметрах и не публикуйте его в исходном коде.
  contact:
    name: Поддержка Kooly
    url: https://kooly.ru
servers:
  - url: https://api.kooly.ru/api/v1/private
    description: Production
tags:
  - name: Account
    description: Текущий пользователь и участники рабочего пространства
  - name: Contacts
    description: Контакты рабочего пространства
  - name: Projects
    description: Проекты и задачи проекта
  - name: Boards
    description: Доски, стадии и канбан
  - name: Tasks
    description: Личные и назначенные задачи
  - name: Comments
    description: Комментарии к задачам
security:
  - bearerAuth: []
paths:
  /me:
    get:
      tags: [Account]
      operationId: getCurrentUser
      summary: Получить текущего пользователя
      responses:
        "200":
          description: Пользователь и выбранное рабочее пространство
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CurrentUser"
        "401":
          $ref: "#/components/responses/Unauthorized"

  /members:
    get:
      tags: [Account]
      operationId: listWorkspaceMembers
      summary: Список участников рабочего пространства
      responses:
        "200":
          description: Участники текущего рабочего пространства
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Member"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"

  /contacts:
    get:
      tags: [Contacts]
      operationId: listContacts
      summary: Получить контакты
      parameters:
        - name: search
          in: query
          description: Поиск по имени, email, телефону и компании
          schema:
            type: string
        - name: client
          in: query
          description: Фильтр по названию компании
          schema:
            type: string
        - name: sort
          in: query
          schema:
            type: string
            enum: [first_name, last_name, email, client_name, source, created_at]
            default: created_at
        - name: dir
          in: query
          schema:
            type: string
            enum: [asc, desc]
            default: desc
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
      responses:
        "200":
          description: Страница контактов
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedContacts"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    post:
      tags: [Contacts]
      operationId: createContact
      summary: Создать контакт
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ContactInput"
            example:
              first_name: Анна
              last_name: Петрова
              email: anna@example.com
              phone: "+79990000000"
              position: Директор по маркетингу
              client_name: Example
      responses:
        "201":
          description: Контакт создан
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"

  /contacts/{contact}:
    parameters:
      - $ref: "#/components/parameters/ContactId"
    get:
      tags: [Contacts]
      operationId: getContact
      summary: Получить контакт
      responses:
        "200":
          description: Контакт
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      tags: [Contacts]
      operationId: updateContact
      summary: Изменить контакт
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ContactPatch"
      responses:
        "200":
          description: Контакт изменён
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"
    delete:
      tags: [Contacts]
      operationId: deleteContact
      summary: Удалить контакт
      responses:
        "204":
          description: Контакт удалён
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /projects:
    get:
      tags: [Projects]
      operationId: listProjects
      summary: Получить проекты
      parameters:
        - name: archived
          in: query
          schema:
            type: boolean
            default: false
        - name: status
          in: query
          schema:
            $ref: "#/components/schemas/ProjectStatus"
        - name: overdue
          in: query
          schema:
            type: boolean
            default: false
      responses:
        "200":
          description: Проекты
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Project"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    post:
      tags: [Projects]
      operationId: createProject
      summary: Создать проект
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProjectInput"
            example:
              name: Запуск нового сайта
              description: Проект команды маркетинга
              status: active
              start_date: "2026-08-01"
              due_date: "2026-09-15"
      responses:
        "201":
          description: Проект создан
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProjectResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"

  /projects/{project}:
    parameters:
      - $ref: "#/components/parameters/ProjectId"
    get:
      tags: [Projects]
      operationId: getProject
      summary: Получить проект
      responses:
        "200":
          description: Проект
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProjectResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
    patch:
      tags: [Projects]
      operationId: updateProject
      summary: Изменить проект
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProjectPatch"
      responses:
        "200":
          description: Проект изменён
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ProjectResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"
    delete:
      tags: [Projects]
      operationId: deleteProject
      summary: Удалить проект
      responses:
        "204":
          description: Проект удалён
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /projects/{project}/tasks:
    parameters:
      - $ref: "#/components/parameters/ProjectId"
    get:
      tags: [Projects]
      operationId: listProjectTasks
      summary: Получить задачи проекта
      description: Возвращает плоский список. Иерархия задач задаётся полем `parent_id`.
      responses:
        "200":
          description: Задачи и связи блокировки
          content:
            application/json:
              schema:
                type: object
                required: [data, links]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Task"
                  links:
                    type: array
                    items:
                      type: object
                      required: [source, target]
                      properties:
                        source:
                          type: integer
                        target:
                          type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      tags: [Projects]
      operationId: createProjectTask
      summary: Создать задачу проекта
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ProjectTaskInput"
            example:
              name: Подготовить структуру лендинга
              priority: high
              expected_close_date: "2026-08-07"
              participant_ids: [12, 18]
      responses:
        "201":
          description: Задача создана
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"

  /boards:
    get:
      tags: [Boards]
      operationId: listBoards
      summary: Получить доступные доски
      responses:
        "200":
          description: Доски, участником которых является пользователь
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Board"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    post:
      tags: [Boards]
      operationId: createBoard
      summary: Создать доску
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BoardInput"
      responses:
        "201":
          description: Доска создана
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: "#/components/schemas/Board"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"

  /boards/{board}:
    parameters:
      - $ref: "#/components/parameters/BoardId"
    patch:
      tags: [Boards]
      operationId: updateBoard
      summary: Изменить доску
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BoardPatch"
      responses:
        "200":
          description: Доска изменена
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: "#/components/schemas/Board"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"
    delete:
      tags: [Boards]
      operationId: deleteBoard
      summary: Удалить доску
      responses:
        "204":
          description: Доска удалена
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /boards/{board}/stages:
    parameters:
      - $ref: "#/components/parameters/BoardId"
    get:
      tags: [Boards]
      operationId: listBoardStages
      summary: Получить стадии доски
      responses:
        "200":
          description: Стадии в порядке отображения
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Stage"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      tags: [Boards]
      operationId: createBoardStage
      summary: Создать стадию
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                  maxLength: 255
                color:
                  $ref: "#/components/schemas/Color"
      responses:
        "201":
          description: Стадия создана
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: "#/components/schemas/Stage"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"

  /boards/{board}/kanban:
    parameters:
      - $ref: "#/components/parameters/BoardId"
    get:
      tags: [Boards]
      operationId: getBoardKanban
      summary: Получить канбан
      description: Возвращает стадии вместе с активными задачами каждой стадии.
      responses:
        "200":
          description: Канбан доски
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      allOf:
                        - $ref: "#/components/schemas/Stage"
                        - type: object
                          required: [deals]
                          properties:
                            deals:
                              type: array
                              items:
                                $ref: "#/components/schemas/Task"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /my-tasks:
    get:
      tags: [Tasks]
      operationId: listMyTasks
      summary: Получить личные или назначенные задачи
      parameters:
        - name: tab
          in: query
          schema:
            type: string
            enum: [my, assigned]
            default: my
        - name: per_page
          in: query
          description: Размер страницы завершённых задач
          schema:
            type: integer
            minimum: 1
            default: 20
        - name: completed_page
          in: query
          schema:
            type: integer
            minimum: 1
            default: 1
      responses:
        "200":
          description: Активные и завершённые задачи
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MyTasksResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
    post:
      tags: [Tasks]
      operationId: createMyTask
      summary: Создать личную задачу
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MyTaskInput"
            example:
              name: Позвонить поставщику
              priority: medium
              expected_close_date: "2026-08-03"
              expected_end_time: "14:30"
              participant_ids: [12]
      responses:
        "201":
          description: Задача создана
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TaskResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"

  /my-tasks/search:
    get:
      tags: [Tasks]
      operationId: searchTasks
      summary: Найти доступные задачи
      parameters:
        - name: q
          in: query
          required: true
          schema:
            type: string
            minLength: 1
        - name: tab
          in: query
          schema:
            type: string
            enum: [my, assigned]
            default: my
        - name: scope
          in: query
          description: Значение `accessible` ищет среди всех доступных задач
          schema:
            type: string
            enum: [accessible]
      responses:
        "200":
          description: До 50 найденных задач
          content:
            application/json:
              schema:
                type: object
                required: [data, counts]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Task"
                  counts:
                    type: object
                    required: [my, assigned]
                    properties:
                      my:
                        type: integer
                      assigned:
                        type: integer
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"

  /deals:
    post:
      tags: [Tasks]
      operationId: createStandaloneTask
      summary: Создать автономную задачу
      description: |
        Создаёт задачу вне доски. Для задачи внутри проекта используйте
        `POST /projects/{project}/tasks`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/StandaloneTaskInput"
      responses:
        "201":
          description: Задача создана
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DetailedTaskResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "422":
          $ref: "#/components/responses/ValidationError"

  /deals/{deal}:
    parameters:
      - $ref: "#/components/parameters/DealUuid"
    get:
      tags: [Tasks]
      operationId: getTask
      summary: Получить задачу
      responses:
        "200":
          description: Задача и разрешённые действия
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DetailedTaskResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
    put:
      tags: [Tasks]
      operationId: updateTask
      summary: Изменить задачу
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/TaskPatch"
      responses:
        "200":
          description: Задача изменена
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DetailedTaskResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"
    delete:
      tags: [Tasks]
      operationId: deleteTask
      summary: Удалить задачу
      responses:
        "204":
          description: Задача удалена
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /deals/{deal}/toggle:
    parameters:
      - $ref: "#/components/parameters/DealUuid"
    post:
      tags: [Tasks]
      operationId: toggleTaskCompletion
      summary: Переключить статус выполнения
      responses:
        "200":
          description: Задача с новым статусом
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DetailedTaskResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

  /deals/{deal}/participants:
    parameters:
      - $ref: "#/components/parameters/DealUuid"
    put:
      tags: [Tasks]
      operationId: replaceTaskParticipants
      summary: Заменить участников задачи
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [user_ids]
              properties:
                user_ids:
                  type: array
                  items:
                    type: integer
      responses:
        "200":
          description: Обновлённые участники
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/MemberSummary"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"

  /boards/{board}/deals/{deal}/move:
    parameters:
      - $ref: "#/components/parameters/BoardId"
      - $ref: "#/components/parameters/DealUuid"
    patch:
      tags: [Tasks]
      operationId: moveTask
      summary: Переместить задачу на другую стадию
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [stage_id, position]
              properties:
                stage_id:
                  type: integer
                position:
                  type: number
      responses:
        "200":
          description: Результат перемещения
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"

  /deals/{deal}/comments:
    parameters:
      - $ref: "#/components/parameters/DealUuid"
    get:
      tags: [Comments]
      operationId: listTaskComments
      summary: Получить комментарии
      responses:
        "200":
          description: Корневые комментарии с ответами
          content:
            application/json:
              schema:
                type: object
                required: [data, has_unread_mention]
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Comment"
                  has_unread_mention:
                    type: boolean
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
    post:
      tags: [Comments]
      operationId: createTaskComment
      summary: Добавить комментарий
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CommentInput"
      responses:
        "201":
          description: Комментарий создан
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: "#/components/schemas/Comment"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"

  /deals/{deal}/comments/{comment}:
    parameters:
      - $ref: "#/components/parameters/DealUuid"
      - $ref: "#/components/parameters/CommentId"
    put:
      tags: [Comments]
      operationId: updateTaskComment
      summary: Изменить свой комментарий
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [body]
              properties:
                body:
                  type: string
      responses:
        "200":
          description: Комментарий изменён
          content:
            application/json:
              schema:
                type: object
                required: [data]
                properties:
                  data:
                    $ref: "#/components/schemas/Comment"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/ValidationError"
    delete:
      tags: [Comments]
      operationId: deleteTaskComment
      summary: Удалить свой комментарий
      responses:
        "204":
          description: Комментарий удалён
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: Personal API token
      description: Персональный токен из раздела «Профиль → Интеграции».

  parameters:
    Page:
      name: page
      in: query
      schema:
        type: integer
        minimum: 1
        default: 1
    PerPage:
      name: per_page
      in: query
      schema:
        type: integer
        minimum: 1
        default: 20
    ContactId:
      name: contact
      in: path
      required: true
      schema:
        type: integer
    ProjectId:
      name: project
      in: path
      required: true
      schema:
        type: integer
    BoardId:
      name: board
      in: path
      required: true
      schema:
        type: integer
    DealUuid:
      name: deal
      in: path
      required: true
      description: UUID задачи, не числовой `id`
      schema:
        type: string
        format: uuid
    CommentId:
      name: comment
      in: path
      required: true
      schema:
        type: integer

  responses:
    Unauthorized:
      description: Токен отсутствует, истёк или отозван
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            message: Unauthenticated.
    Forbidden:
      description: У пользователя нет доступа к рабочему пространству или ресурсу
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            message: У вас нет доступа к выбранной области.
    NotFound:
      description: Ресурс не найден или недоступен пользователю
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            message: Not Found
    ValidationError:
      description: Ошибка валидации данных
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ValidationError"
          example:
            message: Поле name обязательно для заполнения.
            errors:
              name:
                - Поле name обязательно для заполнения.

  schemas:
    Error:
      type: object
      required: [message]
      properties:
        message:
          type: string

    ValidationError:
      allOf:
        - $ref: "#/components/schemas/Error"
        - type: object
          required: [errors]
          properties:
            errors:
              type: object
              additionalProperties:
                type: array
                items:
                  type: string

    Color:
      type:
        - string
        - "null"
      pattern: "^#[0-9a-fA-F]{6}$"
      example: "#4ECBA0"

    Priority:
      type: string
      enum: [none, low, medium, high, urgent]
      default: none

    ProjectStatus:
      type: string
      enum: [active, on_hold, completed]

    MemberSummary:
      type: object
      required: [id, name]
      properties:
        id:
          type: integer
        name:
          type: string
        avatar_url:
          type:
            - string
            - "null"
          format: uri

    Member:
      allOf:
        - $ref: "#/components/schemas/MemberSummary"
        - type: object
          properties:
            email:
              type: string
              format: email
            role:
              type: string

    Workspace:
      type: object
      required: [id, name, slug]
      properties:
        id:
          type: integer
        name:
          type: string
        slug:
          type: string
        color:
          $ref: "#/components/schemas/Color"
        members_count:
          type: integer

    CurrentUser:
      type: object
      required: [id, name, email, email_verified, tenants, plugins]
      properties:
        id:
          type: integer
        name:
          type: string
        email:
          type: string
          format: email
        phone:
          type:
            - string
            - "null"
        position:
          type:
            - string
            - "null"
        avatar_url:
          type:
            - string
            - "null"
          format: uri
        timezone:
          type: string
          example: Europe/Moscow
        email_verified:
          type: boolean
        tenant:
          oneOf:
            - $ref: "#/components/schemas/Workspace"
            - type: "null"
        tenants:
          type: array
          items:
            $ref: "#/components/schemas/Workspace"
        tenant_role:
          type:
            - string
            - "null"
        plugins:
          type: array
          items:
            type: string

    LabeledValue:
      type: object
      required: [value, label]
      properties:
        value:
          type: string
        label:
          type: string
          maxLength: 50

    SocialLink:
      type: object
      required: [platform, value]
      properties:
        platform:
          type: string
          maxLength: 50
        value:
          type: string
          maxLength: 255

    ContactFields:
      type: object
      properties:
        first_name:
          type: string
          maxLength: 255
        last_name:
          type:
            - string
            - "null"
          maxLength: 255
        email:
          type:
            - string
            - "null"
          format: email
        phone:
          type:
            - string
            - "null"
          maxLength: 50
        client_name:
          type:
            - string
            - "null"
          maxLength: 255
        client_id:
          type:
            - integer
            - "null"
        position:
          type:
            - string
            - "null"
          maxLength: 255
        notes:
          type:
            - string
            - "null"
        phones:
          type: array
          items:
            $ref: "#/components/schemas/LabeledValue"
        emails:
          type: array
          items:
            $ref: "#/components/schemas/LabeledValue"
        birthday:
          type:
            - string
            - "null"
          format: date
        source:
          type:
            - string
            - "null"
          maxLength: 100
        address:
          type:
            - string
            - "null"
        social_links:
          type: array
          items:
            $ref: "#/components/schemas/SocialLink"

    ContactInput:
      allOf:
        - $ref: "#/components/schemas/ContactFields"
        - type: object
          required: [first_name]

    ContactPatch:
      $ref: "#/components/schemas/ContactFields"

    Contact:
      allOf:
        - $ref: "#/components/schemas/ContactFields"
        - type: object
          required: [id, first_name, created_at, updated_at]
          properties:
            id:
              type: integer
            last_contacted_at:
              type:
                - string
                - "null"
              format: date-time
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time

    ContactResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Contact"

    PaginationLinks:
      type: object
      properties:
        first:
          type:
            - string
            - "null"
          format: uri
        last:
          type:
            - string
            - "null"
          format: uri
        prev:
          type:
            - string
            - "null"
          format: uri
        next:
          type:
            - string
            - "null"
          format: uri

    PaginationMeta:
      type: object
      required: [current_page, from, last_page, path, per_page, to, total]
      properties:
        current_page:
          type: integer
        from:
          type:
            - integer
            - "null"
        last_page:
          type: integer
        path:
          type: string
          format: uri
        per_page:
          type: integer
        to:
          type:
            - integer
            - "null"
        total:
          type: integer

    PaginatedContacts:
      type: object
      required: [data, links, meta]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Contact"
        links:
          $ref: "#/components/schemas/PaginationLinks"
        meta:
          $ref: "#/components/schemas/PaginationMeta"

    ProjectMemberInput:
      type: object
      required: [id]
      properties:
        id:
          type: integer
        role:
          type: string
          enum: [owner, member]

    ProjectFields:
      type: object
      properties:
        name:
          type: string
          maxLength: 255
        description:
          type:
            - string
            - "null"
        client_id:
          type:
            - integer
            - "null"
        icon:
          type:
            - string
            - "null"
          maxLength: 50
        color:
          $ref: "#/components/schemas/Color"
        background:
          type:
            - string
            - "null"
          maxLength: 255
        status:
          $ref: "#/components/schemas/ProjectStatus"
        start_date:
          type:
            - string
            - "null"
          format: date
        due_date:
          type:
            - string
            - "null"
          format: date
        members:
          type: array
          items:
            $ref: "#/components/schemas/ProjectMemberInput"

    ProjectInput:
      allOf:
        - $ref: "#/components/schemas/ProjectFields"
        - type: object
          required: [name]

    ProjectPatch:
      allOf:
        - $ref: "#/components/schemas/ProjectFields"
        - type: object
          properties:
            archived:
              type: boolean
            settings:
              type: object
              properties:
                board_ids:
                  type: array
                  items:
                    type: integer
                working_days:
                  type: array
                  items:
                    type: integer
                    minimum: 0
                    maximum: 6
                holidays:
                  type: array
                  items:
                    type: string
                    format: date

    Project:
      allOf:
        - $ref: "#/components/schemas/ProjectFields"
        - type: object
          required: [id, name, status, overdue, position, archived]
          properties:
            id:
              type: integer
            overdue:
              type: boolean
            position:
              type: integer
            created_by:
              type: integer
            archived:
              type: boolean
            member_ids:
              type: array
              items:
                type: integer
            stats:
              type: object
              properties:
                total:
                  type: integer
                done:
                  type: integer
                in_progress:
                  type: integer
                not_started:
                  type: integer
                overdue:
                  type: integer

    ProjectResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Project"

    BoardFields:
      type: object
      properties:
        name:
          type: string
          maxLength: 255
        icon:
          type:
            - string
            - "null"
          maxLength: 50
        color:
          $ref: "#/components/schemas/Color"
        background:
          type:
            - string
            - "null"
          maxLength: 255

    BoardInput:
      allOf:
        - $ref: "#/components/schemas/BoardFields"
        - type: object
          required: [name]

    BoardPatch:
      $ref: "#/components/schemas/BoardFields"

    Board:
      allOf:
        - $ref: "#/components/schemas/BoardFields"
        - type: object
          required: [id, name, position]
          properties:
            id:
              type: integer
            position:
              type: integer

    StageSummary:
      type: object
      required: [id, name]
      properties:
        id:
          type: integer
        name:
          type: string
        color:
          $ref: "#/components/schemas/Color"

    Stage:
      allOf:
        - $ref: "#/components/schemas/StageSummary"
        - type: object
          required: [position]
          properties:
            position:
              type: number
            allow_create:
              type: boolean

    ProjectTaskInput:
      type: object
      required: [name]
      properties:
        name:
          type: string
          maxLength: 255
        parent_id:
          type:
            - integer
            - "null"
        priority:
          $ref: "#/components/schemas/Priority"
        expected_close_date:
          type:
            - string
            - "null"
          format: date
        participant_ids:
          type: array
          items:
            type: integer

    MyTaskInput:
      type: object
      required: [name]
      properties:
        name:
          type: string
          maxLength: 255
        type:
          type: string
          enum: [task, conference]
          default: task
        expected_close_date:
          type:
            - string
            - "null"
          format: date
        expected_end_time:
          type:
            - string
            - "null"
          pattern: "^\\d{2}:\\d{2}$"
        priority:
          $ref: "#/components/schemas/Priority"
        participant_ids:
          type: array
          items:
            type: integer
        description:
          type:
            - string
            - "null"
        auto_record:
          type: boolean

    StandaloneTaskInput:
      type: object
      required: [name]
      properties:
        name:
          type: string
          maxLength: 255
        contact_id:
          type:
            - integer
            - "null"
        stage_id:
          type:
            - integer
            - "null"
        value:
          type:
            - number
            - "null"
          minimum: 0
        currency:
          type:
            - string
            - "null"
          minLength: 3
          maxLength: 3
        priority:
          $ref: "#/components/schemas/Priority"
        expected_close_date:
          type:
            - string
            - "null"
          format: date
        parent_id:
          type:
            - integer
            - "null"

    TaskPatch:
      type: object
      properties:
        board_id:
          type: integer
        contact_id:
          type:
            - integer
            - "null"
        client_id:
          type:
            - integer
            - "null"
        stage_id:
          type:
            - integer
            - "null"
        name:
          type: string
          maxLength: 255
        description:
          type:
            - string
            - "null"
        value:
          type:
            - number
            - "null"
          minimum: 0
        currency:
          type:
            - string
            - "null"
          minLength: 3
          maxLength: 3
        priority:
          $ref: "#/components/schemas/Priority"
        expected_close_date:
          type:
            - string
            - "null"
          format: date
        expected_end_time:
          type:
            - string
            - "null"
          pattern: "^\\d{2}:\\d{2}$"
        planned_start_date:
          type:
            - string
            - "null"
          format: date
        planned_end_date:
          type:
            - string
            - "null"
          format: date
        completed_at:
          type:
            - string
            - "null"
          format: date-time
        parent_id:
          type:
            - integer
            - "null"

    Task:
      type: object
      required: [id, uuid, name, priority]
      properties:
        id:
          type: integer
        uuid:
          type: string
          format: uuid
        number:
          type:
            - integer
            - "null"
        name:
          type: string
        description:
          type:
            - string
            - "null"
        board_id:
          type:
            - integer
            - "null"
        project_id:
          type:
            - integer
            - "null"
        parent_id:
          type:
            - integer
            - "null"
        priority:
          $ref: "#/components/schemas/Priority"
        expected_close_date:
          type:
            - string
            - "null"
          format: date-time
        expected_end_time:
          type:
            - string
            - "null"
        planned_start_date:
          type:
            - string
            - "null"
          format: date
        planned_end_date:
          type:
            - string
            - "null"
          format: date
        completed_at:
          type:
            - string
            - "null"
          format: date-time
        stage:
          oneOf:
            - $ref: "#/components/schemas/StageSummary"
            - type: "null"
        participants:
          type: array
          items:
            $ref: "#/components/schemas/MemberSummary"
        children_count:
          type: integer
        completed_children_count:
          type: integer
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    DetailedTask:
      allOf:
        - $ref: "#/components/schemas/Task"
        - type: object
          properties:
            value:
              type:
                - number
                - "null"
            currency:
              type:
                - string
                - "null"
            visibility:
              type: string
            is_subscribed:
              type: boolean
            tags:
              type: array
              items:
                type: object
                required: [id, name]
                properties:
                  id:
                    type: integer
                  name:
                    type: string
                  color:
                    $ref: "#/components/schemas/Color"

    TaskResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/Task"

    DetailedTaskResponse:
      type: object
      required: [data]
      properties:
        data:
          $ref: "#/components/schemas/DetailedTask"
        permissions:
          type: object
          additionalProperties:
            type: boolean

    MyTasksResponse:
      type: object
      required: [data, running_timer_deal_ids, completed]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/Task"
        running_timer_deal_ids:
          type: array
          items:
            type: integer
        completed:
          type: object
          required: [data, current_page, last_page, total]
          properties:
            data:
              type: array
              items:
                $ref: "#/components/schemas/Task"
            current_page:
              type: integer
            last_page:
              type: integer
            total:
              type: integer

    CommentInput:
      type: object
      required: [body]
      properties:
        body:
          type: string
        parent_id:
          type:
            - integer
            - "null"

    Comment:
      allOf:
        - $ref: "#/components/schemas/CommentInput"
        - type: object
          required: [id, user, replies, created_at, updated_at]
          properties:
            id:
              type: integer
            user:
              $ref: "#/components/schemas/MemberSummary"
            replies:
              type: array
              items:
                $ref: "#/components/schemas/Comment"
            created_at:
              type: string
              format: date-time
            updated_at:
              type: string
              format: date-time
