{
  "openapi": "3.1.0",
  "info": {
    "title": "P2P Messaging API",
    "description": "Decentralized peer-to-peer messaging protocol API",
    "license": {
      "name": ""
    },
    "version": "0.1.0"
  },
  "servers": [
    {
      "url": "http://localhost:3000",
      "description": "Local development server"
    }
  ],
  "paths": {
    "/conversations": {
      "get": {
        "tags": [
          "chats"
        ],
        "operationId": "get_user_chats",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "Max number of chats to return (default 50, max 500)",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "after",
            "in": "query",
            "description": "Pagination cursor (hex)",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "User chats retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListUserChatsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden - user mismatch",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "X-User": []
          },
          {
            "X-Ts": []
          },
          {
            "X-Node": []
          },
          {
            "X-Sig": []
          },
          {
            "X-Sig-Version": []
          }
        ]
      }
    },
    "/dialogs/{peer}/messages": {
      "get": {
        "tags": [
          "messages"
        ],
        "summary": "GET /dm/{peer}/messages — чтение DM с конкретным собеседником",
        "description": "Безопасность: user аутентифицирован через подпись (X-Sig),\nchat_id вычисляется из (user, peer) — посторонний не может читать чужие DM",
        "operationId": "get_dialog_messages",
        "parameters": [
          {
            "name": "peer",
            "in": "path",
            "description": "Peer address (hex 0x...)",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "path",
            "required": true,
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "format": "int64",
              "minimum": 0
            }
          },
          {
            "name": "to",
            "in": "path",
            "required": true,
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "format": "int64",
              "minimum": 0
            }
          },
          {
            "name": "after",
            "in": "path",
            "required": true,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "name": "limit",
            "in": "path",
            "required": true,
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Messages retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetChatRangeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "X-User": []
          },
          {
            "X-Ts": []
          },
          {
            "X-Node": []
          },
          {
            "X-Sig": []
          },
          {
            "X-Sig-Version": []
          }
        ]
      },
      "post": {
        "tags": [
          "dialogs"
        ],
        "operationId": "post_direct_message",
        "parameters": [
          {
            "name": "peer",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostDirectMessageBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Message sent successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostDirectMessageResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden - sender mismatch",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "X-User": []
          },
          {
            "X-Ts": []
          },
          {
            "X-Node": []
          },
          {
            "X-Sig": []
          },
          {
            "X-Sig-Version": []
          }
        ]
      }
    },
    "/dialogs/{peer}/messages/control": {
      "post": {
        "tags": [
          "messages"
        ],
        "operationId": "post_control_direct_message",
        "parameters": [
          {
            "name": "peer",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostControlDirectMessageBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Control message sent successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostControlMessageResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Forbidden - sender mismatch",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "X-User": []
          },
          {
            "X-Ts": []
          },
          {
            "X-Node": []
          },
          {
            "X-Sig": []
          },
          {
            "X-Sig-Version": []
          }
        ]
      }
    },
    "/dialogs/{peer}/messages/read": {
      "post": {
        "tags": [
          "messages"
        ],
        "operationId": "read_dialog_message",
        "parameters": [
          {
            "name": "peer",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReadChatMessageBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Message read successfully"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "X-User": []
          },
          {
            "X-Ts": []
          },
          {
            "X-Node": []
          },
          {
            "X-Sig": []
          },
          {
            "X-Sig-Version": []
          }
        ]
      }
    },
    "/groups/{chat_id}/members": {
      "get": {
        "tags": [
          "groups"
        ],
        "summary": "GET /groups/{chat_id}/members -- list group members with roles.",
        "description": "Pure local read from members CF -- no gossip roundtrip needed.\nOnly current members can view the member list (is_member gate\nin the MPSC handler).\n\n# Errors\n\n* 400 -- bad hex\n* 401 -- unauthorized\n* 500 -- internal error or non-member rejection",
        "operationId": "get_group_members_handler",
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "description": "Group chat ID (hex 0x...)",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Members list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GroupMembersResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "X-User": []
          },
          {
            "X-Ts": []
          },
          {
            "X-Node": []
          },
          {
            "X-Sig": []
          },
          {
            "X-Sig-Version": []
          }
        ]
      }
    },
    "/groups/{chat_id}/membership": {
      "delete": {
        "tags": [
          "groups"
        ],
        "summary": "DELETE /groups/{chat_id}/membership -- leave a group (D-09).",
        "description": "Sender must be a non-admin member. Admin cannot leave their\nown group (D-11). Inbox entry remains after leave (D-10).\n\n# Errors\n\n* 400 -- bad hex input\n* 401 -- unauthorized or bad ECDSA signature\n* 403 -- admin cannot leave\n* 500 -- internal error",
        "operationId": "leave_group",
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LeaveGroupRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Left group"
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "403": {
            "description": "Admin cannot leave",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "X-User": []
          },
          {
            "X-Ts": []
          },
          {
            "X-Node": []
          },
          {
            "X-Sig": []
          },
          {
            "X-Sig-Version": []
          }
        ]
      }
    },
    "/groups/{chat_id}/messages": {
      "get": {
        "tags": [
          "groups"
        ],
        "summary": "GET /groups/{chat_id}/messages -- read group message history.",
        "description": "Mirrors `get_dialog_messages` but takes chat_id from path and\nsets `skip_membership_check: false` so the MPSC handler checks\nmembership in the members CF. Non-members receive an empty list.\n\n# Errors\n\n* 400 -- validation error\n* 401 -- unauthorized\n* 500 -- internal error",
        "operationId": "get_group_messages",
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "description": "Group chat ID (hex 0x...)",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "path",
            "required": true,
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "format": "int64",
              "minimum": 0
            }
          },
          {
            "name": "to",
            "in": "path",
            "required": true,
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "format": "int64",
              "minimum": 0
            }
          },
          {
            "name": "after",
            "in": "path",
            "required": true,
            "schema": {
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "name": "limit",
            "in": "path",
            "required": true,
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "minimum": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Messages retrieved",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetChatRangeResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "X-User": []
          },
          {
            "X-Ts": []
          },
          {
            "X-Node": []
          },
          {
            "X-Sig": []
          },
          {
            "X-Sig-Version": []
          }
        ]
      },
      "post": {
        "tags": [
          "groups"
        ],
        "summary": "POST /groups/{chat_id}/messages -- send a text message to a group.",
        "description": "Mirrors `post_direct_message` but takes chat_id from path (group\nchat_id is not derivable from user addresses). Membership check\nhappens in the MPSC handler (is_member gate in put_message).\n\n# Errors\n\n* 400 -- validation error (empty text, bad hex)\n* 401 -- unauthorized\n* 500 -- internal error or non-member rejection",
        "operationId": "post_group_message",
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostGroupMessageBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Message sent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostDirectMessageResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "X-User": []
          },
          {
            "X-Ts": []
          },
          {
            "X-Node": []
          },
          {
            "X-Sig": []
          },
          {
            "X-Sig-Version": []
          }
        ]
      }
    },
    "/groups/{chat_id}/messages/control": {
      "post": {
        "tags": [
          "groups"
        ],
        "summary": "POST /groups/{chat_id}/messages/control -- send a control message\nto a group (E2EE handshake, key rotation, etc.).",
        "description": "Mirrors `post_control_direct_message` but takes chat_id from path.\n\n# Errors\n\n* 400 -- validation error (bad hex, msg_type out of range)\n* 401 -- unauthorized\n* 500 -- internal error or non-member rejection",
        "operationId": "post_control_group_message",
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostControlGroupMessageBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Control message sent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostControlMessageResponse"
                }
              }
            }
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "X-User": []
          },
          {
            "X-Ts": []
          },
          {
            "X-Node": []
          },
          {
            "X-Sig": []
          },
          {
            "X-Sig-Version": []
          }
        ]
      }
    },
    "/groups/{chat_id}/messages/read": {
      "post": {
        "tags": [
          "groups"
        ],
        "summary": "POST /groups/{chat_id}/messages/read -- mark group messages as\nread up to a given sequence number.",
        "description": "Mirrors `read_dialog_message` but takes chat_id from path and\nsets `skip_membership_check: false` for explicit membership\nverification.\n\n# Errors\n\n* 400 -- validation error\n* 401 -- unauthorized\n* 500 -- internal error or non-member rejection",
        "operationId": "read_group_message",
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReadChatMessageBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Read progress updated"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "X-User": []
          },
          {
            "X-Ts": []
          },
          {
            "X-Node": []
          },
          {
            "X-Sig": []
          },
          {
            "X-Sig-Version": []
          }
        ]
      }
    },
    "/groups/{chat_id}/ops": {
      "post": {
        "tags": [
          "groups"
        ],
        "summary": "POST /groups/{chat_id}/ops -- compound membership operation (D-14).",
        "description": "Accepts one or more membership ops (add/remove/create) with optional\naccompanying messages (MLS Welcome/Commit). The node unfolds the\nbatch into individual gossip messages (D-24).\n\nFor Create ops, the request must include a `nonce` field (D-04).\nThe node verifies that `chat_id == blake3(domain || signer || nonce)`\nbefore accepting the operation (D-02, D-03, D-05).\n\n# Authorization\n\nEach operation carries its own ECDSA signature. The API verifies\nsignatures cryptographically (D-13). Role-based authorization\n(admin check) is performed by the MPSC handler which has DB access.\n\n# Errors\n\n* 400 -- empty ops, bad hex, unknown op_type, missing nonce,\n         chat_id mismatch\n* 401 -- invalid ECDSA signature\n* 500 -- channel error",
        "operationId": "handle_membership",
        "parameters": [
          {
            "name": "chat_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CompoundMembershipRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Membership ops processed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MembershipResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized (bad signature)"
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "X-User": []
          },
          {
            "X-Ts": []
          },
          {
            "X-Node": []
          },
          {
            "X-Sig": []
          },
          {
            "X-Sig-Version": []
          }
        ]
      }
    },
    "/identity": {
      "put": {
        "tags": [
          "identity"
        ],
        "summary": "Start the HTTP API server.",
        "description": "# Arguments\n\n",
        "operationId": "put_identity",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetIdentityBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Identity stored"
          },
          "400": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          }
        },
        "security": [
          {
            "X-User": []
          },
          {
            "X-Sig": []
          }
        ]
      }
    },
    "/identity/{address}": {
      "get": {
        "tags": [
          "identity"
        ],
        "operationId": "get_identity",
        "parameters": [
          {
            "name": "address",
            "in": "path",
            "description": "Hex user address (40 chars)",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Identity found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetIdentityResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Identity not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiErrorResponse"
                }
              }
            }
          }
        },
        "security": [
          {
            "X-User": []
          },
          {
            "X-Sig": []
          }
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "AccompanyingMessage": {
        "type": "object",
        "description": "Accompanying message to send after membership ops (D-19).\n\nCarries MLS data (Welcome, Commit) as an opaque control payload.\nSent as regular `PutMessage` after membership ops complete.",
        "properties": {
          "control": {
            "type": [
              "string",
              "null"
            ],
            "description": "Control payload (base64-encoded CBOR), optional"
          },
          "msg_type": {
            "type": "integer",
            "format": "int32",
            "description": "Client-defined msg_type (opaque u8)",
            "minimum": 0
          },
          "recipients": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Recipients for inbox fanout (hex addresses).\nIf empty, fanout to all group members."
          },
          "text": {
            "type": "string",
            "description": "Message text (may be empty for control messages)"
          }
        }
      },
      "ApiErrorResponse": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "example": "forbidden"
          }
        }
      },
      "ChatKindDto": {
        "oneOf": [
          {
            "type": "object",
            "description": "Личные сообщения",
            "required": [
              "peer",
              "type"
            ],
            "properties": {
              "peer": {
                "type": "string",
                "description": "Собеседник (hex 0x...)",
                "example": "0xabcdef1234567890abcdef1234567890abcdef12"
              },
              "type": {
                "type": "string",
                "enum": [
                  "dm"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "Групповой чат",
            "required": [
              "type"
            ],
            "properties": {
              "title": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "My Group Chat"
              },
              "type": {
                "type": "string",
                "enum": [
                  "group"
                ]
              }
            }
          },
          {
            "type": "object",
            "description": "Канал",
            "required": [
              "type"
            ],
            "properties": {
              "title": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "Announcements"
              },
              "type": {
                "type": "string",
                "enum": [
                  "channel"
                ]
              }
            }
          }
        ],
        "description": "Тип чата для API ответов (JSON)"
      },
      "ChatMessage": {
        "type": "object",
        "required": [
          "key",
          "msg_cbor"
        ],
        "properties": {
          "key": {
            "type": "string",
            "example": "0x1234567890abcdef1234567890abcdef"
          },
          "msg_cbor": {
            "type": "string",
            "example": "0xa3646368617478207468697320697320612074657374206d657373616765"
          }
        }
      },
      "CompoundMembershipRequest": {
        "type": "object",
        "description": "Compound membership request body (D-19, D-23).\n\nBundles one or more membership operations with optional\naccompanying messages (e.g. MLS Welcome/Commit) in a single\nHTTP call. The node unfolds this into N separate gossip messages.",
        "required": [
          "ops"
        ],
        "properties": {
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AccompanyingMessage"
            },
            "description": "Optional accompanying messages (D-19: Welcome/Commit data)"
          },
          "nonce": {
            "type": [
              "string",
              "null"
            ],
            "description": "16-byte hex nonce for group creation (D-04). Required when\nops contain a \"create\" op. Hex string (32 chars = 16 bytes)."
          },
          "ops": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MembershipOpEntry"
            },
            "description": "One or more membership operations (D-23: batch support)"
          }
        }
      },
      "FieldValidationError": {
        "allOf": [
          {},
          {
            "type": "object",
            "required": [
              "msg"
            ],
            "properties": {
              "msg": {
                "type": "string"
              }
            }
          }
        ]
      },
      "GetChatRangeQueryParams": {
        "type": "object",
        "properties": {
          "after": {
            "type": [
              "string",
              "null"
            ],
            "example": "0x1234567890abcdef"
          },
          "from": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "example": 1699900000000,
            "minimum": 0
          },
          "limit": {
            "type": [
              "integer",
              "null"
            ],
            "example": 100,
            "minimum": 0
          },
          "to": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64",
            "example": 1699999999999,
            "minimum": 0
          }
        }
      },
      "GetChatRangeResponse": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ChatMessage"
            }
          },
          "next_after": {
            "type": [
              "string",
              "null"
            ],
            "example": "0x1234567890abcdef"
          }
        }
      },
      "GetIdentityResponse": {
        "type": "object",
        "description": "Response body for GET /identity/{address}.",
        "required": [
          "identity"
        ],
        "properties": {
          "identity": {
            "type": "string",
            "description": "Base64-encoded opaque identity blob.",
            "example": "SGVsbG8gV29ybGQ="
          }
        }
      },
      "GroupMember": {
        "type": "object",
        "description": "A group member with address and role.",
        "required": [
          "address",
          "role"
        ],
        "properties": {
          "address": {
            "type": "string",
            "description": "Member address (hex 0x...)",
            "example": "0x1234567890abcdef1234567890abcdef12345678"
          },
          "role": {
            "type": "integer",
            "format": "int32",
            "description": "Role: 0 = participant, 1 = admin",
            "example": 0,
            "minimum": 0
          }
        }
      },
      "GroupMembersResponse": {
        "type": "object",
        "description": "Response for GET /groups/{chat_id}/members.",
        "required": [
          "members"
        ],
        "properties": {
          "members": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/GroupMember"
            }
          }
        }
      },
      "InboxChat": {
        "type": "object",
        "required": [
          "chat_id",
          "kind",
          "last_ts",
          "last_sender",
          "last_text_preview",
          "unread",
          "cursor"
        ],
        "properties": {
          "chat_id": {
            "type": "string",
            "example": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
          },
          "cursor": {
            "type": "string",
            "example": "0x1234567890abcdef"
          },
          "kind": {
            "$ref": "#/components/schemas/ChatKindDto",
            "description": "Тип чата с peer/title"
          },
          "last_sender": {
            "type": "string",
            "example": "0x1234567890abcdef1234567890abcdef12345678"
          },
          "last_text_preview": {
            "type": "string",
            "example": "Hey, how are you?"
          },
          "last_ts": {
            "type": "integer",
            "format": "int64",
            "example": 1699900000000,
            "minimum": 0
          },
          "unread": {
            "type": "integer",
            "format": "int32",
            "example": 3,
            "minimum": 0
          }
        }
      },
      "LeaveGroupRequest": {
        "type": "object",
        "description": "Request body for DELETE /groups/{chat_id}/membership (D-09).\n\nContains ECDSA signature for gossip propagation (MembershipOp\nrequires sig for independent verification by other nodes, D-13).\nTarget is implied (sender = target for self-remove).",
        "required": [
          "sig"
        ],
        "properties": {
          "sig": {
            "type": "string",
            "description": "ECDSA sig over keccak256(chat_id || sender || 1_u8)\nwhere 1 = Remove op_type"
          }
        }
      },
      "ListUserChatsQueryParams": {
        "type": "object",
        "properties": {
          "after": {
            "type": [
              "string",
              "null"
            ],
            "example": "0x1234567890abcdef"
          },
          "limit": {
            "type": [
              "integer",
              "null"
            ],
            "example": 50,
            "minimum": 0
          }
        }
      },
      "ListUserChatsResponse": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InboxChat"
            }
          },
          "next_after": {
            "type": [
              "string",
              "null"
            ],
            "example": "0x1234567890abcdef"
          }
        }
      },
      "MembershipOpEntry": {
        "type": "object",
        "description": "Single membership operation in a batch (D-23).\n\nPart of `CompoundMembershipRequest.ops`. Each op carries its own\nECDSA signature so the node can independently verify the signer.",
        "required": [
          "target",
          "sig",
          "op_type"
        ],
        "properties": {
          "op_type": {
            "type": "string",
            "description": "Operation type: \"add\", \"remove\", or \"create\"",
            "example": "add"
          },
          "role": {
            "type": "integer",
            "format": "int32",
            "description": "Role for target: 0 = participant, 1 = admin",
            "example": 0,
            "minimum": 0
          },
          "sig": {
            "type": "string",
            "description": "ECDSA signature over keccak256(chat_id || target || op_type_byte), hex",
            "example": "0xabcdef"
          },
          "target": {
            "type": "string",
            "description": "Target user address (hex, 20 bytes)",
            "example": "0x1234567890abcdef1234567890abcdef12345678"
          }
        }
      },
      "MembershipResponse": {
        "type": "object",
        "description": "Response for compound membership endpoint.",
        "required": [
          "ops_processed",
          "messages_sent"
        ],
        "properties": {
          "messages_sent": {
            "type": "integer",
            "description": "Number of messages sent",
            "minimum": 0
          },
          "ops_processed": {
            "type": "integer",
            "description": "Number of membership ops processed",
            "minimum": 0
          }
        }
      },
      "PostControlDirectMessageBody": {
        "type": "object",
        "description": "Request body for sending control messages (E2EE handshake, key rotation, etc.)",
        "required": [
          "msg_type",
          "control"
        ],
        "properties": {
          "control": {
            "type": "string",
            "description": "CBOR-encoded control payload (base64 string).\nContent depends on msg_type, node doesn't parse it.\nMax 1368 base64 chars = 1024 bytes decoded.",
            "example": "pGplbmNyeXB0aW9u"
          },
          "msg_type": {
            "type": "integer",
            "format": "int32",
            "description": "Control message type (1 = handshake, 2 = key_rotation, etc.)",
            "example": 1,
            "minimum": 0
          }
        }
      },
      "PostControlGroupMessageBody": {
        "type": "object",
        "description": "Request body for sending a control message to a group.",
        "required": [
          "msg_type",
          "control"
        ],
        "properties": {
          "control": {
            "type": "string",
            "description": "CBOR-encoded control payload (base64 string).\nMax 43692 base64 chars = 32 KiB decoded.",
            "example": "pGplbmNyeXB0aW9u"
          },
          "msg_type": {
            "type": "integer",
            "format": "int32",
            "description": "Control message type (1 = handshake, 2 = key_rotation, etc.)",
            "example": 1,
            "minimum": 0
          }
        }
      },
      "PostControlMessageResponse": {
        "type": "object",
        "description": "Response for control message",
        "required": [
          "chat_id",
          "msg_id",
          "ts"
        ],
        "properties": {
          "chat_id": {
            "type": "string",
            "example": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
          },
          "msg_id": {
            "type": "string",
            "example": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
          },
          "ts": {
            "type": "integer",
            "format": "int64",
            "example": 1699900000000,
            "minimum": 0
          }
        }
      },
      "PostDirectMessageBody": {
        "type": "object",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "example": "Hello, world!"
          }
        }
      },
      "PostDirectMessageResponse": {
        "type": "object",
        "required": [
          "chat_id",
          "msg_id",
          "ts"
        ],
        "properties": {
          "chat_id": {
            "type": "string",
            "example": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
          },
          "msg_id": {
            "type": "string",
            "example": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
          },
          "ts": {
            "type": "integer",
            "format": "int64",
            "example": 1699900000000,
            "minimum": 0
          }
        }
      },
      "PostGroupMessageBody": {
        "type": "object",
        "description": "Request body for sending a text message to a group.",
        "required": [
          "text"
        ],
        "properties": {
          "text": {
            "type": "string",
            "description": "Message text",
            "example": "Hello group!"
          }
        }
      },
      "ReadChatMessageBody": {
        "type": "object",
        "required": [
          "seq"
        ],
        "properties": {
          "seq": {
            "type": "integer",
            "format": "int32",
            "example": 123,
            "minimum": 0
          }
        }
      },
      "SetIdentityBody": {
        "type": "object",
        "description": "Request body for PUT /identity.",
        "required": [
          "identity"
        ],
        "properties": {
          "identity": {
            "type": "string",
            "description": "Base64-encoded opaque identity blob (max 1024 bytes raw).",
            "example": "SGVsbG8gV29ybGQ="
          }
        }
      },
      "ValidationErrorResponse": {
        "type": "object",
        "required": [
          "error",
          "fields"
        ],
        "properties": {
          "error": {
            "type": "string",
            "example": "validation_error"
          },
          "fields": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/FieldValidationError"
            },
            "propertyNames": {
              "type": "string"
            },
            "example": {
              "peer": {
                "max": 42,
                "min": 42,
                "msg": "length must be between 42 and 42",
                "value": "0xa60d8c1413640B15aCD25430C96D02b47453d51"
              }
            }
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "p2p-mes",
      "description": "P2P Messaging API"
    }
  ]
}