Files
WebDrop/docs/openapi.yaml
T

1450 lines
50 KiB
YAML

openapi: 3.1.0
info:
title: WebDrop API
version: 6.0.0
servers:
- url: /api/v1
tags:
- name: server
- name: channel
- name: seat
- 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/Endpoint'
- 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/Endpoint'
- 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/Endpoint'
- 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/Endpoint'
- 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/Endpoint'
- 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/Endpoint'
- 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}/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/Endpoint'
- 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/Endpoint'
- 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/Endpoint'
- 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/Endpoint' }
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/Endpoint' }
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: 任意角色可调用。`seats_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}/seats:
parameters:
- { $ref: '#/components/parameters/ChannelId' }
post:
tags: [seat]
summary: 登记当前设备并加入频道
description: |
幂等:同一终端重复调用返回同一设备席位,不重复登记。
同一账号的设备不限量,账号首次加入时才占一个成员名额;成员名额已满返回 403 `seats_full`。
未登录访客每个终端单独计一个成员名额。读取频道信息和 owner 管理接口不占成员名额;频道内容、设备席位/设备名等操作要求终端已入席,否则 403 `not_joined`。
security: [{ bearer: [] }]
parameters:
- { $ref: '#/components/parameters/Role' }
- { $ref: '#/components/parameters/Endpoint' }
responses:
'200':
description: 已入席
content:
application/json:
schema: { $ref: '#/components/schemas/SeatJoined' }
'401': { $ref: '#/components/responses/Error' }
'403': { $ref: '#/components/responses/Error' }
'404': { $ref: '#/components/responses/Error' }
get:
tags: [seat]
summary: 查看已加入设备列表(所有者)
description: 有效的 owner 凭据即可调用,不要求 owner 终端占用席位。
security: [{ bearer: [] }]
parameters:
- { $ref: '#/components/parameters/RoleOwner' }
responses:
'200':
description: OK
content:
application/json:
schema:
type: array
items: { $ref: '#/components/schemas/Seat' }
'403': { $ref: '#/components/responses/Error' }
/channels/{id}/seats/me:
put:
tags: [seat]
summary: 终端上传自己的加密设备名
description: 请求体是用 `K_enc` 加密后的设备名字节,≤256 B。服务端不解析。
security: [{ bearer: [] }]
parameters:
- { $ref: '#/components/parameters/ChannelId' }
- { $ref: '#/components/parameters/Role' }
- { $ref: '#/components/parameters/Endpoint' }
requestBody:
required: true
content:
application/octet-stream:
schema: { type: string, format: binary, maxLength: 256 }
responses:
'204': { description: 已保存 }
'403': { $ref: '#/components/responses/Error' }
'413': { $ref: '#/components/responses/Error' }
/channels/{id}/seats/{endpointHash}/label:
put:
tags: [seat]
summary: 所有者重命名设备
description: 请求体是设备名称(≤256 B);enc 模式以目标 endpoint hash 作为 AAD 加密,raw 模式为明文。
security: [{ bearer: [] }]
parameters:
- { $ref: '#/components/parameters/ChannelId' }
- name: endpointHash
in: path
required: true
description: 终端标识,即 `base64url(SHA-256(S))`
schema: { $ref: '#/components/schemas/Base64Url' }
- { $ref: '#/components/parameters/RoleOwner' }
requestBody:
required: true
content:
application/octet-stream:
schema: { type: string, format: binary, maxLength: 256 }
responses:
'204': { description: 名称已更新 }
'403': { $ref: '#/components/responses/Error' }
'404': { $ref: '#/components/responses/Error' }
'413': { $ref: '#/components/responses/Error' }
/channels/{id}/seats/{endpointHash}:
delete:
tags: [seat]
summary: 撤销单台设备(所有者)
description: 有效的 owner 凭据即可调用,不要求 owner 终端占用设备席位。被释放终端后续请求返回 `not_joined`;账号成员身份及人数名额保留;向订阅者推送 `{"t":"seats"}`。
security: [{ bearer: [] }]
parameters:
- { $ref: '#/components/parameters/ChannelId' }
- name: endpointHash
in: path
required: true
description: 终端标识,即 `base64url(SHA-256(S))`
schema: { $ref: '#/components/schemas/Base64Url' }
- { $ref: '#/components/parameters/RoleOwner' }
responses:
'204': { description: 已释放 }
'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/Endpoint' }
- 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/Endpoint' }
- 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.<role>, wd.auth.<base64url(令牌)>, wd.ep.<base64url(S)>`,
服务端回选 `wd.v1`。不要把令牌放在 URL 里。
**服务端 → 客户端**
- 二进制消息:一条日志记录(`seq ‖ kind ‖ body`)。
- 文本消息(JSON):`{"t":"hb","seq":N}` 心跳(每 20 秒);`{"t":"settings"}` 设置变更;`{"t":"seats"}` 席位变更。
客户端 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/Endpoint' }
- 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/Endpoint' }
- 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/Endpoint' }
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' }
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: true
description: 终端密钥 `S` 的 base64url。每个频道每个终端各生成一个,仅存本地。
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
- seats_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'
SeatJoined:
$ref: '../contracts/api.schemas.json#/$defs/SeatJoined'
Seat:
$ref: '../contracts/api.schemas.json#/$defs/SeatInfo'
AppendResult:
$ref: '../contracts/api.schemas.json#/$defs/AppendResult'