openapi: 3.1.0 info: title: WebDrop API version: 8.2.0 servers: - url: /api/v1 tags: - name: server - name: channel - name: join - name: frame - name: item - name: share - name: account paths: /accounts/profile: get: tags: [account] parameters: - name: X-WD-Account in: header required: true schema: { type: string } responses: '200': description: 服务端权威数据 content: application/json: schema: $ref: '../contracts/api.schemas.json#/$defs/AccountProfile' '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } patch: tags: [account] parameters: - name: X-WD-Account in: header required: true schema: { type: string } requestBody: required: true content: application/json: schema: { $ref: '../contracts/api.schemas.json#/$defs/AccountProfileRequest' } responses: '200': description: 服务端权威数据 content: application/json: schema: $ref: '../contracts/api.schemas.json#/$defs/AccountProfile' '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /accounts/spaces: get: tags: [account] parameters: - name: X-WD-Account in: header required: true schema: { type: string } responses: '200': description: 服务端权威数据 content: application/json: schema: type: array items: { $ref: '../contracts/api.schemas.json#/$defs/ChannelInfo' } '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /spaces/{id}/structure: get: tags: [channel] parameters: - name: id in: path required: true schema: { type: string } - name: X-WD-Account in: header required: false schema: { type: string } - $ref: '#/components/parameters/Role' - $ref: '#/components/parameters/Account' - name: Authorization in: header schema: { type: string } responses: '200': description: 服务端权威数据 content: application/json: schema: $ref: '../contracts/api.schemas.json#/$defs/WorkspaceBody' '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /spaces/{id}/channels: post: tags: [channel] parameters: - name: id in: path required: true schema: { type: string } - name: X-WD-Account in: header required: true schema: { type: string } - $ref: '#/components/parameters/RoleWriter' - $ref: '#/components/parameters/Account' - name: Authorization in: header schema: { type: string } requestBody: required: true content: application/json: schema: { $ref: '../contracts/api.schemas.json#/$defs/WorkspaceChannelRequest' } responses: '201': description: 服务端权威数据 content: application/json: schema: $ref: '../contracts/api.schemas.json#/$defs/WorkspaceChannelCreatedBody' '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /spaces/{id}/channels/{channelId}: patch: tags: [channel] parameters: - name: id in: path required: true schema: { type: string } - name: channelId in: path required: true schema: { type: string, format: uuid } - name: X-WD-Account in: header required: true schema: { type: string } - $ref: '#/components/parameters/RoleWriter' - $ref: '#/components/parameters/Account' - name: Authorization in: header schema: { type: string } requestBody: required: true content: application/json: schema: { $ref: '../contracts/api.schemas.json#/$defs/WorkspaceNameRequest' } responses: '200': description: 服务端权威数据 content: application/json: schema: $ref: '../contracts/api.schemas.json#/$defs/WorkspaceBody' '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /spaces/{id}/channels/{channelId}/group: put: tags: [channel] parameters: - name: id in: path required: true schema: { type: string } - name: channelId in: path required: true schema: { type: string, format: uuid } - name: X-WD-Account in: header required: true schema: { type: string } - $ref: '#/components/parameters/RoleWriter' - $ref: '#/components/parameters/Account' - name: Authorization in: header schema: { type: string } requestBody: required: true content: application/json: schema: { $ref: '../contracts/api.schemas.json#/$defs/WorkspaceGroupRequest' } responses: '200': description: 服务端权威数据 content: application/json: schema: $ref: '../contracts/api.schemas.json#/$defs/WorkspaceBody' '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /spaces/{id}/groups: post: tags: [channel] parameters: - name: id in: path required: true schema: { type: string } - name: X-WD-Account in: header required: true schema: { type: string } - $ref: '#/components/parameters/RoleWriter' - $ref: '#/components/parameters/Account' - name: Authorization in: header schema: { type: string } requestBody: required: true content: application/json: schema: { $ref: '../contracts/api.schemas.json#/$defs/WorkspaceNameRequest' } responses: '201': description: 服务端权威数据 content: application/json: schema: $ref: '../contracts/api.schemas.json#/$defs/WorkspaceGroupCreatedBody' '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /spaces/{id}/groups/{groupId}: patch: tags: [channel] parameters: - name: id in: path required: true schema: { type: string } - name: groupId in: path required: true schema: { type: string, format: uuid } - name: X-WD-Account in: header required: true schema: { type: string } - $ref: '#/components/parameters/RoleWriter' - $ref: '#/components/parameters/Account' - name: Authorization in: header schema: { type: string } requestBody: required: true content: application/json: schema: { $ref: '../contracts/api.schemas.json#/$defs/WorkspaceNameRequest' } responses: '200': description: 服务端权威数据 content: application/json: schema: $ref: '../contracts/api.schemas.json#/$defs/WorkspaceBody' '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } delete: tags: [channel] summary: 删除分组;组内频道回到未分组,频道与内容保留 parameters: - name: id in: path required: true schema: { type: string } - name: groupId in: path required: true schema: { type: string, format: uuid } - name: X-WD-Account in: header required: true schema: { type: string } - $ref: '#/components/parameters/RoleWriter' - $ref: '#/components/parameters/Account' - name: Authorization in: header schema: { type: string } responses: '200': description: 服务端权威数据 content: application/json: schema: $ref: '../contracts/api.schemas.json#/$defs/WorkspaceBody' '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } /spaces/{id}/roles: post: tags: [channel] summary: 新建身份组(只有空间主) parameters: - name: id in: path required: true schema: { type: string } - name: X-WD-Account in: header required: true schema: { type: string } - $ref: '#/components/parameters/RoleWriter' - $ref: '#/components/parameters/Account' - name: Authorization in: header schema: { type: string } requestBody: required: true content: application/json: schema: { $ref: '../contracts/api.schemas.json#/$defs/WorkspaceRoleRequest' } responses: '201': description: 完整结构快照和新建的身份组 content: application/json: schema: $ref: '../contracts/api.schemas.json#/$defs/WorkspaceRoleCreatedBody' '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } /spaces/{id}/roles/{roleId}: patch: tags: [channel] summary: 修改身份组的名称、前缀后缀、颜色和权限(只有空间主) parameters: - name: id in: path required: true schema: { type: string } - name: roleId in: path required: true schema: { type: string, format: uuid } - name: X-WD-Account in: header required: true schema: { type: string } - $ref: '#/components/parameters/RoleWriter' - $ref: '#/components/parameters/Account' - name: Authorization in: header schema: { type: string } requestBody: required: true content: application/json: schema: { $ref: '../contracts/api.schemas.json#/$defs/WorkspaceRoleRequest' } responses: '200': description: 服务端权威数据 content: application/json: schema: $ref: '../contracts/api.schemas.json#/$defs/WorkspaceBody' '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } delete: tags: [channel] summary: 删除身份组(只有空间主);其成员失去它授予的权限 parameters: - name: id in: path required: true schema: { type: string } - name: roleId in: path required: true schema: { type: string, format: uuid } - name: X-WD-Account in: header required: true schema: { type: string } - $ref: '#/components/parameters/RoleWriter' - $ref: '#/components/parameters/Account' - name: Authorization in: header schema: { type: string } responses: '200': description: 服务端权威数据 content: application/json: schema: $ref: '../contracts/api.schemas.json#/$defs/WorkspaceBody' '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } /spaces/{id}/roles/{roleId}/members: put: tags: [channel] summary: 整体替换身份组的成员(只有空间主);成员必须已加入空间 parameters: - name: id in: path required: true schema: { type: string } - name: roleId in: path required: true schema: { type: string, format: uuid } - name: X-WD-Account in: header required: true schema: { type: string } - $ref: '#/components/parameters/RoleWriter' - $ref: '#/components/parameters/Account' - name: Authorization in: header schema: { type: string } requestBody: required: true content: application/json: schema: { $ref: '../contracts/api.schemas.json#/$defs/WorkspaceRoleMembersRequest' } responses: '200': description: 服务端权威数据 content: application/json: schema: $ref: '../contracts/api.schemas.json#/$defs/WorkspaceBody' '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } /spaces/{id}/folders: post: tags: [channel] parameters: - name: id in: path required: true schema: { type: string } - name: X-WD-Account in: header required: true schema: { type: string } - $ref: '#/components/parameters/RoleWriter' - $ref: '#/components/parameters/Account' - name: Authorization in: header schema: { type: string } requestBody: required: true content: application/json: schema: { $ref: '../contracts/api.schemas.json#/$defs/WorkspaceFolderRequest' } responses: '201': description: 服务端权威数据 content: application/json: schema: $ref: '../contracts/api.schemas.json#/$defs/WorkspaceBody' '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /spaces/{id}/folders/{folderId}: patch: tags: [channel] parameters: - name: id in: path required: true schema: { type: string } - name: folderId in: path required: true schema: { type: string, format: uuid } - name: X-WD-Account in: header required: true schema: { type: string } - $ref: '#/components/parameters/RoleWriter' - $ref: '#/components/parameters/Account' - name: Authorization in: header schema: { type: string } requestBody: required: true content: application/json: schema: { $ref: '../contracts/api.schemas.json#/$defs/WorkspaceNameRequest' } responses: '200': description: 服务端权威数据 content: application/json: schema: $ref: '../contracts/api.schemas.json#/$defs/WorkspaceBody' '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /spaces/{id}/locations: put: tags: [channel] parameters: - name: id in: path required: true schema: { type: string } - name: X-WD-Account in: header required: true schema: { type: string } - $ref: '#/components/parameters/RoleWriter' - $ref: '#/components/parameters/Account' - name: Authorization in: header schema: { type: string } requestBody: required: true content: application/json: schema: { $ref: '../contracts/api.schemas.json#/$defs/WorkspaceMoveRequest' } responses: '200': description: 服务端权威数据 content: application/json: schema: $ref: '../contracts/api.schemas.json#/$defs/WorkspaceBody' '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /accounts/channels: get: tags: [account] security: [{ accountBearer: [] }] summary: 领取当前账号已加入频道的加密密钥包 responses: '200': description: 仅返回已加入频道 content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/AccountChannel' } } '401': { $ref: '#/components/responses/Error' } /accounts/devices: get: tags: [account] security: [{ accountBearer: [] }] summary: 读取当前账号的设备会话 responses: '200': description: 设备列表 content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/AccountDevice' } } '401': { $ref: '#/components/responses/Error' } /accounts/devices/{id}: parameters: - { name: id, in: path, required: true, schema: { type: string, format: uuid } } patch: tags: [account] security: [{ accountBearer: [] }] summary: 重命名本账号设备 requestBody: required: true content: application/json: schema: type: object required: [name] properties: { name: { type: string, minLength: 1, maxLength: 80 } } responses: '204': { description: 已更新 } '400': { $ref: '#/components/responses/Error' } '401': { $ref: '#/components/responses/Error' } delete: tags: [account] security: [{ accountBearer: [] }] summary: 撤销该设备的全部 sessions,保留账号成员资格 responses: '204': { description: 已撤销 } '401': { $ref: '#/components/responses/Error' } /channels/{id}/key-package: put: tags: [channel] security: [{ bearer: [] }] summary: 频道主补齐给自己账号的加密密钥包 parameters: - { $ref: '#/components/parameters/ChannelId' } - { $ref: '#/components/parameters/Role' } requestBody: required: true content: application/json: schema: type: object required: [payload] properties: { payload: { type: string, minLength: 1, maxLength: 16384 } } responses: '204': { description: 已保存 } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } /info: get: tags: [server] security: [] summary: 服务端能力与限制 responses: '200': description: OK content: application/json: schema: { $ref: '#/components/schemas/ServerInfo' } /accounts: post: tags: [account] security: [] summary: 创建账号 description: 仅接收口令 verifier 和客户端加密保险库,不接收密码。 requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/AccountRegister' } responses: '204': { description: 已创建 } '400': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } '429': { $ref: '#/components/responses/Error' } /accounts/salt: post: tags: [account] security: [] summary: 查询账号口令派生 salt requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/AccountSaltRequest' } responses: '200': description: 客户端 PBKDF2 salt content: application/json: schema: { $ref: '#/components/schemas/AccountSalt' } '400': { $ref: '#/components/responses/Error' } /accounts/session: post: tags: [account] security: [] summary: 验证客户端派生凭据并创建 session requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/AccountSessionRequest' } responses: '200': description: 已登录 content: application/json: schema: { $ref: '#/components/schemas/AccountSession' } '401': { $ref: '#/components/responses/Error' } '400': { $ref: '#/components/responses/Error' } delete: tags: [account] security: [{ accountBearer: [] }] summary: 撤销当前账号 session responses: '204': { description: 已撤销 } /accounts/vault: get: tags: [account] security: [{ accountBearer: [] }] summary: 读取客户端加密保险库 responses: '200': description: 当前保险库和 revision content: application/json: schema: { $ref: '#/components/schemas/AccountVault' } '401': { $ref: '#/components/responses/Error' } put: tags: [account] security: [{ accountBearer: [] }] summary: 以 revision 乐观并发更新保险库 requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/AccountVault' } responses: '200': description: 已更新 content: application/json: schema: { $ref: '#/components/schemas/AccountVault' } '401': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /accounts/users/{username}/key: get: tags: [account] security: [{ accountBearer: [] }] summary: 查询被邀请用户公钥 parameters: - { name: username, in: path, required: true, schema: { type: string } } responses: '200': description: 公钥 content: application/json: schema: { $ref: '#/components/schemas/PublicKey' } '404': { $ref: '#/components/responses/Error' } /accounts/invitations: get: tags: [account] security: [{ accountBearer: [] }] summary: 当前账号收到的邀请 responses: '200': description: 邀请列表 content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/ChannelInvitation' } } /channels/{id}/members: get: tags: [channel] security: [{ bearer: [] }] summary: 读取频道内账号成员及角色 parameters: - { $ref: '#/components/parameters/ChannelId' } - { $ref: '#/components/parameters/Role' } - { $ref: '#/components/parameters/Account' } responses: '200': description: 成员列表 content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/ChannelMember' } } /channels/{id}/invitations: parameters: - { $ref: '#/components/parameters/ChannelId' } - { $ref: '#/components/parameters/RoleOwner' } get: tags: [channel] security: [{ bearer: [], accountBearer: [] }] summary: 频道主查询邀请 responses: '200': description: 邀请列表 content: application/json: schema: { type: array, items: { $ref: '#/components/schemas/ChannelInvitation' } } post: tags: [channel] security: [{ bearer: [], accountBearer: [] }] summary: 原子提交账号邀请及服务端授权计划要求的域密钥包 requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/InvitationRequest' } responses: '204': { description: 已邀请 } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /channels/{id}/invitations/{username}/crypto-plan: get: tags: [channel] security: [{ bearer: [], accountBearer: [] }] summary: 获取新成员域密钥封装计划 description: 返回由服务端历史策略和当前成员权限计算的 epoch 与收件人集合;提交时服务端会重新计算并校验。 parameters: - { $ref: '#/components/parameters/ChannelId' } - { $ref: '#/components/parameters/RoleOwner' } - { name: username, in: path, required: true, schema: { type: string } } responses: '200': description: 服务端计算的加密授权计划 content: application/json: schema: { $ref: '#/components/schemas/InvitationCryptoPlan' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /channels/{id}/invitations/{username}: delete: tags: [channel] security: [{ bearer: [], accountBearer: [] }] summary: 撤销邀请和成员授权并移除其设备 parameters: - { $ref: '#/components/parameters/ChannelId' } - { $ref: '#/components/parameters/RoleOwner' } - { name: username, in: path, required: true, schema: { type: string } } responses: '204': { description: 已移除 } '403': { $ref: '#/components/responses/Error' } /channels: post: tags: [channel] security: [{ accountBearer: [] }] summary: 创建频道 description: | 服务端启用验证码时需要 `X-WD-Cap`(Cap 的 token,见 protocol §11),并需要终端头 `X-WD-Endpoint`。 创建者的账号自动成为该频道 owner,其终端自动入席。 parameters: - { $ref: '#/components/parameters/Captcha' } - { $ref: '#/components/parameters/Account' } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/CreateChannelRequest' } responses: '201': description: 已创建 content: application/json: schema: { $ref: '#/components/schemas/ChannelInfo' } '400': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } '428': { $ref: '#/components/responses/Error' } '429': { $ref: '#/components/responses/Error' } '503': { $ref: '#/components/responses/Error' } /channels/{id}: parameters: - { $ref: '#/components/parameters/ChannelId' } get: tags: [channel] summary: 读取频道信息(不占成员名额) description: 需要账号会话。`members_used` 表示唯一账号成员数,不是设备数。 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/Role' } responses: '200': description: OK content: application/json: schema: { $ref: '#/components/schemas/ChannelInfo' } '401': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } delete: tags: [channel] summary: 删除整个频道及全部密文(所有者) description: 有效的 owner 凭据即可调用,不要求 owner 终端登记。 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/RoleOwner' } responses: '204': { description: 已删除 } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } /channels/{id}/settings: patch: tags: [channel] summary: 修改设置(所有者) description: | 字段均可选,只修改提供的字段。`ttl_seconds` 的变更从现在起算。 成功后向订阅者推送 `{"t":"settings"}`。有效的 owner 凭据即可调用,不要求 owner 终端登记。 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/ChannelId' } - { $ref: '#/components/parameters/RoleOwner' } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/ChannelSettingsPatch' } responses: '200': description: OK content: application/json: schema: { $ref: '#/components/schemas/ChannelInfo' } '400': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } /channels/{id}/join: parameters: - { $ref: '#/components/parameters/ChannelId' } post: tags: [join] summary: 加入频道 description: | 幂等。必须带账号会话(没有则 401 `account_required`)。只登记成员关系:同一账号的多台设备不增加人数,此后仅凭账号会话鉴权。 成员名额已满返回 403 `members_full`。读取频道信息和 owner 管理接口不占名额;访问频道内容前必须已加入,否则 403 `not_joined`。 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/Role' } - { $ref: '#/components/parameters/Account' } requestBody: required: false content: application/json: schema: type: object properties: password: { type: string, minLength: 8, maxLength: 256 } responses: '200': description: 已加入 content: application/json: schema: { $ref: '#/components/schemas/JoinResult' } '401': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } /channels/{id}/frames: post: tags: [frame] summary: 追加一帧 description: | 请求体是完整的帧封装(26 字节头 + 负载),格式见 `protocol.md` §4。 幂等键为 `(channel_id, item_id, frame_index)`。内容相同则 200 返回原 seq,内容不同则 409。 第 0 帧可带 `X-WD-Max-Downloads`;名称与线程字段统一在条目元数据中。 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/ChannelId' } - { $ref: '#/components/parameters/RoleWriter' } - { $ref: '#/components/parameters/Account' } - name: X-WD-Max-Downloads in: header required: false schema: { type: integer, minimum: 0 } description: 条目的下载上限,0 为不限。只对 GATED 条目生效。 requestBody: required: true content: application/octet-stream: schema: { type: string, format: binary } responses: '201': description: 新追加 content: application/json: schema: { $ref: '#/components/schemas/AppendResult' } '200': description: 幂等重发 content: application/json: schema: { $ref: '#/components/schemas/AppendResult' } '400': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } '413': { $ref: '#/components/responses/Error' } '429': { $ref: '#/components/responses/Error' } /channels/{id}/log: get: tags: [frame] summary: 按 seq 拉取日志(长轮询、续传、全量同步) description: | 返回日志记录的线格式拼接(`protocol.md` §8):每条 `seq(u64) ‖ kind(u8) ‖ body`。 `wait>0` 时无新记录会挂起,最长 25 秒,超时返回空体。 满足 `0 < from < oldest_seq` 时返回 410 并带 `oldest_seq`,客户端必须从 `from=0` 全量同步并丢弃本地已不存在的条目(不要从 `oldest_seq` 读起)。`from=0` 永远允许。 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/ChannelId' } - { $ref: '#/components/parameters/Role' } - { $ref: '#/components/parameters/Account' } - name: from in: query required: false schema: { type: integer, format: int64, minimum: 0, default: 0 } - name: limit in: query required: false schema: { type: integer, minimum: 1, maximum: 1000, default: 200 } - name: wait in: query required: false schema: { type: integer, minimum: 0, maximum: 25, default: 0 } description: 长轮询等待秒数 responses: '200': description: 记录拼接,可为空 headers: X-WD-Last-Seq: description: 频道当前最新 seq schema: { type: integer, format: int64 } content: application/octet-stream: schema: { type: string, format: binary } '403': { $ref: '#/components/responses/Error' } '410': description: 0 < from < oldest_seq,需要从 0 全量同步 content: application/json: schema: { $ref: '#/components/schemas/GoneLog' } /channels/{id}/ws: get: tags: [frame] security: [] summary: WebSocket 订阅(降级链第一级) description: | 升级为 WebSocket。浏览器不能设置头,凭据通过子协议传递: `Sec-WebSocket-Protocol: wd.v1, wd.role., wd.auth., wd.ep.`, 服务端回选 `wd.v1`。不要把令牌放在 URL 里。 **服务端 → 客户端** - 二进制消息:一条日志记录(`seq ‖ kind ‖ body`)。 - 文本消息(JSON):`{"t":"hb","seq":N}` 心跳(每 20 秒);`{"t":"settings"}` 设置变更;`{"t":"members"}` 成员变更。 客户端 45 秒收不到任何消息应判定死链,以指数退避(1~30 秒)加抖动重连,并带 `from=last_seq+1`。 上传不走 WebSocket,仍用 `POST /frames`。 parameters: - { $ref: '#/components/parameters/ChannelId' } - name: from in: query required: false schema: { type: integer, format: int64, minimum: 0, default: 0 } responses: '101': { description: 已升级 } '403': { $ref: '#/components/responses/Error' } '410': description: 0 < from < oldest_seq,需要从 0 全量同步 content: application/json: schema: { $ref: '#/components/schemas/GoneLog' } /channels/{id}/events: get: tags: [frame] summary: SSE 订阅(降级链第二级) description: | `text/event-stream`。每个事件的 `id:` 是 seq,`data:` 是 base64url 的日志记录; 控制消息与 WebSocket 的文本消息相同,事件名为 `control`。 `EventSource` 不能设置头,前端应使用 `fetch` 流式读取并带头部鉴权。 续传用 `from` 或 `Last-Event-ID`。 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/ChannelId' } - { $ref: '#/components/parameters/Role' } - { $ref: '#/components/parameters/Account' } - name: from in: query required: false schema: { type: integer, format: int64, minimum: 0, default: 0 } responses: '200': description: 事件流 content: text/event-stream: schema: { type: string } '403': { $ref: '#/components/responses/Error' } '410': description: 0 < from < oldest_seq,需要从 0 全量同步 content: application/json: schema: { $ref: '#/components/schemas/GoneLog' } /channels/{id}/items/{itemId}/frames: get: tags: [item] summary: 拉取条目的帧(GATED 条目的取数方式) description: | 返回封装拼接,每帧自带 `payload_len`,可流式解析。条目尚未 FINAL 时响应持续输出直到 FINAL。 `from=0` 计一次下载;`from>0` 只有同一终端此前对同一条目成功启动过 `from=0` 才能续传,否则返回 410 `gone`。 达到下载上限后返回 410 `gone`,5 分钟宽限期后删除密文,宽限期内已开始的拉取可继续续传。 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/ChannelId' } - { $ref: '#/components/parameters/ItemId' } - { $ref: '#/components/parameters/Role' } - { $ref: '#/components/parameters/Account' } - name: from in: query required: false schema: { type: integer, format: int64, minimum: 0, default: 0 } description: 起始 frame_index responses: '200': description: 封装拼接 content: application/octet-stream: schema: { type: string, format: binary } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '410': { $ref: '#/components/responses/Error' } /channels/{id}/items/{itemId}: delete: tags: [item] summary: 删除条目 description: | 按频道的 `delete_policy` 授权:`owner_only` 仅所有者;`own` 时所有者或条目作者;`anyone` 时任意已入席终端。 成功后在日志追加 tombstone(`reason=0`)。 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/ChannelId' } - { $ref: '#/components/parameters/ItemId' } - { $ref: '#/components/parameters/Role' } - { $ref: '#/components/parameters/Account' } responses: '204': { description: 已删除 } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } /shares: post: tags: [share] security: [{ accountBearer: [] }] summary: 创建便捷分享草稿 description: 启用验证码时需携带 Cap token。创建者 endpoint 可选,创建者不计入访客数。 parameters: - { $ref: '#/components/parameters/Captcha' } - { $ref: '#/components/parameters/ShareEndpoint' } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/CreateShareRequest' } responses: '201': description: 已创建草稿 content: application/json: schema: { $ref: '#/components/schemas/ShareInfo' } '400': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } '428': { $ref: '#/components/responses/Error' } '429': { $ref: '#/components/responses/Error' } '503': { $ref: '#/components/responses/Error' } /shares/{id}: parameters: - { $ref: '#/components/parameters/ShareId' } get: tags: [share] summary: 读取分享信息 description: 已封存时使用 read;草稿仅 owner 可读,read 对草稿返回 not_found。 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/Role' } responses: '200': description: 分享与条目下载状态 content: application/json: schema: { $ref: '#/components/schemas/ShareInfo' } '401': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } delete: tags: [share] summary: 删除草稿 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/RoleOwner' } responses: '204': { description: 草稿已删除 } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /shares/{id}/settings: patch: tags: [share] summary: 修改草稿设置 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/ShareId' } - { $ref: '#/components/parameters/RoleOwner' } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/ShareSettingsPatch' } responses: '200': description: 设置已更新 content: application/json: schema: { $ref: '#/components/schemas/ShareInfo' } '400': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /shares/{id}/frames: post: tags: [share] summary: 向草稿追加加密帧 description: 不支持 SNAPSHOT。第 0 帧可用 X-WD-Max-Downloads 覆盖默认下载上限。响应的 seq 是 frame_index 确认值,不是日志游标。 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/ShareId' } - { $ref: '#/components/parameters/RoleOwner' } - name: X-WD-Max-Downloads in: header required: false schema: { type: integer, format: int64, minimum: 0 } requestBody: required: true content: application/octet-stream: schema: { type: string, format: binary } responses: '201': description: 新帧已追加 content: application/json: schema: { $ref: '#/components/schemas/AppendResult' } '200': description: 幂等重发 content: application/json: schema: { $ref: '#/components/schemas/AppendResult' } '400': { $ref: '#/components/responses/Error' } '403': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } '413': { $ref: '#/components/responses/Error' } /shares/{id}/items/{itemId}: delete: tags: [share] summary: 从草稿移除条目 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/ShareId' } - { $ref: '#/components/parameters/ItemId' } - { $ref: '#/components/parameters/RoleOwner' } responses: '204': { description: 条目已删除 } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /shares/{id}/finish: post: tags: [share] summary: 封存分享 description: 验证 manifest 是单帧非 GATED 完整条目,且草稿内所有条目完整;成功后永久禁用写权限。 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/ShareId' } - { $ref: '#/components/parameters/RoleOwner' } requestBody: required: true content: application/json: schema: { $ref: '#/components/schemas/FinishShareRequest' } responses: '200': description: 分享已封存 content: application/json: schema: { $ref: '#/components/schemas/ShareInfo' } '400': { $ref: '#/components/responses/Error' } '409': { $ref: '#/components/responses/Error' } /shares/{id}/manifest: get: tags: [share] summary: 获取加密清单帧 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/ShareId' } - { $ref: '#/components/parameters/Role' } - { $ref: '#/components/parameters/Endpoint' } responses: '200': description: 完整封装的二进制清单 content: application/octet-stream: schema: { type: string, format: binary } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '410': { $ref: '#/components/responses/Error' } /shares/{id}/items/{itemId}/frames: get: tags: [share] summary: 下载封存分享中的文件帧 description: from=0 计一次下载;from>0 只有同一 X-WD-Endpoint 此前对同一文件成功启动过 from=0 才能续传,否则返回 410 gone 且不登记访客。首次成功读取会登记访客终端。 security: [{ bearer: [] }] parameters: - { $ref: '#/components/parameters/ShareId' } - { $ref: '#/components/parameters/ItemId' } - { $ref: '#/components/parameters/Role' } - { $ref: '#/components/parameters/Endpoint' } - name: from in: query required: false schema: { type: integer, format: int64, minimum: 0, default: 0 } responses: '200': description: 连续封装帧拼接 content: application/octet-stream: schema: { type: string, format: binary } '403': { $ref: '#/components/responses/Error' } '404': { $ref: '#/components/responses/Error' } '410': { $ref: '#/components/responses/Error' } components: securitySchemes: accountBearer: type: apiKey in: header name: X-WD-Account description: WebDrop 账号登录后颁发的随机 session token。 bearer: type: http scheme: bearer description: | 令牌的 base64url(`T_read` / `T_write` / `T_owner`),与 `X-WD-Role` 配套。 raw 模式的读请求不需要令牌。 parameters: ChannelId: name: id in: path required: true description: 频道 id,16 字节 base64url schema: { $ref: '#/components/schemas/Base64Url' } ShareId: name: id in: path required: true description: 便捷分享 id,16 字节 base64url schema: { $ref: '#/components/schemas/Base64Url' } ItemId: name: itemId in: path required: true description: 条目 id,16 字节 base64url schema: { $ref: '#/components/schemas/Base64Url' } Account: name: X-WD-Account in: header required: true description: 账号会话令牌。频道与空间只接受已登录账号,缺少或失效返回 401 `account_required`。 schema: { type: string } Role: name: X-WD-Role in: header required: true description: 令牌所属角色 schema: { type: string, enum: [read, write, owner] } RoleWriter: name: X-WD-Role in: header required: true schema: { type: string, enum: [write, owner] } RoleOwner: name: X-WD-Role in: header required: true schema: { type: string, enum: [owner] } Endpoint: name: X-WD-Endpoint in: header required: false description: 终端密钥 `S` 的 base64url,仅存本地。只用于便捷分享(未登录的创建者与读者);频道与空间使用 `X-WD-Account` 会话,不需要它。 schema: { $ref: '#/components/schemas/Base64Url' } ShareEndpoint: name: X-WD-Endpoint in: header required: false description: 创建者终端密钥 S 的 base64url,可选,不占访客名额。 schema: { $ref: '#/components/schemas/Base64Url' } Captcha: name: X-WD-Cap in: header required: false description: Cap 验证码 token。服务端 `/info` 的 `captcha.enabled` 为 true 时必填;token 一次有效。 schema: { type: string, maxLength: 2048 } responses: Error: description: 错误。响应体不回显任何用户提交的内容。 content: application/json: schema: { $ref: '#/components/schemas/Error' } schemas: AccountChannel: $ref: '../contracts/api.schemas.json#/$defs/AccountChannelBody' AccountDevice: $ref: '../contracts/api.schemas.json#/$defs/AccountDeviceBody' PublicKey: $ref: '../contracts/api.schemas.json#/$defs/PublicKeyBody' ChannelInvitation: $ref: '../contracts/api.schemas.json#/$defs/InvitationBody' InvitationRequest: $ref: '../contracts/api.schemas.json#/$defs/InvitationRequest' InvitationCryptoPlan: $ref: '../contracts/api.schemas.json#/$defs/InvitationCryptoPlan' ChannelMember: $ref: '../contracts/api.schemas.json#/$defs/MemberBody' AccountRegister: $ref: '../contracts/api.schemas.json#/$defs/AccountRegisterRequest' AccountSaltRequest: $ref: '../contracts/api.schemas.json#/$defs/AccountSaltRequest' AccountSalt: $ref: '../contracts/api.schemas.json#/$defs/AccountSaltBody' AccountSessionRequest: $ref: '../contracts/api.schemas.json#/$defs/AccountSessionRequest' AccountSession: $ref: '../contracts/api.schemas.json#/$defs/AccountSessionBody' AccountVault: $ref: '../contracts/api.schemas.json#/$defs/AccountVaultBody' Base64Url: type: string pattern: '^[A-Za-z0-9_-]+$' description: base64url,无填充 ErrorCode: type: string enum: - bad_request - unauthorized - forbidden - not_joined - members_full - mode_mismatch - not_found - conflict - gone - too_large - captcha_required - captcha_unavailable - rate_limited - visitors_full - sealed - account_required - account_invalid - invitation_required - channel_password_required - channel_password_invalid - username_unavailable - vault_conflict Error: type: object required: [error] properties: error: { $ref: '#/components/schemas/ErrorCode' } GoneLog: type: object required: [error, oldest_seq] properties: error: { type: string, const: gone } oldest_seq: { type: integer, format: int64, description: "清理水位 + 1;0 < from < oldest_seq 的续传需要从 0 全量同步" } ServerInfo: $ref: '../contracts/api.schemas.json#/$defs/ServerInfoBody' ChannelMode: $ref: '../contracts/api.schemas.json#/$defs/ChannelMode' DeletePolicy: $ref: '../contracts/api.schemas.json#/$defs/DeletePolicy' ChannelSettings: $ref: '../contracts/api.schemas.json#/$defs/ChannelSettings' ChannelSettingsPatch: $ref: '../contracts/api.schemas.json#/$defs/ChannelSettingsPatch' CreateChannelRequest: $ref: '../contracts/api.schemas.json#/$defs/CreateChannelRequest' ShareSettings: $ref: '../contracts/api.schemas.json#/$defs/ShareSettings' ShareSettingsPatch: $ref: '../contracts/api.schemas.json#/$defs/ShareSettingsPatch' CreateShareRequest: $ref: '../contracts/api.schemas.json#/$defs/CreateShareRequest' FinishShareRequest: $ref: '../contracts/api.schemas.json#/$defs/FinishShareRequest' ShareItemInfo: $ref: '../contracts/api.schemas.json#/$defs/ShareItemInfo' ShareInfo: $ref: '../contracts/api.schemas.json#/$defs/ShareInfo' ChannelInfo: $ref: '../contracts/api.schemas.json#/$defs/ChannelInfo' JoinResult: $ref: '../contracts/api.schemas.json#/$defs/JoinResult' AppendResult: $ref: '../contracts/api.schemas.json#/$defs/AppendResult'