{
  "openapi": "3.1.0",
  "info": {
    "title": "trololo API",
    "version": "1.0.0",
    "summary": "Kanban-доски, списки, карточки и всё, что на них висит.",
    "description": "Публичный HTTP API проекта trololo.\n\n**Аутентификация.** Только персональный токен доступа (PAT) в заголовке\n`Authorization: Bearer trl_pat_…`. Куки-сессии на `/api/v1/*` не\nпринимаются, поэтому CSRF-защита здесь не нужна.\n\n**Идентификаторы** — UUID v7 в виде строки. Целочисленных id из Django в\nAPI нет.\n\n**Даты** — RFC 3339 в UTC с суффиксом `Z`.\n\n**Позиция** (`position`) — строка дробного ранга base62 (`\"V\"`, `\"aZ9\"`).\nКлиент её не разбирает: чтобы переместить сущность, он присылает соседей\n(`after_id` / `before_id`), а не позицию.\n\n**Ошибки** — RFC 9457 `application/problem+json`.\n\n**Списки** — курсорная пагинация `?limit=50&cursor=…`, ответ\n`{items: [...], next_cursor: string|null}`. Максимум `limit` — 200.\n\n**Конкурентность** — `ETag` на доске, списке и карточке; условная запись\nчерез `If-Match`. Несовпадение → `412`. Занятая позиция → `409`.\n\n**Скрытие существования.** «Не найдено» и «нет доступа» — одинаковый `404`,\nчтобы перебор идентификаторов не выдавал чужие ресурсы.\n",
    "contact": {
      "name": "trololo",
      "url": "https://trololo.telco-system.com/"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "/api/v1",
      "description": "Текущий хост, префикс версии 1"
    }
  ],
  "externalDocs": {
    "description": "Контракт API (источник правды)",
    "url": "https://trololo.telco-system.com/docs"
  },
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Мета",
      "description": "Кто я, поиск пользователей, документация."
    },
    {
      "name": "Рабочие пространства",
      "description": "Контейнер для досок; правами управляют владелец и админы."
    },
    {
      "name": "Доски",
      "description": "Доски, их участники и метки."
    },
    {
      "name": "Списки",
      "description": "Колонки доски и массовые операции над их карточками."
    },
    {
      "name": "Карточки",
      "description": "Поиск, создание, перемещение и содержимое карточек."
    },
    {
      "name": "Чек-листы",
      "description": "Чек-листы карточки и пункты внутри них."
    },
    {
      "name": "Комментарии",
      "description": "Обсуждение на карточке; Markdown рендерится на выдаче."
    },
    {
      "name": "Вложения",
      "description": "Файлы карточки; загрузка multipart, скачивание через API."
    },
    {
      "name": "Уведомления",
      "description": "Личный ящик вызывающего пользователя."
    },
    {
      "name": "Живые обновления",
      "description": "Поток изменений доски по Server-Sent Events."
    },
    {
      "name": "Документация",
      "description": "Спецификация и Swagger UI; доступны без токена."
    }
  ],
  "paths": {
    "/me": {
      "get": {
        "tags": [
          "Мета"
        ],
        "operationId": "getMe",
        "summary": "Кто я и что может токен",
        "description": "Первый вызов любой интеграции: возвращает владельца токена и список его\nскоупов. Для куки-сессии (HTML-маршруты) `auth` равен `session`, а в\n`scopes` приходит полный набор.\n",
        "x-required-scopes": [],
        "responses": {
          "200": {
            "description": "Владелец токена и его права.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                },
                "examples": {
                  "token": {
                    "summary": "Вызов по токену",
                    "value": {
                      "user": {
                        "id": "01a044b6-c390-7b83-9ce9-8303881bc906",
                        "username": "dare",
                        "display_name": "Дарья Лебедева",
                        "initials": "ДЛ",
                        "avatar_url": "https://trololo.telco-system.com/media/avatars/dare.png"
                      },
                      "scopes": [
                        "boards:read",
                        "cards:read",
                        "cards:write"
                      ],
                      "auth": "token"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/users/search": {
      "get": {
        "tags": [
          "Мета"
        ],
        "operationId": "searchUsers",
        "summary": "Поиск пользователей по префиксу",
        "description": "Подбор участника для доски или карточки. Совпадение ищется по **началу**\n`username`, имени или фамилии — инфиксный поиск не поддерживается\nспециально, чтобы эндпоинт нельзя было использовать для перебора всей\nтаблицы пользователей. Возвращает не более 10 записей **массивом**, без\nконверта `Page`.\n",
        "x-required-scopes": [
          "profile:read"
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Префикс, минимум 2 символа.",
            "schema": {
              "type": "string",
              "minLength": 2
            },
            "example": "да"
          }
        ],
        "responses": {
          "200": {
            "description": "До 10 совпадений, упорядоченных по `username`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "maxItems": 10,
                  "items": {
                    "$ref": "#/components/schemas/UserRef"
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      }
    },
    "/workspaces": {
      "get": {
        "tags": [
          "Рабочие пространства"
        ],
        "operationId": "listWorkspaces",
        "summary": "Пространства, доступные вызывающему",
        "x-required-scopes": [
          "boards:read"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Страница пространств.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageOfWorkspace"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Рабочие пространства"
        ],
        "operationId": "createWorkspace",
        "summary": "Создать пространство",
        "description": "Автор становится владельцем и админом пространства.",
        "x-required-scopes": [
          "boards:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkspaceCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Пространство создано.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workspace"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/workspaces/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Рабочие пространства"
        ],
        "operationId": "getWorkspace",
        "summary": "Одно пространство",
        "x-required-scopes": [
          "boards:read"
        ],
        "responses": {
          "200": {
            "description": "Пространство.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workspace"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Рабочие пространства"
        ],
        "operationId": "updateWorkspace",
        "summary": "Переименовать пространство",
        "description": "Доступно владельцу и админам пространства.",
        "x-required-scopes": [
          "boards:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkspacePatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Обновлённое пространство.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Workspace"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "tags": [
          "Рабочие пространства"
        ],
        "operationId": "deleteWorkspace",
        "summary": "Удалить пространство",
        "description": "Только владелец и только когда в пространстве не осталось досок —\nиначе `409`.\n",
        "x-required-scopes": [
          "boards:write"
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/workspaces/{id}/members": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Рабочие пространства"
        ],
        "operationId": "listWorkspaceMembers",
        "summary": "Участники пространства",
        "x-required-scopes": [
          "boards:read"
        ],
        "responses": {
          "200": {
            "description": "Страница участников; `next_cursor` всегда `null`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageOfBoardMember"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Рабочие пространства"
        ],
        "operationId": "addWorkspaceMember",
        "summary": "Добавить участника пространства",
        "description": "Роль по умолчанию — `member`. Управляют владелец и админы.",
        "x-required-scopes": [
          "boards:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkspaceMemberCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Участник добавлен.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BoardMember"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/workspaces/{id}/members/{user_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        },
        {
          "$ref": "#/components/parameters/PathUserID"
        }
      ],
      "delete": {
        "tags": [
          "Рабочие пространства"
        ],
        "operationId": "removeWorkspaceMember",
        "summary": "Убрать участника пространства",
        "description": "Выйти самому можно без прав админа; убрать другого — только владельцу\nили админу. Владельца убрать нельзя (`409`).\n",
        "x-required-scopes": [
          "boards:write"
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/boards": {
      "get": {
        "tags": [
          "Доски"
        ],
        "operationId": "listBoards",
        "summary": "Доски, доступные вызывающему",
        "description": "Возвращает доски, где пользователь состоит участником, владеет\nпространством или которые открыты его пространству либо всем.\n",
        "x-required-scopes": [
          "boards:read"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Страница досок.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageOfBoard"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Доски"
        ],
        "operationId": "createBoard",
        "summary": "Создать доску",
        "description": "Членство в целевом пространстве проверяется внутри той же транзакции,\nчто и вставка. Автор становится админом доски.\n",
        "x-required-scopes": [
          "boards:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BoardCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Доска создана; в заголовке `ETag` — валидатор для `If-Match`.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Board"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/boards/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Доски"
        ],
        "operationId": "getBoard",
        "summary": "Доска целиком (BoardFull)",
        "description": "Всё, что нужно для отрисовки доски, одним запросом: сама доска, её\nметки, участники и живые списки. Карточки запрашиваются отдельно —\n`GET /lists/{id}/cards` или `GET /cards?board_id=…`.\n",
        "x-required-scopes": [
          "boards:read"
        ],
        "responses": {
          "200": {
            "description": "Доска со списками, метками и участниками.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BoardFull"
                },
                "examples": {
                  "board": {
                    "summary": "Доска с одной колонкой",
                    "value": {
                      "id": "01a044b6-c390-7b83-9ce9-8303881bc906",
                      "workspace_id": "01a044b6-c390-7b83-9ce9-8303881bc900",
                      "name": "Разработка",
                      "description": "Основная доска команды",
                      "background_color": "#0079BF",
                      "background_url": null,
                      "visibility": "workspace",
                      "is_closed": false,
                      "position": "V",
                      "lists_count": 1,
                      "cards_count": 2,
                      "my_role": "admin",
                      "created_at": "2026-08-01T09:00:00Z",
                      "updated_at": "2026-08-27T18:12:03Z",
                      "labels": [
                        {
                          "id": "01a044b6-c390-7b83-9ce9-8303881bc910",
                          "board_id": "01a044b6-c390-7b83-9ce9-8303881bc906",
                          "name": "Срочно",
                          "color": "red"
                        }
                      ],
                      "members": [
                        {
                          "id": "01a044b6-c390-7b83-9ce9-8303881bc920",
                          "user": {
                            "id": "01a044b6-c390-7b83-9ce9-8303881bc9a1",
                            "username": "dare",
                            "display_name": "Дарья Лебедева",
                            "initials": "ДЛ",
                            "avatar_url": null
                          },
                          "role": "admin",
                          "joined_at": "2026-08-01T09:00:00Z"
                        }
                      ],
                      "lists": [
                        {
                          "id": "01a044b6-c390-7b83-9ce9-8303881bc930",
                          "board_id": "01a044b6-c390-7b83-9ce9-8303881bc906",
                          "name": "В работе",
                          "color": "",
                          "position": "V",
                          "is_archived": false,
                          "cards_count": 2,
                          "created_at": "2026-08-01T09:01:00Z"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Доски"
        ],
        "operationId": "updateBoard",
        "summary": "Изменить настройки доски",
        "description": "Настройки меняют только админы доски. Если прислан `If-Match` и он\nразошёлся с текущим `ETag` — `412`, запись не выполняется.\n",
        "x-required-scopes": [
          "boards:write"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BoardPatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Обновлённая доска.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Board"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "tags": [
          "Доски"
        ],
        "operationId": "deleteBoard",
        "summary": "Удалить доску",
        "description": "Необратимо: списки, карточки и всё под ними уходят каскадом. Только\nадмины доски. Обратимая операция — `POST /boards/{id}/archive`.\n",
        "x-required-scopes": [
          "boards:write"
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/boards/{id}/archive": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "post": {
        "tags": [
          "Доски"
        ],
        "operationId": "archiveBoard",
        "summary": "Закрыть или снова открыть доску",
        "description": "Переключатель `is_closed`. Закрытая доска доступна только на чтение,\nпоэтому право на переключение — админское, а не «право на правку».\n",
        "x-required-scopes": [
          "boards:write"
        ],
        "responses": {
          "200": {
            "description": "Доска после переключения.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Board"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/boards/{id}/members": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Доски"
        ],
        "operationId": "listBoardMembers",
        "summary": "Участники доски",
        "x-required-scopes": [
          "boards:read"
        ],
        "responses": {
          "200": {
            "description": "Страница участников; `next_cursor` всегда `null`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageOfBoardMember"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Доски"
        ],
        "operationId": "addBoardMember",
        "summary": "Добавить участника доски",
        "description": "Приглашаемого называют либо `user_id`, либо `username`. Роль по\nумолчанию — `normal`. Управляют участием только админы доски.\n",
        "x-required-scopes": [
          "boards:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BoardMemberCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Участник добавлен.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BoardMember"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/boards/{id}/members/{user_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        },
        {
          "$ref": "#/components/parameters/PathUserID"
        }
      ],
      "patch": {
        "tags": [
          "Доски"
        ],
        "operationId": "updateBoardMemberRole",
        "summary": "Сменить роль участника доски",
        "description": "Меняют роль только админы доски. У доски не может остаться ноль\nадминистраторов: понижение последнего `admin` — включая понижение\nсамого себя — отвечает `409`.\n\nВладелец рабочего пространства — исключение: его права идут из\n`workspaces.owner_id`, а не из строки `board_members`, поэтому его\nроль на доске меняется свободно и доступа он не теряет.\n",
        "x-required-scopes": [
          "boards:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BoardMemberPatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Роль изменена.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BoardMember"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "tags": [
          "Доски"
        ],
        "operationId": "removeBoardMember",
        "summary": "Убрать участника доски",
        "description": "Выйти самому можно без прав админа; убрать другого — нельзя.\nПоследнего администратора доски убрать не даст `409` — как и понижение\nего роли.\n",
        "x-required-scopes": [
          "boards:write"
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          }
        }
      }
    },
    "/boards/{id}/labels": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Доски"
        ],
        "operationId": "listLabels",
        "summary": "Метки доски",
        "x-required-scopes": [
          "boards:read"
        ],
        "responses": {
          "200": {
            "description": "Страница меток; `next_cursor` всегда `null`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageOfLabel"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Доски"
        ],
        "operationId": "createLabel",
        "summary": "Создать метку",
        "description": "Пара «цвет + имя» уникальна в пределах доски: повтор — `409`, а не\nмолчаливый дубликат.\n",
        "x-required-scopes": [
          "boards:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LabelCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Метка создана.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Label"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/labels/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Доски"
        ],
        "operationId": "getLabel",
        "summary": "Одна метка",
        "x-required-scopes": [
          "boards:read"
        ],
        "responses": {
          "200": {
            "description": "Метка.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Label"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Доски"
        ],
        "operationId": "updateLabel",
        "summary": "Изменить метку",
        "x-required-scopes": [
          "boards:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LabelPatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Обновлённая метка.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Label"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "tags": [
          "Доски"
        ],
        "operationId": "deleteLabel",
        "summary": "Удалить метку",
        "description": "Связи `card_labels` уходят каскадом.",
        "x-required-scopes": [
          "boards:write"
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/boards/{id}/lists": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Списки"
        ],
        "operationId": "listBoardLists",
        "summary": "Колонки доски",
        "description": "Набор ограничен доской, поэтому `next_cursor` всегда `null`.",
        "x-required-scopes": [
          "boards:read"
        ],
        "parameters": [
          {
            "name": "archived",
            "in": "query",
            "required": false,
            "description": "`true` или `1` — включить архивные колонки. Любое другое значение\nозначает «только живые».\n",
            "schema": {
              "type": "string",
              "default": "false"
            },
            "example": "true"
          }
        ],
        "responses": {
          "200": {
            "description": "Страница колонок в порядке `position`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageOfList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Списки"
        ],
        "operationId": "createList",
        "summary": "Создать колонку",
        "description": "Колонка добавляется в конец доски.",
        "x-required-scopes": [
          "boards:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Колонка создана.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/List"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/lists/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Списки"
        ],
        "operationId": "getList",
        "summary": "Одна колонка",
        "x-required-scopes": [
          "boards:read"
        ],
        "responses": {
          "200": {
            "description": "Колонка.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/List"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Списки"
        ],
        "operationId": "updateList",
        "summary": "Переименовать или перекрасить колонку",
        "description": "Пустой патч не трогает `updated_at`: иначе он обесценил бы `ETag` у\nвсех клиентов и разослал событие без изменения.\n",
        "x-required-scopes": [
          "boards:write"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListPatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Обновлённая колонка.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/List"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "tags": [
          "Списки"
        ],
        "operationId": "deleteList",
        "summary": "Удалить колонку вместе с карточками",
        "description": "Необратимо, поэтому требует прав админа доски. Обратимый вариант —\n`POST /lists/{id}/archive`, он доступен обычному участнику.\n",
        "x-required-scopes": [
          "boards:write"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          }
        }
      }
    },
    "/lists/{id}/position": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "patch": {
        "tags": [
          "Списки"
        ],
        "operationId": "moveList",
        "summary": "Переставить колонку",
        "description": "Клиент присылает соседей, а не позицию. Оба поля `null` или пустое тело\nозначают «в конец доски». Колонка не может быть соседом самой себе.\nПроигранная гонка за щель между соседями повторяется до трёх раз,\nзатем — `409`.\n",
        "x-required-scopes": [
          "boards:write"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListMove"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Колонка на новом месте.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/List"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/lists/{id}/copy": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "post": {
        "tags": [
          "Списки"
        ],
        "operationId": "copyList",
        "summary": "Скопировать колонку",
        "description": "Копия встаёт сразу справа от оригинала и получает его живые карточки с\nметками и участниками. Чек-листы, вложения, комментарии и история **не**\nкопируются. Тело необязательно.\n",
        "x-required-scopes": [
          "boards:write"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListCopy"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Созданная копия.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/List"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/lists/{id}/archive": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "post": {
        "tags": [
          "Списки"
        ],
        "operationId": "archiveList",
        "summary": "Архивировать или восстановить колонку",
        "description": "Переключатель `is_archived`; ничего не теряется.",
        "x-required-scopes": [
          "boards:write"
        ],
        "responses": {
          "200": {
            "description": "Колонка после переключения.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/List"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/lists/{id}/actions/move-all-cards": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "post": {
        "tags": [
          "Списки"
        ],
        "operationId": "moveAllCards",
        "summary": "Перенести все карточки в другую колонку",
        "description": "Целевая колонка должна быть на той же доске. Ранги пересчитываются выше\nтекущего максимума приёмника, поэтому массовый UPDATE не наткнётся на\n`UNIQUE (list_id, position)` на полпути.\n",
        "x-required-scopes": [
          "cards:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MoveAllCards"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Колонка-источник после переноса.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/List"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/lists/{id}/actions/sort-cards": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "post": {
        "tags": [
          "Списки"
        ],
        "operationId": "sortCards",
        "summary": "Отсортировать карточки колонки",
        "description": "Порядок фиксируется в рангах: после сортировки карточки остаются в\nвыбранном порядке, пока их не переместят. Карточки без срока всегда\nвнизу — в обе стороны сортировки.\n",
        "x-required-scopes": [
          "cards:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SortCards"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Колонка после сортировки.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/List"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/lists/{id}/actions/archive-cards": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "post": {
        "tags": [
          "Списки"
        ],
        "operationId": "archiveListCards",
        "summary": "Архивировать все карточки колонки",
        "description": "Колонка остаётся на месте, скрываются только её карточки.",
        "x-required-scopes": [
          "cards:write"
        ],
        "responses": {
          "200": {
            "description": "Колонка после архивации карточек.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/List"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/cards": {
      "get": {
        "tags": [
          "Карточки"
        ],
        "operationId": "searchCards",
        "summary": "Поиск карточек по всем доступным доскам",
        "description": "Главный эндпоинт для внешней интеграции. Выдача ограничена досками,\nкоторые вызывающий вправе видеть, поэтому недоступный `board_id` даёт\nпустую страницу, а не доказательство существования доски.\n\nПорядок — `created_at DESC, id DESC`. Курсор непрозрачен: его берут из\n`next_cursor` и возвращают как есть.\n\nПараметры `label` и `member` можно повторять — условия складываются.\nЗначение `member=me` избавляет от лишнего вызова `/me`.\n",
        "x-required-scopes": [
          "cards:read"
        ],
        "parameters": [
          {
            "name": "board_id",
            "in": "query",
            "description": "Ограничить одной доской.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "list_id",
            "in": "query",
            "description": "Ограничить одной колонкой.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Полнотекстовый поиск по названию и описанию. Запрос короче трёх\nсимволов ищется подстрокой по названию.\n",
            "schema": {
              "type": "string"
            },
            "example": "релиз"
          },
          {
            "name": "label",
            "in": "query",
            "description": "Идентификатор метки; можно повторять.",
            "schema": {
              "type": "array",
              "items": {
                "type": "string",
                "format": "uuid"
              }
            },
            "style": "form",
            "explode": true
          },
          {
            "name": "member",
            "in": "query",
            "description": "Идентификатор участника или литерал `me`; можно повторять.\n",
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "style": "form",
            "explode": true
          },
          {
            "name": "due_before",
            "in": "query",
            "description": "Срок строго раньше указанной отметки (RFC 3339).",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "example": "2026-09-01T00:00:00Z"
          },
          {
            "name": "due_after",
            "in": "query",
            "description": "Срок строго позже указанной отметки (RFC 3339).",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "$ref": "#/components/parameters/Archived"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Страница карточек.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageOfCard"
                },
                "examples": {
                  "overdue": {
                    "summary": "Просроченные карточки вызывающего",
                    "description": "GET /api/v1/cards?member=me&due_before=2026-08-28T00:00:00Z&limit=2",
                    "value": {
                      "items": [
                        {
                          "id": "01a044b6-c390-7b83-9ce9-8303881bc941",
                          "list_id": "01a044b6-c390-7b83-9ce9-8303881bc930",
                          "board_id": "01a044b6-c390-7b83-9ce9-8303881bc906",
                          "parent_id": null,
                          "name": "Починить импорт",
                          "description": "",
                          "position": "aZ9",
                          "due_date": "2026-08-20T12:00:00Z",
                          "due_complete": false,
                          "due_state": "overdue",
                          "cover_color": "",
                          "cover_url": null,
                          "is_archived": false,
                          "labels": [],
                          "members": [
                            {
                              "id": "01a044b6-c390-7b83-9ce9-8303881bc9a1",
                              "username": "dare",
                              "display_name": "Дарья Лебедева",
                              "initials": "ДЛ",
                              "avatar_url": null
                            }
                          ],
                          "checklist_stats": {
                            "checked": 1,
                            "total": 3
                          },
                          "comments_count": 2,
                          "attachments_count": 0,
                          "subtasks_count": 0,
                          "created_at": "2026-08-10T08:00:00Z",
                          "updated_at": "2026-08-21T10:30:00Z"
                        }
                      ],
                      "next_cursor": "MTc1NjM2NTIwMDoxYTA0NGI2"
                    }
                  },
                  "empty": {
                    "summary": "Ничего не нашлось",
                    "value": {
                      "items": [],
                      "next_cursor": null
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Карточки"
        ],
        "operationId": "createCard",
        "summary": "Создать карточку (колонка в теле)",
        "description": "То же, что `POST /lists/{id}/cards`, но целевая колонка передаётся полем\n`list_id`. Карточка встаёт в конец колонки.\n",
        "x-required-scopes": [
          "cards:write"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CardCreate"
              },
              "examples": {
                "minimal": {
                  "summary": "Минимальная карточка",
                  "value": {
                    "list_id": "01a044b6-c390-7b83-9ce9-8303881bc930",
                    "name": "Обновить README"
                  }
                },
                "full": {
                  "summary": "Со сроком и обложкой",
                  "value": {
                    "list_id": "01a044b6-c390-7b83-9ce9-8303881bc930",
                    "name": "Выкатить релиз 1.4",
                    "description": "Тег, миграции, прогон smoke-тестов.",
                    "due_date": "2026-09-05T15:00:00Z",
                    "due_complete": false,
                    "cover_color": "#0079BF"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Карточка создана.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                },
                "examples": {
                  "created": {
                    "summary": "Ответ на минимальный запрос",
                    "value": {
                      "id": "01a044b6-c390-7b83-9ce9-8303881bc950",
                      "list_id": "01a044b6-c390-7b83-9ce9-8303881bc930",
                      "board_id": "01a044b6-c390-7b83-9ce9-8303881bc906",
                      "parent_id": null,
                      "name": "Обновить README",
                      "description": "",
                      "position": "b",
                      "due_date": null,
                      "due_complete": false,
                      "due_state": "none",
                      "cover_color": "",
                      "cover_url": null,
                      "is_archived": false,
                      "labels": [],
                      "members": [],
                      "checklist_stats": null,
                      "comments_count": 0,
                      "attachments_count": 0,
                      "subtasks_count": 0,
                      "created_at": "2026-08-28T07:15:00Z",
                      "updated_at": "2026-08-28T07:15:00Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/lists/{id}/cards": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Карточки"
        ],
        "operationId": "listCards",
        "summary": "Карточки одной колонки",
        "description": "В отличие от `GET /cards`, колонка разрешается заранее: неизвестный или\nчужой `id` даёт `404`, а не пустую страницу. Порядок — по `position`.\n",
        "x-required-scopes": [
          "cards:read"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Archived"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Страница карточек в порядке колонки.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageOfCard"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Карточки"
        ],
        "operationId": "createCardInList",
        "summary": "Создать карточку в колонке",
        "description": "Карточка встаёт в конец колонки. Поле `list_id` в теле допустимо только\nесли совпадает с колонкой из пути.\n",
        "x-required-scopes": [
          "cards:write"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CardCreateInList"
              },
              "examples": {
                "minimal": {
                  "summary": "Минимальная карточка",
                  "value": {
                    "name": "Обновить README"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Карточка создана.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/cards/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Карточки"
        ],
        "operationId": "getCard",
        "summary": "Карточка со всем содержимым (CardDetail)",
        "description": "Всё, что нужно модальному окну карточки: чек-листы, комментарии,\nвложения, история, подзадачи и связи — одним запросом.\n",
        "x-required-scopes": [
          "cards:read"
        ],
        "responses": {
          "200": {
            "description": "Карточка с коллекциями.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CardDetail"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Карточки"
        ],
        "operationId": "updateCard",
        "summary": "Изменить карточку",
        "description": "Отсутствующие поля не трогаются. `due_date: null` **снимает** срок, а\nотсутствие поля оставляет его как есть. Позиция здесь не меняется — для\nэтого есть `PATCH /cards/{id}/position`.\n",
        "x-required-scopes": [
          "cards:write"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          },
          {
            "$ref": "#/components/parameters/ClientID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CardPatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Обновлённая карточка.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "tags": [
          "Карточки"
        ],
        "operationId": "deleteCard",
        "summary": "Удалить карточку",
        "description": "Необратимо. Обратимая операция — `POST /cards/{id}/archive`.\n",
        "x-required-scopes": [
          "cards:write"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientID"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/cards/{id}/position": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "patch": {
        "tags": [
          "Карточки"
        ],
        "operationId": "moveCard",
        "summary": "Переместить карточку",
        "description": "Клиент присылает соседей, а не позицию: `after_id` — карточка, **после**\nкоторой встать, `before_id` — **перед** которой. Оба `null` означают «в\nконец колонки». Необязательный `list_id` переносит карточку в другую\nколонку **той же доски**.\n\nСервер читает ранги соседей и вычисляет ключ между ними. При конфликте\nуникального индекса `(list_id, position)` — до трёх повторов, затем\n`409`. Карточка не может быть соседом самой себе.\n",
        "x-required-scopes": [
          "cards:write"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IfMatch"
          },
          {
            "$ref": "#/components/parameters/ClientID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CardMove"
              },
              "examples": {
                "between": {
                  "summary": "Между двумя карточками той же колонки",
                  "value": {
                    "after_id": "01a044b6-c390-7b83-9ce9-8303881bc941",
                    "before_id": "01a044b6-c390-7b83-9ce9-8303881bc942"
                  }
                },
                "toEnd": {
                  "summary": "В конец другой колонки",
                  "value": {
                    "list_id": "01a044b6-c390-7b83-9ce9-8303881bc931",
                    "after_id": null,
                    "before_id": null
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Карточка на новом месте.",
            "headers": {
              "ETag": {
                "$ref": "#/components/headers/ETag"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                },
                "examples": {
                  "moved": {
                    "summary": "После переноса в другую колонку",
                    "value": {
                      "id": "01a044b6-c390-7b83-9ce9-8303881bc950",
                      "list_id": "01a044b6-c390-7b83-9ce9-8303881bc931",
                      "board_id": "01a044b6-c390-7b83-9ce9-8303881bc906",
                      "parent_id": null,
                      "name": "Обновить README",
                      "description": "",
                      "position": "c",
                      "due_date": null,
                      "due_complete": false,
                      "due_state": "none",
                      "cover_color": "",
                      "cover_url": null,
                      "is_archived": false,
                      "labels": [],
                      "members": [],
                      "checklist_stats": null,
                      "comments_count": 0,
                      "attachments_count": 0,
                      "subtasks_count": 0,
                      "created_at": "2026-08-28T07:15:00Z",
                      "updated_at": "2026-08-28T07:22:41Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "412": {
            "$ref": "#/components/responses/PreconditionFailed"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/cards/{id}/archive": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "post": {
        "tags": [
          "Карточки"
        ],
        "operationId": "archiveCard",
        "summary": "Архивировать или восстановить карточку",
        "description": "Переключатель `is_archived` в один клик.",
        "x-required-scopes": [
          "cards:write"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientID"
          }
        ],
        "responses": {
          "200": {
            "description": "Карточка после переключения.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/cards/{id}/labels/{label_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        },
        {
          "name": "label_id",
          "in": "path",
          "required": true,
          "description": "Метка той же доски.",
          "schema": {
            "type": "string",
            "format": "uuid"
          }
        }
      ],
      "put": {
        "tags": [
          "Карточки"
        ],
        "operationId": "attachLabel",
        "summary": "Повесить метку на карточку",
        "description": "Идемпотентно: повтор не ошибка.",
        "x-required-scopes": [
          "cards:write"
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Карточки"
        ],
        "operationId": "detachLabel",
        "summary": "Снять метку с карточки",
        "description": "Идемпотентно: повтор не ошибка.",
        "x-required-scopes": [
          "cards:write"
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/cards/{id}/members/{user_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        },
        {
          "$ref": "#/components/parameters/PathUserID"
        }
      ],
      "put": {
        "tags": [
          "Карточки"
        ],
        "operationId": "assignCardMember",
        "summary": "Назначить участника на карточку",
        "description": "Назначить можно только того, кто вправе открыть доску, иначе он видел бы\nкарточку, до которой не дотянется.\n",
        "x-required-scopes": [
          "cards:write"
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Карточки"
        ],
        "operationId": "unassignCardMember",
        "summary": "Снять участника с карточки",
        "description": "Снятие не требует членства на доске: иначе назначения выбывшего\nучастника нечем было бы вычистить.\n",
        "x-required-scopes": [
          "cards:write"
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/cards/{id}/subtasks": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Карточки"
        ],
        "operationId": "listSubtasks",
        "summary": "Подзадачи карточки",
        "description": "Возвращает детей **полными карточками**, в порядке колонки. В\n`CardDetail.subtasks` тот же набор приходит в компактной форме `CardRef`.\n",
        "x-required-scopes": [
          "cards:read"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Archived"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Страница подзадач.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageOfCard"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Карточки"
        ],
        "operationId": "createSubtask",
        "summary": "Создать подзадачу",
        "description": "Подзадача создаётся в колонке родителя. Второго уровня вложенности нет:\nу подзадачи не может быть своих подзадач.\n",
        "x-required-scopes": [
          "cards:write"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubtaskCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Подзадача создана.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Card"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/cards/{id}/relations": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Карточки"
        ],
        "operationId": "listRelations",
        "summary": "Связи карточки",
        "description": "Обе стороны сразу: и что карточка блокирует, и что блокирует её.\n`next_cursor` всегда `null` — связей по природе немного.\n",
        "x-required-scopes": [
          "cards:read"
        ],
        "responses": {
          "200": {
            "description": "Страница связей.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageOfRelation"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Карточки"
        ],
        "operationId": "createRelation",
        "summary": "Связать две карточки",
        "description": "Связи могут пересекать доски, поэтому концы авторизуются раздельно: на\nисточнике нужно право правки, на цели — хотя бы право просмотра.\nПовторная связь того же типа — `409`.\n",
        "x-required-scopes": [
          "cards:write"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RelationCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Связь создана.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Relation"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/relations/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "delete": {
        "tags": [
          "Карточки"
        ],
        "operationId": "deleteRelation",
        "summary": "Удалить связь",
        "description": "Снять связь можно с любого конца: достаточно видеть обе карточки и иметь\nправо правки хотя бы на одной из досок.\n",
        "x-required-scopes": [
          "cards:write"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientID"
          }
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/cards/{id}/activities": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Карточки"
        ],
        "operationId": "listCardActivities",
        "summary": "История карточки",
        "description": "Новые записи первыми.",
        "x-required-scopes": [
          "cards:read"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Страница записей истории.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageOfActivity"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/cards/{id}/checklists": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Чек-листы"
        ],
        "operationId": "listChecklists",
        "summary": "Чек-листы карточки",
        "description": "Пункты приходят вложенными; `next_cursor` всегда `null`.",
        "x-required-scopes": [
          "cards:read"
        ],
        "responses": {
          "200": {
            "description": "Страница чек-листов с пунктами.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageOfChecklist"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Чек-листы"
        ],
        "operationId": "createChecklist",
        "summary": "Создать чек-лист",
        "description": "Место задаётся соседями: оба поля отсутствуют или `null` — в конец\nкарточки. В ответе `items` пуст.\n",
        "x-required-scopes": [
          "cards:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChecklistCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Чек-лист создан.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checklist"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/checklists/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Чек-листы"
        ],
        "operationId": "getChecklist",
        "summary": "Один чек-лист",
        "x-required-scopes": [
          "cards:read"
        ],
        "responses": {
          "200": {
            "description": "Чек-лист с пунктами.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checklist"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Чек-листы"
        ],
        "operationId": "updateChecklist",
        "summary": "Переименовать и/или переставить чек-лист",
        "description": "Хотя бы одно из `name`, `after_id`, `before_id` обязано присутствовать,\nиначе `422`. Явный `null` у соседей означает «переставить», отсутствие\nполя — «позицию не трогать».\n",
        "x-required-scopes": [
          "cards:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChecklistPatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Обновлённый чек-лист.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Checklist"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "tags": [
          "Чек-листы"
        ],
        "operationId": "deleteChecklist",
        "summary": "Удалить чек-лист",
        "description": "Пункты уходят каскадом.",
        "x-required-scopes": [
          "cards:write"
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/checklists/{id}/items": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Чек-листы"
        ],
        "operationId": "listChecklistItems",
        "summary": "Пункты чек-листа",
        "x-required-scopes": [
          "cards:read"
        ],
        "responses": {
          "200": {
            "description": "Страница пунктов; `next_cursor` всегда `null`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageOfChecklistItem"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Чек-листы"
        ],
        "operationId": "createChecklistItem",
        "summary": "Добавить пункт",
        "x-required-scopes": [
          "cards:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChecklistItemCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Пункт создан.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChecklistItem"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/checklist-items/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "patch": {
        "tags": [
          "Чек-листы"
        ],
        "operationId": "updateChecklistItem",
        "summary": "Изменить пункт",
        "description": "Текст, галочка и перестановка — в любом сочетании, но хотя бы одно поле\nобязано присутствовать.\n",
        "x-required-scopes": [
          "cards:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChecklistItemPatch"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Обновлённый пункт.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChecklistItem"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "tags": [
          "Чек-листы"
        ],
        "operationId": "deleteChecklistItem",
        "summary": "Удалить пункт",
        "x-required-scopes": [
          "cards:write"
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/cards/{id}/comments": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Комментарии"
        ],
        "operationId": "listComments",
        "summary": "Комментарии карточки",
        "description": "Курсор — идентификатор последнего элемента предыдущей страницы. Читать\nкомментарии позволяет скоуп `cards:read`.\n",
        "x-required-scopes": [
          "cards:read"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "name": "cursor",
            "in": "query",
            "description": "Непрозрачный курсор — `next_cursor` предыдущей страницы.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Страница комментариев.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageOfComment"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "post": {
        "tags": [
          "Комментарии"
        ],
        "operationId": "createComment",
        "summary": "Написать комментарий",
        "description": "Текст хранится как Markdown, а `text_html` собирается на выдаче и\nсанируется — поэтому ужесточение санитайзера задним числом накрывает и\nуже написанное.\n",
        "x-required-scopes": [
          "comments:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CommentBody"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Комментарий создан.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Comment"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/comments/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "patch": {
        "tags": [
          "Комментарии"
        ],
        "operationId": "updateComment",
        "summary": "Изменить свой комментарий",
        "description": "Править может только автор.",
        "x-required-scopes": [
          "comments:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CommentBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Обновлённый комментарий.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Comment"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      },
      "delete": {
        "tags": [
          "Комментарии"
        ],
        "operationId": "deleteComment",
        "summary": "Удалить комментарий",
        "description": "Удалять может автор, а также админ доски — это единственный способ\nмодерации. Удаление мягкое: строка остаётся за `deleted_at`.\n",
        "x-required-scopes": [
          "comments:write"
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/cards/{id}/attachments": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Вложения"
        ],
        "operationId": "listAttachments",
        "summary": "Вложения карточки",
        "x-required-scopes": [
          "attachments:read"
        ],
        "responses": {
          "200": {
            "description": "Страница вложений; `next_cursor` всегда `null`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageOfAttachment"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "tags": [
          "Вложения"
        ],
        "operationId": "uploadAttachment",
        "summary": "Загрузить файл",
        "description": "`multipart/form-data`, поле `file`. Тип определяется по магическим\nбайтам, а не по заголовку клиента, — исполняемый файл не сможет прийти\nпод видом картинки. Имя на диске генерирует сервер; имя клиента живёт\nтолько как отображаемое.\n\nПредел размера — меньшее из `MAX_UPLOAD_SIZE` и 100 МиБ; превышение —\n`413`, не-multipart — `415`, пустой или отсутствующий файл — `422`.\n",
        "x-required-scopes": [
          "attachments:write"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Содержимое файла."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Вложение записано.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Attachment"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/Validation"
          }
        }
      }
    },
    "/attachments/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Вложения"
        ],
        "operationId": "downloadAttachment",
        "summary": "Скачать вложение",
        "description": "Отдаёт **сами байты**, а не JSON. Скачивание идёт через API, а не через\n`MEDIA_URL`, потому что статика ничего не знает о членстве на доске и\nкаждое чтение обязано перепроверить доступ.\n\nОтвет несёт `Content-Type`, определённый при загрузке,\n`X-Content-Type-Options: nosniff` и `Content-Disposition: attachment`.\nПоддерживаются диапазоны (`Range`).\n",
        "x-required-scopes": [
          "attachments:read"
        ],
        "responses": {
          "200": {
            "description": "Содержимое файла.",
            "headers": {
              "Content-Disposition": {
                "description": "Всегда `attachment`; имя файла — по RFC 2231.",
                "schema": {
                  "type": "string"
                }
              },
              "X-Content-Type-Options": {
                "description": "Всегда `nosniff`.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "delete": {
        "tags": [
          "Вложения"
        ],
        "operationId": "deleteAttachment",
        "summary": "Удалить вложение",
        "x-required-scopes": [
          "attachments:write"
        ],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/notifications": {
      "get": {
        "tags": [
          "Уведомления"
        ],
        "operationId": "listNotifications",
        "summary": "Ящик вызывающего",
        "description": "Новые первыми. Параметров доски или пользователя нет намеренно: вернуть\nэтот эндпоинт может только строки, адресованные владельцу токена.\n",
        "x-required-scopes": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          },
          {
            "name": "unread",
            "in": "query",
            "description": "`true` — только непрочитанные. Нераспознанное значение — `400`, а не\nмолчаливое «весь ящик».\n",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Страница уведомлений.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PageOfNotification"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/notifications/count": {
      "get": {
        "tags": [
          "Уведомления"
        ],
        "operationId": "countUnreadNotifications",
        "summary": "Счётчик непрочитанных",
        "description": "Питает бейдж в шапке.",
        "x-required-scopes": [],
        "responses": {
          "200": {
            "description": "Количество непрочитанных.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "unread"
                  ],
                  "properties": {
                    "unread": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Непрочитанных уведомлений."
                    }
                  },
                  "examples": [
                    {
                      "unread": 3
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/notifications/read": {
      "post": {
        "tags": [
          "Уведомления"
        ],
        "operationId": "markAllNotificationsRead",
        "summary": "Пометить весь ящик прочитанным",
        "x-required-scopes": [],
        "responses": {
          "200": {
            "description": "Сколько строк обновилось.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "updated"
                  ],
                  "properties": {
                    "updated": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Число помеченных уведомлений."
                    }
                  },
                  "examples": [
                    {
                      "updated": 7
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/notifications/{id}/read": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "post": {
        "tags": [
          "Уведомления"
        ],
        "operationId": "markNotificationRead",
        "summary": "Пометить одно уведомление прочитанным",
        "description": "Чужой и несуществующий идентификатор дают одинаковый `404`.",
        "x-required-scopes": [],
        "responses": {
          "204": {
            "$ref": "#/components/responses/NoContent"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/boards/{id}/events": {
      "parameters": [
        {
          "$ref": "#/components/parameters/PathID"
        }
      ],
      "get": {
        "tags": [
          "Живые обновления"
        ],
        "operationId": "streamBoardEvents",
        "summary": "Поток изменений доски (SSE)",
        "description": "`text/event-stream`, один безымянный кадр на изменение, поэтому обычный\n`EventSource.onmessage` получает всё и разбирает JSON тела.\n\nИдентификатор кадра — первичный ключ `board_events`. При переподключении\nбраузер шлёт его в `Last-Event-ID`, и сервер доигрывает пропуск (до 500\nсобытий) перед возвратом к живой ленте.\n\n`client_id` — эхо-фильтр: вкладка, вызвавшая изменение, узнаёт свой\nсобственный кадр по значению, которое сама прислала в `X-Client-Id`, и\nпропускает его.\n\nРаз в 15 секунд идёт heartbeat-комментарий, чтобы прокси не закрыл\nпростаивающее соединение.\n",
        "x-required-scopes": [
          "boards:read"
        ],
        "parameters": [
          {
            "name": "Last-Event-ID",
            "in": "header",
            "required": false,
            "description": "Идентификатор последнего полученного кадра.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Открытый поток событий.",
            "content": {
              "text/event-stream": {
                "schema": {
                  "$ref": "#/components/schemas/BoardEvent"
                },
                "examples": {
                  "move": {
                    "summary": "Одно перемещение карточки",
                    "value": {
                      "id": 1843,
                      "board_id": "01a044b6-c390-7b83-9ce9-8303881bc906",
                      "actor_id": "01a044b6-c390-7b83-9ce9-8303881bc9a1",
                      "client_id": "tab-7",
                      "entity_type": "card",
                      "entity_id": "01a044b6-c390-7b83-9ce9-8303881bc950",
                      "action": "move",
                      "payload": {
                        "from_list_id": "01a044b6-c390-7b83-9ce9-8303881bc930",
                        "to_list_id": "01a044b6-c390-7b83-9ce9-8303881bc931",
                        "position": "c"
                      },
                      "created_at": "2026-08-28T07:22:41Z"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/openapi.yaml": {
      "servers": [
        {
          "url": "/",
          "description": "Корень хоста, без префикса версии"
        }
      ],
      "get": {
        "tags": [
          "Документация"
        ],
        "operationId": "getOpenapiYaml",
        "summary": "Спецификация в YAML",
        "description": "Реальный путь — `/openapi.yaml`, без префикса `/api/v1`. Доступен без\nтокена, иначе внешний разработчик не смог бы прочитать документацию.\n",
        "security": [],
        "x-required-scopes": [],
        "responses": {
          "200": {
            "description": "Спецификация OpenAPI 3.1 в YAML.",
            "content": {
              "application/yaml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "servers": [
        {
          "url": "/",
          "description": "Корень хоста, без префикса версии"
        }
      ],
      "get": {
        "tags": [
          "Документация"
        ],
        "operationId": "getOpenapiJson",
        "summary": "Спецификация в JSON",
        "description": "Реальный путь — `/openapi.json`, без префикса `/api/v1`. Тот же\nдокумент, сконвертированный из YAML при сборке. Доступен без токена.\n",
        "security": [],
        "x-required-scopes": [],
        "responses": {
          "200": {
            "description": "Спецификация OpenAPI 3.1 в JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      }
    },
    "/docs": {
      "servers": [
        {
          "url": "/",
          "description": "Корень хоста, без префикса версии"
        }
      ],
      "get": {
        "tags": [
          "Документация"
        ],
        "operationId": "getDocs",
        "summary": "Swagger UI",
        "description": "Реальный путь — `/docs`, без префикса `/api/v1`. HTML-страница Swagger\nUI; ассеты вшиты в бинарник, обращений к CDN нет. Доступна без токена.\n",
        "security": [],
        "x-required-scopes": [],
        "responses": {
          "200": {
            "description": "HTML-страница документации.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "trl_pat",
        "description": "Персональный токен доступа вида `trl_pat_<prefix6>_<secret>`.\n\nЗаголовок: `Authorization: Bearer trl_pat_…`\n\nВ базе лежит `HMAC-SHA256(PAT_PEPPER, secret)`, поэтому дампа базы\nнедостаточно для подделки токена. Отзыв мгновенный.\n\nКаждая операция объявляет нужные права в расширении\n`x-required-scopes`. Токен без нужного скоупа получает `403` с\n`type: …/problems/forbidden`. Пустой список означает, что операция\nдоступна любому валидному токену.\n\nИзвестные скоупы:\n\n| Скоуп | Что открывает |\n|---|---|\n| `boards:read` | чтение досок, списков, меток, участников |\n| `boards:write` | создание и изменение досок, списков, меток, участников |\n| `cards:read` | чтение карточек, чек-листов, комментариев, истории |\n| `cards:write` | создание, изменение, перемещение, архивация карточек и чек-листов |\n| `comments:write` | создание и изменение своих комментариев |\n| `attachments:read` | чтение и скачивание вложений |\n| `attachments:write` | загрузка и удаление вложений |\n| `profile:read` | `/me`, поиск пользователей |\n"
      }
    },
    "headers": {
      "ETag": {
        "description": "Валидатор конкурентной записи. Вернуть его в `If-Match` следующего\nPATCH или DELETE, чтобы чужая параллельная запись не была затёрта.\n",
        "schema": {
          "type": "string"
        },
        "example": "\"1756365200123456789\""
      }
    },
    "parameters": {
      "PathID": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Идентификатор ресурса (UUID v7).",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "example": "01a044b6-c390-7b83-9ce9-8303881bc906"
      },
      "PathUserID": {
        "name": "user_id",
        "in": "path",
        "required": true,
        "description": "Идентификатор пользователя (UUID v7).",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Размер страницы, 1..200.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "Непрозрачный курсор — значение `next_cursor` предыдущей страницы.\nРазбирать его на клиенте не нужно и не следует.\n",
        "schema": {
          "type": "string"
        }
      },
      "Archived": {
        "name": "archived",
        "in": "query",
        "required": false,
        "description": "`false` — только живые (по умолчанию), `true` — только архивные,\n`all` — и те и другие. Принимаются также синонимы `0`/`no`, `1`/`yes`,\n`any`; всё прочее — `400`.\n",
        "schema": {
          "type": "string",
          "enum": [
            "true",
            "false",
            "all"
          ],
          "default": "false"
        }
      },
      "IfMatch": {
        "name": "If-Match",
        "in": "header",
        "required": false,
        "description": "Условная запись: значение `ETag`, прочитанное ранее. Несовпадение —\n`412`, запись не выполняется. Отсутствие заголовка означает\nбезусловную запись.\n",
        "schema": {
          "type": "string"
        },
        "example": "\"1756365200123456789\""
      },
      "ClientID": {
        "name": "X-Client-Id",
        "in": "header",
        "required": false,
        "description": "Метка вкладки-инициатора. Возвращается в поле `client_id` кадра SSE,\nчтобы та же вкладка не применила поверх своего оптимистичного\nобновления серверное эхо. До 64 символов `[A-Za-z0-9_-]`.\n",
        "schema": {
          "type": "string",
          "maxLength": 64
        },
        "example": "tab-7"
      }
    },
    "responses": {
      "NoContent": {
        "description": "Успех, тела нет."
      },
      "BadRequest": {
        "description": "Некорректный параметр строки запроса.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://trololo.telco-system.com/problems/bad-request",
              "title": "Некорректный запрос",
              "status": 400,
              "detail": "Параметр board_id не является UUID.",
              "instance": "/api/v1/cards"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Токен не предъявлен, недействителен, просрочен или отозван.\n",
        "headers": {
          "WWW-Authenticate": {
            "description": "Присутствует, когда заголовок `Authorization` отсутствует.",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://trololo.telco-system.com/problems/unauthorized",
              "title": "Требуется вход",
              "status": 401,
              "detail": "Нужен персональный токен доступа: Authorization: Bearer trl_pat_…",
              "instance": "/api/v1/boards"
            }
          }
        }
      },
      "Forbidden": {
        "description": "У токена нет нужного скоупа, либо роль на доске не позволяет операцию,\nлибо доска закрыта.\n",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://trololo.telco-system.com/problems/forbidden",
              "title": "Доступ запрещён",
              "status": 403,
              "detail": "У токена нет нужного права: cards:write.",
              "instance": "/api/v1/cards"
            }
          }
        }
      },
      "NotFound": {
        "description": "Ресурса нет — либо он есть, но вызывающему не виден. Ответы намеренно\nнеразличимы, чтобы перебор идентификаторов ничего не выдавал.\n",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://trololo.telco-system.com/problems/not-found",
              "title": "Не найдено",
              "status": 404,
              "detail": "Объект не найден или доступ к нему закрыт.",
              "instance": "/api/v1/cards/01a044b6-c390-7b83-9ce9-8303881bc950"
            }
          }
        }
      },
      "Conflict": {
        "description": "Позиция занята параллельной записью, ресурс-дубликат уже существует,\nлибо пространство рангов между соседями исчерпано.\n",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://trololo.telco-system.com/problems/conflict",
              "title": "Конфликт",
              "status": 409,
              "detail": "Позиция уже занята. Нужно перечитать соседние записи и повторить запрос.",
              "instance": "/api/v1/cards/01a044b6-c390-7b83-9ce9-8303881bc950/position"
            }
          }
        }
      },
      "PreconditionFailed": {
        "description": "`If-Match` разошёлся с текущим `ETag`: ресурс изменился с момента\nчтения. Перечитать и повторить.\n",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://trololo.telco-system.com/problems/precondition-failed",
              "title": "Условие не выполнено",
              "status": 412,
              "detail": "Карточка изменилась с момента чтения: нужно перечитать её и повторить запрос.",
              "instance": "/api/v1/cards/01a044b6-c390-7b83-9ce9-8303881bc950"
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "Тело запроса или загружаемый файл превышают предел.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://trololo.telco-system.com/problems/payload-too-large",
              "title": "Слишком большой запрос",
              "status": 413,
              "detail": "Файл больше допустимого размера.",
              "instance": "/api/v1/cards/01a044b6-c390-7b83-9ce9-8303881bc950/attachments"
            }
          }
        }
      },
      "UnsupportedMediaType": {
        "description": "Не тот `Content-Type`: JSON-эндпоинты ждут `application/json`,\nзагрузка вложения — `multipart/form-data`.\n",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://trololo.telco-system.com/problems/unsupported-media-type",
              "title": "Неподдерживаемый тип",
              "status": 415,
              "detail": "Ожидается Content-Type: multipart/form-data.",
              "instance": "/api/v1/cards/01a044b6-c390-7b83-9ce9-8303881bc950/attachments"
            }
          }
        }
      },
      "Validation": {
        "description": "Тело запроса не прошло проверку; подробности — в `errors`.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://trololo.telco-system.com/problems/validation-failed",
              "title": "Ошибка валидации",
              "status": 422,
              "detail": "Тело запроса не прошло проверку.",
              "instance": "/api/v1/cards",
              "errors": [
                {
                  "field": "name",
                  "detail": "Название обязательно."
                }
              ]
            }
          }
        }
      }
    },
    "schemas": {
      "UUID": {
        "type": "string",
        "format": "uuid",
        "description": "UUID v7 в каноническом виде.",
        "examples": [
          "01a044b6-c390-7b83-9ce9-8303881bc906"
        ]
      },
      "Timestamp": {
        "type": "string",
        "format": "date-time",
        "description": "RFC 3339 в UTC, с суффиксом `Z`.",
        "examples": [
          "2026-08-28T07:22:41Z"
        ]
      },
      "Position": {
        "type": "string",
        "description": "Дробный ранг base62. Значение непрозрачно: клиент передаёт его обратно\nкак есть, а для перемещения шлёт соседей.\n",
        "examples": [
          "V",
          "aZ9"
        ]
      },
      "HexColor": {
        "type": "string",
        "description": "Цвет `#RRGGBB` либо пустая строка, если цвет не задан.",
        "pattern": "^(#[0-9A-Fa-f]{6})?$",
        "examples": [
          "#0079BF",
          ""
        ]
      },
      "LabelColor": {
        "type": "string",
        "description": "Именованный цвет метки.",
        "enum": [
          "green",
          "yellow",
          "orange",
          "red",
          "purple",
          "blue",
          "sky",
          "lime",
          "pink",
          "black"
        ]
      },
      "BoardRole": {
        "type": "string",
        "description": "Роль участника на доске.",
        "enum": [
          "admin",
          "normal",
          "observer"
        ]
      },
      "WorkspaceRole": {
        "type": "string",
        "description": "Роль участника в рабочем пространстве.",
        "enum": [
          "admin",
          "member"
        ]
      },
      "Visibility": {
        "type": "string",
        "description": "`private` — только участникам доски, `workspace` — всему пространству,\n`public` — всем аутентифицированным.\n",
        "enum": [
          "private",
          "workspace",
          "public"
        ]
      },
      "DueState": {
        "type": "string",
        "description": "Состояние срока, вычисленное сервером, чтобы клиенты подсвечивали\nдедлайны одинаково: `complete` — отмечен выполненным, `overdue` —\nпросрочен, `soon` — меньше 24 часов, `upcoming` — больше 24 часов,\n`none` — срока нет.\n",
        "enum": [
          "none",
          "upcoming",
          "soon",
          "overdue",
          "complete"
        ]
      },
      "RelationType": {
        "type": "string",
        "description": "Тип связи между карточками.",
        "enum": [
          "related",
          "blocks",
          "blocked_by"
        ]
      },
      "Scope": {
        "type": "string",
        "description": "Право, которое несёт токен.",
        "enum": [
          "boards:read",
          "boards:write",
          "cards:read",
          "cards:write",
          "comments:write",
          "attachments:read",
          "attachments:write",
          "profile:read"
        ]
      },
      "UserRef": {
        "type": "object",
        "description": "Компактное представление пользователя, встречается везде.",
        "required": [
          "id",
          "username",
          "display_name",
          "initials",
          "avatar_url"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "username": {
            "type": "string",
            "description": "Логин."
          },
          "display_name": {
            "type": "string",
            "description": "Имя для показа; при пустом ФИО совпадает с `username`."
          },
          "initials": {
            "type": "string",
            "description": "Инициалы для аватара-заглушки."
          },
          "avatar_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Абсолютный URL аватара или `null`."
          }
        }
      },
      "Label": {
        "type": "object",
        "description": "Метка доски.",
        "required": [
          "id",
          "board_id",
          "name",
          "color"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "board_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "name": {
            "type": "string",
            "maxLength": 100,
            "description": "Имя метки; может быть пустым — тогда метка «просто цвет»."
          },
          "color": {
            "$ref": "#/components/schemas/LabelColor"
          }
        }
      },
      "BoardMember": {
        "type": "object",
        "description": "Членство на доске или в пространстве, с развёрнутым пользователем.",
        "required": [
          "id",
          "user",
          "role",
          "joined_at"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "user": {
            "$ref": "#/components/schemas/UserRef"
          },
          "role": {
            "type": "string",
            "description": "На доске — `admin`, `normal` или `observer`; в пространстве —\n`admin` или `member`.\n"
          },
          "joined_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "Board": {
        "type": "object",
        "description": "Доска в свёрнутом виде — то, что нужно для списка досок.",
        "required": [
          "id",
          "workspace_id",
          "name",
          "description",
          "background_color",
          "background_url",
          "visibility",
          "is_closed",
          "position",
          "lists_count",
          "cards_count",
          "my_role",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "workspace_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": "string"
          },
          "background_color": {
            "$ref": "#/components/schemas/HexColor"
          },
          "background_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Абсолютный URL фоновой картинки или `null`."
          },
          "visibility": {
            "$ref": "#/components/schemas/Visibility"
          },
          "is_closed": {
            "type": "boolean",
            "description": "Закрытая доска доступна только на чтение."
          },
          "position": {
            "$ref": "#/components/schemas/Position"
          },
          "lists_count": {
            "type": "integer",
            "minimum": 0
          },
          "cards_count": {
            "type": "integer",
            "minimum": 0
          },
          "my_role": {
            "$ref": "#/components/schemas/BoardRole"
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          },
          "updated_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "BoardFull": {
        "description": "Доска со всем, что нужно для её отрисовки одним запросом.",
        "allOf": [
          {
            "$ref": "#/components/schemas/Board"
          },
          {
            "type": "object",
            "required": [
              "labels",
              "members",
              "lists"
            ],
            "properties": {
              "labels": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Label"
                }
              },
              "members": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BoardMember"
                }
              },
              "lists": {
                "type": "array",
                "description": "Живые колонки в порядке `position`.",
                "items": {
                  "$ref": "#/components/schemas/List"
                }
              }
            }
          }
        ]
      },
      "BoardRef": {
        "type": "object",
        "description": "Минимум, чтобы поставить ссылку на доску.",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "List": {
        "type": "object",
        "description": "Колонка доски.",
        "required": [
          "id",
          "board_id",
          "name",
          "color",
          "position",
          "is_archived",
          "cards_count",
          "created_at"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "board_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "color": {
            "$ref": "#/components/schemas/HexColor"
          },
          "position": {
            "$ref": "#/components/schemas/Position"
          },
          "is_archived": {
            "type": "boolean"
          },
          "cards_count": {
            "type": "integer",
            "minimum": 0,
            "description": "Живые карточки колонки."
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "ChecklistStats": {
        "type": "object",
        "description": "Бейдж «3/7» на лицевой стороне карточки.",
        "required": [
          "checked",
          "total"
        ],
        "properties": {
          "checked": {
            "type": "integer",
            "minimum": 0
          },
          "total": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "Card": {
        "type": "object",
        "description": "Лицевая сторона карточки — всё, что рисуется на доске.",
        "required": [
          "id",
          "list_id",
          "board_id",
          "parent_id",
          "name",
          "description",
          "position",
          "due_date",
          "due_complete",
          "due_state",
          "cover_color",
          "cover_url",
          "is_archived",
          "labels",
          "members",
          "checklist_stats",
          "comments_count",
          "attachments_count",
          "subtasks_count",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "list_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "board_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "parent_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UUID"
              },
              {
                "type": "null"
              }
            ],
            "description": "Родительская карточка, если это подзадача."
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "description": {
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/Position"
          },
          "due_date": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "due_complete": {
            "type": "boolean"
          },
          "due_state": {
            "$ref": "#/components/schemas/DueState"
          },
          "cover_color": {
            "$ref": "#/components/schemas/HexColor"
          },
          "cover_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "is_archived": {
            "type": "boolean"
          },
          "labels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Label"
            }
          },
          "members": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/UserRef"
            }
          },
          "checklist_stats": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ChecklistStats"
              },
              {
                "type": "null"
              }
            ],
            "description": "`null`, когда у карточки нет ни одного чек-листа."
          },
          "comments_count": {
            "type": "integer",
            "minimum": 0
          },
          "attachments_count": {
            "type": "integer",
            "minimum": 0
          },
          "subtasks_count": {
            "type": "integer",
            "minimum": 0
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          },
          "updated_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "CardRef": {
        "type": "object",
        "description": "Компактная карточка — для подзадач и связей.",
        "required": [
          "id",
          "name",
          "list_id",
          "is_archived",
          "due_date"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "name": {
            "type": "string"
          },
          "list_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "is_archived": {
            "type": "boolean"
          },
          "due_date": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "Relation": {
        "type": "object",
        "description": "Связь двух карточек, увиденная со стороны текущей.",
        "required": [
          "id",
          "type",
          "direction",
          "card",
          "created_at"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "type": {
            "$ref": "#/components/schemas/RelationType"
          },
          "direction": {
            "type": "string",
            "description": "`out` — связь исходит от этой карточки, `in` — направлена к ней.",
            "enum": [
              "out",
              "in"
            ]
          },
          "card": {
            "$ref": "#/components/schemas/CardRef"
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "ChecklistItem": {
        "type": "object",
        "description": "Одна строка с галочкой.",
        "required": [
          "id",
          "checklist_id",
          "text",
          "is_checked",
          "position"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "checklist_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "is_checked": {
            "type": "boolean"
          },
          "position": {
            "$ref": "#/components/schemas/Position"
          }
        }
      },
      "Checklist": {
        "type": "object",
        "description": "Группа пунктов внутри карточки.",
        "required": [
          "id",
          "card_id",
          "name",
          "position",
          "items",
          "checked",
          "total"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "card_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "position": {
            "$ref": "#/components/schemas/Position"
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChecklistItem"
            }
          },
          "checked": {
            "type": "integer",
            "minimum": 0
          },
          "total": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "Comment": {
        "type": "object",
        "description": "Сообщение на карточке. `text` — исходный Markdown, `text_html` — его\nсанированный рендер, собранный на выдаче.\n",
        "required": [
          "id",
          "card_id",
          "author",
          "text",
          "text_html",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "card_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "author": {
            "$ref": "#/components/schemas/UserRef"
          },
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 16000
          },
          "text_html": {
            "type": "string"
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          },
          "updated_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "Attachment": {
        "type": "object",
        "description": "Файл на карточке.",
        "required": [
          "id",
          "card_id",
          "uploaded_by",
          "name",
          "url",
          "mime",
          "size_bytes",
          "is_image",
          "created_at"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "card_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "uploaded_by": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UserRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "`null`, если загрузивший пользователь удалён."
          },
          "name": {
            "type": "string",
            "maxLength": 255,
            "description": "Имя файла у клиента; только для показа."
          },
          "url": {
            "type": "string",
            "description": "Путь скачивания — `/api/v1/attachments/{id}`."
          },
          "mime": {
            "type": "string",
            "description": "Тип, определённый по содержимому, а не по заголовку клиента."
          },
          "size_bytes": {
            "type": "integer",
            "format": "int64",
            "minimum": 0
          },
          "is_image": {
            "type": "boolean"
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "Activity": {
        "type": "object",
        "description": "Одна строка истории карточки.",
        "required": [
          "id",
          "card_id",
          "actor",
          "action_type",
          "description",
          "created_at"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "card_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "actor": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UserRef"
              },
              {
                "type": "null"
              }
            ]
          },
          "action_type": {
            "type": "string",
            "description": "Машинный код события.",
            "examples": [
              "created",
              "updated",
              "moved",
              "archived",
              "restored",
              "labeled",
              "membered",
              "related"
            ]
          },
          "description": {
            "type": "string",
            "description": "Человекочитаемое описание на русском."
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "CardDetail": {
        "description": "Карточка со всеми коллекциями — то, что открывает модальное окно.",
        "allOf": [
          {
            "$ref": "#/components/schemas/Card"
          },
          {
            "type": "object",
            "required": [
              "checklists",
              "comments",
              "attachments",
              "activities",
              "subtasks",
              "relations"
            ],
            "properties": {
              "checklists": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Checklist"
                }
              },
              "comments": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Comment"
                }
              },
              "attachments": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Attachment"
                }
              },
              "activities": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Activity"
                }
              },
              "subtasks": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CardRef"
                }
              },
              "relations": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Relation"
                }
              }
            }
          }
        ]
      },
      "Notification": {
        "type": "object",
        "description": "Событие на карточке, адресованное вызывающему.",
        "required": [
          "id",
          "is_read",
          "created_at",
          "activity",
          "card",
          "board"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "is_read": {
            "type": "boolean"
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          },
          "activity": {
            "$ref": "#/components/schemas/Activity"
          },
          "card": {
            "$ref": "#/components/schemas/CardRef"
          },
          "board": {
            "$ref": "#/components/schemas/BoardRef"
          }
        }
      },
      "Workspace": {
        "type": "object",
        "description": "Контейнер для досок.",
        "required": [
          "id",
          "name",
          "description",
          "owner",
          "my_role",
          "boards_count",
          "created_at"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": "string"
          },
          "owner": {
            "$ref": "#/components/schemas/UserRef"
          },
          "my_role": {
            "type": "string",
            "description": "Роль вызывающего в пространстве."
          },
          "boards_count": {
            "type": "integer",
            "minimum": 0
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "Me": {
        "type": "object",
        "description": "Владелец учётных данных и что они позволяют.",
        "required": [
          "user",
          "scopes",
          "auth"
        ],
        "properties": {
          "user": {
            "$ref": "#/components/schemas/UserRef"
          },
          "scopes": {
            "type": "array",
            "description": "Скоупы токена; для куки-сессии — полный набор.\n",
            "items": {
              "$ref": "#/components/schemas/Scope"
            }
          },
          "auth": {
            "type": "string",
            "description": "Каким способом аутентифицирован запрос.",
            "enum": [
              "session",
              "token"
            ]
          }
        }
      },
      "TokenInfo": {
        "type": "object",
        "description": "Персональный токен в списке — без секрета. Управление токенами живёт вне\nпубличного API v1.\n",
        "required": [
          "id",
          "name",
          "prefix",
          "scopes",
          "created_at",
          "expires_at",
          "last_used_at",
          "revoked_at"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/UUID"
          },
          "name": {
            "type": "string",
            "description": "Человеческое имя токена."
          },
          "prefix": {
            "type": "string",
            "description": "Открытая часть токена, например `trl_pat_a1B2c3`."
          },
          "scopes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Scope"
            }
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          },
          "expires_at": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "last_used_at": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "revoked_at": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "TokenCreated": {
        "description": "Только что созданный токен: единственный момент, когда виден его\nоткрытый текст.\n",
        "allOf": [
          {
            "$ref": "#/components/schemas/TokenInfo"
          },
          {
            "type": "object",
            "required": [
              "token"
            ],
            "properties": {
              "token": {
                "type": "string",
                "description": "Открытый токен `trl_pat_…`; восстановить его позже нельзя."
              }
            }
          }
        ]
      },
      "BoardEvent": {
        "type": "object",
        "description": "Один кадр потока SSE — строка `board_events`.",
        "required": [
          "id",
          "board_id",
          "actor_id",
          "client_id",
          "entity_type",
          "entity_id",
          "action",
          "payload",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Первичный ключ события; он же `Last-Event-ID`."
          },
          "board_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "actor_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UUID"
              },
              {
                "type": "null"
              }
            ]
          },
          "client_id": {
            "type": "string",
            "description": "Метка вкладки-инициатора из `X-Client-Id`; эхо-фильтр."
          },
          "entity_type": {
            "type": "string",
            "enum": [
              "board",
              "list",
              "card",
              "label",
              "checklist",
              "checklist_item",
              "comment",
              "attachment",
              "member"
            ]
          },
          "entity_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "action": {
            "type": "string",
            "enum": [
              "create",
              "update",
              "delete",
              "move",
              "archive",
              "restore"
            ]
          },
          "payload": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": true,
            "description": "Подробности изменения; состав зависит от `entity_type` и `action`."
          },
          "created_at": {
            "$ref": "#/components/schemas/Timestamp"
          }
        }
      },
      "FieldError": {
        "type": "object",
        "description": "Одно невалидное поле запроса.",
        "required": [
          "field",
          "detail"
        ],
        "properties": {
          "field": {
            "type": "string",
            "description": "Имя поля, как оно называется в теле запроса."
          },
          "detail": {
            "type": "string",
            "description": "Что именно не так."
          }
        }
      },
      "Problem": {
        "type": "object",
        "description": "Ошибка в формате RFC 9457, `application/problem+json`. Единственная\nформа ошибки во всём API.\n",
        "required": [
          "type",
          "title",
          "status"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "URI вида `https://trololo.telco-system.com/problems/<slug>`, где\nslug — устойчивый машинный код ошибки.\n",
            "examples": [
              "https://trololo.telco-system.com/problems/not-found",
              "https://trololo.telco-system.com/problems/validation-failed"
            ]
          },
          "title": {
            "type": "string",
            "description": "Краткое название статуса."
          },
          "status": {
            "type": "integer",
            "minimum": 100,
            "maximum": 599
          },
          "detail": {
            "type": "string",
            "description": "Пояснение для конкретного случая."
          },
          "instance": {
            "type": "string",
            "description": "Путь запроса, на котором произошла ошибка."
          },
          "errors": {
            "type": "array",
            "description": "Ошибки по полям; присутствует только у `422`.",
            "items": {
              "$ref": "#/components/schemas/FieldError"
            }
          }
        }
      },
      "Page": {
        "type": "object",
        "description": "Конверт любого списочного ответа. `items` всегда массив (никогда\n`null`), `next_cursor` — курсор следующей страницы или `null`, если\nстраница последняя.\n",
        "required": [
          "items",
          "next_cursor"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {}
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "PageOfBoard": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Page"
          },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Board"
                }
              }
            }
          }
        ]
      },
      "PageOfWorkspace": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Page"
          },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Workspace"
                }
              }
            }
          }
        ]
      },
      "PageOfBoardMember": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Page"
          },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/BoardMember"
                }
              }
            }
          }
        ]
      },
      "PageOfLabel": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Page"
          },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Label"
                }
              }
            }
          }
        ]
      },
      "PageOfList": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Page"
          },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/List"
                }
              }
            }
          }
        ]
      },
      "PageOfCard": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Page"
          },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Card"
                }
              }
            }
          }
        ]
      },
      "PageOfRelation": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Page"
          },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Relation"
                }
              }
            }
          }
        ]
      },
      "PageOfActivity": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Page"
          },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Activity"
                }
              }
            }
          }
        ]
      },
      "PageOfChecklist": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Page"
          },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Checklist"
                }
              }
            }
          }
        ]
      },
      "PageOfChecklistItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Page"
          },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ChecklistItem"
                }
              }
            }
          }
        ]
      },
      "PageOfComment": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Page"
          },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Comment"
                }
              }
            }
          }
        ]
      },
      "PageOfAttachment": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Page"
          },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Attachment"
                }
              }
            }
          }
        ]
      },
      "PageOfNotification": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Page"
          },
          {
            "type": "object",
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Notification"
                }
              }
            }
          }
        ]
      },
      "WorkspaceCreate": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": "string"
          }
        }
      },
      "WorkspacePatch": {
        "type": "object",
        "description": "Присланные поля меняются, остальные остаются как были.",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": "string"
          }
        }
      },
      "WorkspaceMemberCreate": {
        "type": "object",
        "description": "Приглашаемого называют либо `user_id`, либо `username`.",
        "properties": {
          "user_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "username": {
            "type": "string"
          },
          "role": {
            "$ref": "#/components/schemas/WorkspaceRole"
          }
        },
        "anyOf": [
          {
            "required": [
              "user_id"
            ]
          },
          {
            "required": [
              "username"
            ]
          }
        ]
      },
      "BoardCreate": {
        "type": "object",
        "required": [
          "workspace_id",
          "name"
        ],
        "properties": {
          "workspace_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": "string"
          },
          "background_color": {
            "allOf": [
              {
                "$ref": "#/components/schemas/HexColor"
              }
            ],
            "default": "#0079BF"
          },
          "visibility": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Visibility"
              }
            ],
            "default": "private"
          }
        }
      },
      "BoardPatch": {
        "type": "object",
        "description": "Присланные поля меняются, остальные остаются как были.",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "description": {
            "type": "string"
          },
          "background_color": {
            "$ref": "#/components/schemas/HexColor"
          },
          "visibility": {
            "$ref": "#/components/schemas/Visibility"
          }
        }
      },
      "BoardMemberCreate": {
        "type": "object",
        "description": "Приглашаемого называют либо `user_id`, либо `username`.",
        "properties": {
          "user_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "username": {
            "type": "string"
          },
          "role": {
            "allOf": [
              {
                "$ref": "#/components/schemas/BoardRole"
              }
            ],
            "default": "normal"
          }
        },
        "anyOf": [
          {
            "required": [
              "user_id"
            ]
          },
          {
            "required": [
              "username"
            ]
          }
        ]
      },
      "BoardMemberPatch": {
        "type": "object",
        "description": "Новая роль участника на доске.",
        "required": [
          "role"
        ],
        "properties": {
          "role": {
            "$ref": "#/components/schemas/BoardRole"
          }
        }
      },
      "LabelCreate": {
        "type": "object",
        "required": [
          "color"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100,
            "default": ""
          },
          "color": {
            "$ref": "#/components/schemas/LabelColor"
          }
        }
      },
      "LabelPatch": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100
          },
          "color": {
            "$ref": "#/components/schemas/LabelColor"
          }
        }
      },
      "ListCreate": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "color": {
            "$ref": "#/components/schemas/HexColor"
          }
        }
      },
      "ListPatch": {
        "type": "object",
        "description": "Пустой патч допустим и ничего не меняет.",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "color": {
            "$ref": "#/components/schemas/HexColor"
          }
        }
      },
      "ListMove": {
        "type": "object",
        "description": "Соседи по доске. Оба `null` или пустое тело — «в конец». Колонки не\nпереезжают между досками, поэтому `board_id` здесь нет.\n",
        "properties": {
          "after_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UUID"
              },
              {
                "type": "null"
              }
            ],
            "description": "Колонка, ПОСЛЕ которой встать."
          },
          "before_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UUID"
              },
              {
                "type": "null"
              }
            ],
            "description": "Колонка, ПЕРЕД которой встать."
          }
        }
      },
      "ListCopy": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Имя копии; по умолчанию — имя оригинала."
          }
        }
      },
      "MoveAllCards": {
        "type": "object",
        "required": [
          "target_list_id"
        ],
        "properties": {
          "target_list_id": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UUID"
              }
            ],
            "description": "Колонка-приёмник на той же доске."
          }
        }
      },
      "SortCards": {
        "type": "object",
        "required": [
          "sort_by"
        ],
        "properties": {
          "sort_by": {
            "type": "string",
            "enum": [
              "name",
              "created",
              "due_date"
            ]
          },
          "direction": {
            "type": "string",
            "enum": [
              "asc",
              "desc"
            ],
            "default": "asc"
          }
        }
      },
      "CardCreate": {
        "type": "object",
        "description": "Карточка встаёт в конец указанной колонки.",
        "required": [
          "list_id",
          "name"
        ],
        "properties": {
          "list_id": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UUID"
              }
            ],
            "description": "Колонка, в которую добавляется карточка."
          },
          "parent_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UUID"
              },
              {
                "type": "null"
              }
            ],
            "description": "Родительская карточка той же доски, если создаётся подзадача. У\nподзадачи не может быть своих подзадач.\n"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "description": {
            "type": "string"
          },
          "due_date": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "due_complete": {
            "type": "boolean",
            "default": false
          },
          "cover_color": {
            "$ref": "#/components/schemas/HexColor"
          }
        }
      },
      "CardCreateInList": {
        "type": "object",
        "description": "То же, что `CardCreate`, но колонка берётся из пути. Поле `list_id`\nдопустимо, только если совпадает с ней.\n",
        "required": [
          "name"
        ],
        "properties": {
          "list_id": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UUID"
              }
            ],
            "description": "Должно совпадать с колонкой из пути, если прислано."
          },
          "parent_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UUID"
              },
              {
                "type": "null"
              }
            ]
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "description": {
            "type": "string"
          },
          "due_date": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "due_complete": {
            "type": "boolean",
            "default": false
          },
          "cover_color": {
            "$ref": "#/components/schemas/HexColor"
          }
        }
      },
      "SubtaskCreate": {
        "type": "object",
        "description": "Подзадача создаётся в колонке родителя; `list_id` допустим, только если\nсовпадает с ней, `parent_id` — только если совпадает с путём.\n",
        "required": [
          "name"
        ],
        "properties": {
          "list_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "parent_id": {
            "$ref": "#/components/schemas/UUID"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "description": {
            "type": "string"
          },
          "due_date": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "due_complete": {
            "type": "boolean",
            "default": false
          },
          "cover_color": {
            "$ref": "#/components/schemas/HexColor"
          }
        }
      },
      "CardPatch": {
        "type": "object",
        "description": "Отсутствующее поле не трогается. `due_date: null` снимает срок —\nв этом и разница между «поля нет» и «поле равно null».\n",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "description": {
            "type": "string"
          },
          "due_date": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Timestamp"
              },
              {
                "type": "null"
              }
            ]
          },
          "due_complete": {
            "type": "boolean"
          },
          "cover_color": {
            "$ref": "#/components/schemas/HexColor"
          },
          "is_archived": {
            "type": "boolean"
          }
        }
      },
      "CardMove": {
        "type": "object",
        "description": "Соседи, а не позиция. Оба `null` — «в конец колонки». Карточка не может\nбыть соседом самой себе, и `after_id` не может равняться `before_id`.\n",
        "properties": {
          "list_id": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UUID"
              }
            ],
            "description": "Колонка-приёмник **той же доски**. Отсутствует — карточка остаётся\nв своей колонке.\n"
          },
          "after_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UUID"
              },
              {
                "type": "null"
              }
            ],
            "description": "Карточка, ПОСЛЕ которой встать."
          },
          "before_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UUID"
              },
              {
                "type": "null"
              }
            ],
            "description": "Карточка, ПЕРЕД которой встать."
          }
        }
      },
      "RelationCreate": {
        "type": "object",
        "required": [
          "target_id"
        ],
        "properties": {
          "target_id": {
            "allOf": [
              {
                "$ref": "#/components/schemas/UUID"
              }
            ],
            "description": "Карточка на другом конце связи; не может быть текущей."
          },
          "type": {
            "allOf": [
              {
                "$ref": "#/components/schemas/RelationType"
              }
            ],
            "default": "related"
          }
        }
      },
      "ChecklistCreate": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "after_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UUID"
              },
              {
                "type": "null"
              }
            ],
            "description": "Чек-лист, ПОСЛЕ которого встать."
          },
          "before_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UUID"
              },
              {
                "type": "null"
              }
            ],
            "description": "Чек-лист, ПЕРЕД которым встать."
          }
        }
      },
      "ChecklistPatch": {
        "type": "object",
        "description": "Требуется хотя бы одно поле.",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "after_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UUID"
              },
              {
                "type": "null"
              }
            ]
          },
          "before_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UUID"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "anyOf": [
          {
            "required": [
              "name"
            ]
          },
          {
            "required": [
              "after_id"
            ]
          },
          {
            "required": [
              "before_id"
            ]
          }
        ]
      },
      "ChecklistItemCreate": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "after_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UUID"
              },
              {
                "type": "null"
              }
            ]
          },
          "before_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UUID"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "ChecklistItemPatch": {
        "type": "object",
        "description": "Требуется хотя бы одно поле.",
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "is_checked": {
            "type": "boolean"
          },
          "after_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UUID"
              },
              {
                "type": "null"
              }
            ]
          },
          "before_id": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/UUID"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "anyOf": [
          {
            "required": [
              "text"
            ]
          },
          {
            "required": [
              "is_checked"
            ]
          },
          {
            "required": [
              "after_id"
            ]
          },
          {
            "required": [
              "before_id"
            ]
          }
        ]
      },
      "CommentBody": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "minLength": 1,
            "maxLength": 16000,
            "description": "Markdown; HTML собирается сервером и санируется."
          }
        }
      }
    }
  }
}
