====== ApuSync APP 对接手册:设备配对、照片与文件直传 ====== 本文件同时定义云端发现与 NAS 数据接口,供 Flutter、iOS、Android、iPadOS、Windows、macOS 和 Linux APP 对接。配对后的默认体验是:用户输入 **设备码+配对密钥**,直接访问 NAS 主账号预先授权的目录;APP 不再填写 NAS 用户名、密码,也没有云端人工审批步骤。 运行时以 capabilities 返回的能力为准。新增 NAS 能力版本为 ''2.0'',HTTP 路径保留 ''/v1'' 以兼容旧客户端;签名和 WebRTC 信令版本仍为 ''1''。产品发布版本与协议版本是两个概念。 ===== 1. 两个服务,明确分工 ===== ^ 服务 ^ 地址 ^ 接收的数据 ^ | 云端 ApuSync | ''https://sync.apuos.cn'',内部 TCP 13260 | 设备身份、一次性配对、会话、签名 SDP/ICE | | 地址探测 STUN | ''stun:sync.apuos.cn:13261'',UDP | STUN Binding 报文 | | NAS 局域网接口 | ''http://:18080/api/v1/sync-client/v1'',也可使用 NAS 配置的 HTTPS | 文件、照片、索引、缩略图、上传任务 | | NAS 远程接口 | APP 与 NAS 的 WebRTC DataChannel | 与 NAS RPC 相同的业务参数和内容 | **云端不接收文件、照片、文件名、目录清单、文件哈希、照片元数据或 NAS 密码。** 不得把下文的 NAS RPC 发到 sync 域名。云端无 TURN 或文件中继回退;若网络不能建立直连,APP 应明确提示直连失败并重试/切换网络,不得显示上传成功。 客户端自己的上传队列、系统相册读取权限、后台运行和网络重试由 APP 实现。服务端提供持久上传、完整性验证和提交回执,不代替 iOS/Android 的后台执行权限。 ===== 2. 设备身份、签名与时间单位 ===== 客户端首次运行生成 Ed25519 密钥对,私钥放在 Keychain/Keystore 等平台安全存储中。公钥为 32 字节小写 hex(64 字符);''clientId = hex(SHA256(rawPublicKey))[0:32]''。NAS 同样拥有独立的 Ed25519 身份;私钥不离开 NAS。 云端所有签名 POST 的 JSON 外层必须是: {"signed":"<确切的 UTF-8 JSON 字符串>","signature":""} 先序列化内部对象一次,再对该字符串的 UTF-8 字节签名,外层复用同一字符串。不要签名后重新排序字段、修改空格。内部对象拒绝未知字段。每次请求用随机 16 字节 ''nonce''(32 字符小写 hex),时间偏差不超过 120 秒。云端 ''timestamp/expiresAt/bindingExpiresAt'' 均为 **Unix 秒**;NAS RPC 的日期数值和分享过期时间均为 **Unix 毫秒**,照片 ''capturedAt'' 为 ISO 日期字符串。EXIF 没有时区时返回 ''captureTimeZoneKnown:false'',APP 不应擅自当 UTC。 Bearer、配对密钥、分享密钥和私钥不得放在 URL、查询参数、日志、崩溃上报或普通偏好设置里。生产连接校验 HTTPS 证书;云接口不允许查询参数。 ===== 3. NAS 主账号首次授权 ===== 这些 NAS 管理接口由主账号在 NAS 页面操作,APP 配对时不调用: ^ 方法与 NAS 路径 ^ 请求 ^ 用途 ^ | ''GET /api/v1/apusync/direct'' | NAS 登录 Bearer | 在线状态、''deviceCode''、授权目录、已配对客户端 | | ''POST /api/v1/apusync/direct/settings'' | ''{enabled,currentPassword,confirm:true}'' | 启用/停用直连同步 | | ''POST /api/v1/apusync/direct/invitations'' | ''{root,path,currentPassword,confirm:true}'' | 选定授权目录并生成一次性配对密钥 | | ''POST /api/v1/apusync/direct/sessions/{sessionId}/revoke'' | ''{currentPassword,confirm:true}'' | 撤销该客户端,已有连接停止访问 | 邀请返回 ''deviceId,deviceCode,grantId,sessionId,pin,pairingKey,expiresAt,approvalRequired:false''。''pairingKey'' 与 ''pin'' 是同一个 12 位数字字符串,**保留前导零**。密钥最长 5 分钟有效,一次使用;一个密钥绑定一个客户端。每个新客户端另生成密钥。绑定有效期默认 365 天;短期连接会话仍至多 1 小时,APP 用原私钥恢复,无需重复输入密码。NAS 管理状态返回 ''bindingExpiresAt'',旧授权的 ''loginMode'' 可能为 ''legacy-login'',重新配对即可采用新流程。 ===== 4. 云端注册、配对、恢复 ===== ==== NAS 自注册(由 NAS 进程实现) ==== ''POST /v2/sync/nas/register'',签名内部对象: {"version":1,"action":"nas-session","deviceId":"<32hex>","publicKey":"<64hex>","timestamp":0,"nonce":"<32hex>"} 返回稳定 ''deviceCode''(''APU'' 加 12 位小写 hex)、短期 NAS ''bearer''、''expiresAt''、''mailboxUrl''、''stunUrls''。新流程无需云端操作员批准,也不依赖 ApuConnect K。显式撤销或禁用的设备不能通过再次注册重新启用。旧 ''/v1/sync/nas/session'' 保留兼容。 ==== APP 初次配对 ==== - 用户从 NAS 页面获取设备码及一次性配对密钥。 - APP 以自己的私钥向 ''POST /v2/sync/pair'' 发送签名: {"version":1,"action":"pair","deviceCode":"APU<12hex>","pin":"<12位配对密钥>","clientId":"<32hex>","publicKey":"<64hex>","timestamp":0,"nonce":"<32hex>"} - 返回云端临时 ''bearer,expiresAt,sessionId,deviceId,nasPublicKey,invitation,mailboxUrl,stunUrls''。保存 NAS 身份、公钥及 sessionId;验证 NAS 签名邀请,核对设备 ID、公钥派生、grantId、sessionId 和有效期。 - 用 Bearer 查询 ''GET /v1/sync/client/session'' 或接收 WSS ''approved'' 事件。NAS 会核验原始 APP 签名及配对密钥,自动签署授权。''pending'' 是几秒的后台验证状态,**不是等待人点击审批**。 - 验证原始 ''approval'' 签名确实来自已绑定 NAS,内容包含自己的 clientId/publicKey、sessionId/grantId、未过期 expiresAt,之后才发送 SDP。 - 建立直连后收到 ''authenticated'',其中 ''method:"paired-key"''。APP 直接发 RPC,不发 ''login'',不索要 NAS 密码。 ==== 恢复已配对设备 ==== ''POST /v2/sync/client/resume'',不带旧 Bearer,使用原客户端私钥: {"version":1,"action":"pair-resume","deviceCode":"APU<12hex>","sessionId":"<原32hex>","clientId":"<原32hex>","publicKey":"<原64hex>","timestamp":0,"nonce":"<新的32hex>"} 它轮换云端 Bearer,旧客户端信箱关闭,信令计数重置;NAS 自动核验原绑定后重新签署短期授权。等待并验证新 approval,然后创建**新的** RTCPeerConnection/DataChannel。旧连接不能借长期绑定继续使用。私钥遗失、绑定撤销、NAS 账号凭据变更或授权过期后,需要在 NAS 重新配对。 ''GET /v1/sync/capabilities'' 为公开能力查询;''GET /v1/sync/client/session'' 为本会话恢复状态,不返回其他客户端清单。''POST /v1/sync/client/disconnect'' 使用本客户端 Bearer 和签名 ''{version:1,action:"client-disconnect",clientId,deviceId,sessionId,timestamp,nonce}'',用于明确断开/撤销,**不是短暂切后台的心跳操作**。签名信令和全部错误约定见 [[developer/cloud|SYNC-PROTOCOL.md]] 与 [[developer/index|SYNC-CLIENT-API.md]]。 ===== 5. 建立直连与统一 RPC ===== APP 创建可靠有序通道 ''apusync.control.v1'',采用云返回的 STUN,只接受 application SDP 与 host/srflx ICE,不启用 TURN。WSS ''/v1/sync/mailbox'' 使用 ''Authorization: Bearer'';浏览器可先发 ''{type:"authenticate",bearer}'',不能把令牌拼 URL。每条 offer/answer/ICE 都需要原客户端签名,严格核对 NAS 签名、目标、序号和 DTLS fingerprint。完整 SDP 字段和限制见协议文档。 远程请求: {"v":1,"type":"rpc","requestId":"op_001","operation":"files.stat","params":{"path":"照片/旅行.jpg"}} 成功/失败: {"v":1,"type":"rpc-result","requestId":"op_001","status":200,"data":{"path":"照片/旅行.jpg","etag":"<64hex>"}} {"v":1,"type":"rpc-result","requestId":"op_001","status":403,"error":"sync_scope_denied"} 请求最长 32768 字节,requestId 为 1–64 个字母、数字、''_'' 或 ''-''。多个响应按 requestId 关联;客户端应串行提交同一个上传的片段。旧单文件二进制通道仍兼容,但新 APP 优先使用持久 ''uploads.*''。 局域网 NAS REST 调试:''GET /api/v1/sync-client/v1/capabilities'';''POST /api/v1/sync-client/v1/rpc'',使用 NAS 登录 Bearer,body 只含 ''{operation,params}'',返回 ''{data:...}'' 或 ''{error:...}''。这条 HTTP 调试方式的登录不改变默认的远程免登录配对流程。暂不将云端 Bearer 当作 NAS HTTP Bearer。 **远程所有路径都相对于已授权目录**:''path:""'' 表示授权目录,''root'' 可省略,若传必须等于授权 root。不能以绝对路径、''..''、符号链接或相册/人物 ID 越过目录。REST 则明确传 NAS ''root'',path 相对于该 root。客户端禁止发送保留参数 ''scopePath,scopeExpiresAt,clientContext,publicationTicket'';这些只由 NAS 可信进程注入。 每次文件操作继续走原有 NAS 能力检查和 Linux UID/GID/ACL 文件代理。配对不授予系统管理、任意根目录或其他账号权限。 ===== 6. 文件、下载、搜索和索引 ===== 下表业务参数均位于 ''params'';''root'' 遵循上一节。''?'' 表示可选。 ^ operation ^ 参数 ^ 结果/规则 ^ | ''capabilities'' | ''{}'' | 支持操作、大小限制、stagingAvailable;先查询再展示按钮 | | ''auth.refresh'' | ''{}'' | ''{expiresAt,sessionRefreshed,nasTokenReturned:false}'';延长内部 NAS 会话,不延长云端授权 | | ''files.roots'' | ''{}'' | 远程只返回授权 root,不是全 NAS 磁盘清单 | | ''files.list'' | ''{path?,offset?}'' | entries、nextOffset;每页最多 32 项 | | ''files.mkdir'' | ''{path?,name}'' | 在当前授权目录下新建子目录 | | ''files.stat'' | ''{path}'' | name/path/directory/size/modified/etag/kind/mime,媒体元数据见下一节 | | ''files.read-chunk'' | ''{path,offset?,ifModified?}'' | base64 data、nextOffset、totalBytes、complete、lastModified;每块最多 16384 字节 | | ''files.delete'' | ''{path,confirm:true}'' | 移入回收站,返回 trashId;不永久删除 | | ''files.rmdir'' | ''{path,confirm?}'' | 空目录可删除,非空需 confirm:true;不能删除授权根目录 | | ''files.move'' | ''{path或from,toParent,name?}'' | 同 root 移动/改名,不静默覆盖目标 | | ''files.copy'' | ''{path或from,toParent,name?}'' | NAS 内复制,不经云端;100MiB/2048 节点受控上限,超限报错 | | ''files.replace'' | ''{path:目标,from:替换源,overwrite:true,confirm:true,ifMatch}'' | 将 NAS 已存在的源文件替换目标;原目标入回收站,源文件被移动 | | ''media.scan'' / ''photos.scan'' | ''{path?,recursive?,cursor?}'' | 每次受控扫描一批,返回 cursor、complete 和累计计数;持续调用至 complete | | ''files.search'' / ''photos.search'' | ''{query?,kind?,dateFrom?,dateTo?,place?,tags?,personId?,offset?,limit?}'' | 已索引且有权访问的结果;索引可能不完整,见 indexMayBePartial | | ''widgets.summary'' | ''{}'' | 当前授权范围的 photos/videos/indexedFiles/generatedAt | 下载必须保存 lastModified 并在后续分块传回 ifModified;出现 ''sync_download_source_changed'' 时丢弃旧片段重新下载,不拼接两个版本。服务端不宣称不可变文件快照。零字节文件返回 complete:true、空 data。 搜索和智能相册使用 NAS 持久 SQLite 索引,不把全部文件名发到 APP 才筛选。首次对授权目录调用扫描,建议 ''recursive:true'',用返回 cursor 循环;失败项目计入 errors,不等于全部照片成功解析。搜索 ''kind'' 使用 ''image/video/file'';标签为字符串数组;日期过滤基于 capturedAt,缺失时以已知文件时间处理(以返回字段为准)。地点来自 EXIF GPS 或用户填写的 place,未配置地理编码时不虚构城市名。 ===== 7. 照片信息、缩略图与编辑 ===== ''files.stat'' 对可解码图片返回 width/height、camera/make/lens、orientation、capturedAt、GPS、perceptualHash、projection 等已有字段;视频可通过 NAS ffprobe 返回 width/height、duration(秒)及已有编码信息。字段缺失表示源文件没有或 NAS 解码器不可用,不返回虚假值。''indexState:"metadata-only"'' 与 decoderError 供 APP 显示格式限制。 ^ operation ^ 参数 ^ 结果/规则 ^ | ''files.thumbnail'' | ''{path,size:"sm"或"m"}'' | JPEG base64,sm 最大边 128,m 最大边 384;必要时继续缩小,使 JPEG ≤22KiB;返回实际 width/height/etag | | ''metadata.set'' | ''{path,metadata:{place?,capturedAt?,favorite?,projection?}}'' | 持久保存到 NAS SQLite;projection 仅 null 或 equirectangular;不能伪造其他只读技术字段 | | ''tags.list'' | ''{}'' | 当前范围标签和计数 | | ''tags.set'' | ''{path,tags:["旅行","家庭"]}'' | 替换该文件完整标签列表 | | ''tags.remove'' | ''{path,tags:["旅行"]}'' | 移除指定标签,不影响其他标签 | | ''photos.edit'' | ''{path,rotate?,crop?,name?,overwrite?,confirm?,ifMatch?}'' | crop 为源图像像素 ''[x,y,width,height]'';rotate 为 0/90/180/270;默认另存,不修改原图 | 编辑输出限服务端实际支持的 JPEG/PNG;覆盖时必须显式 overwrite:true、confirm:true 及匹配的 ifMatch,旧版本进回收站。另存以 name 指定同目录新文件名。EXIF 方向、编辑后的元数据保留应由客户端依据实际返回确认,不能假定重编码保留全部原始 EXIF。 360° 识别依赖源文件 GPano/spherical metadata 或用户显式标注;2:1 宽高比本身不能证明是全景。APP 用 projection 决定球面展示;视频播放通过 files.read-chunk/NAS 既有媒体流接口实现,本接口不传播放器 UI,不把转码任务放云端。支持的解码格式以 NAS 已安装引擎为准。 ===== 8. 独立相册、人物、相似照片 ===== ^ operation ^ 参数 ^ 结果/规则 ^ | ''albums.create'' | ''{name}'' | ''{id,name,virtual:true,revision}'' | | ''albums.list'' | ''{}'' | 当前目录范围相册 | | ''albums.rename'' | ''{id,name}'' | 重命名相册 | | ''albums.delete'' | ''{id}'' | 只删虚拟相册,不删照片 | | ''albums.add'' / ''albums.remove'' | ''{albumId,paths:[...]}'' | 最多 100 路径;同一照片可在多个相册,不移动原件 | | ''albums.items'' | ''{albumId,offset?,limit?}'' | 当前仍有权限的成员 | | ''albums.smart.create'' | ''{name,rules:{query?,kind?,tags?,personId?,dateFrom?,dateTo?,place?}}'' | 创建规则相册 | | ''albums.smart.list'' | ''{}'' | 相册及规则 | | ''albums.smart.update'' | ''{id,rules:{...}}'' | 更新规则 | | ''albums.smart.query'' | ''{albumId,offset?,limit?}'' | 根据 NAS 索引动态查询,不能手工增删规则相册成员 | | ''faces.index'' | ''{path}'' | 本机 YuNet 检测+SFace 特征,返回人脸框、id、候选身份;人工确认,不自动认定身份 | | ''faces.list'' | ''{path}'' | 该照片已索引人脸、确认/拒绝状态 | | ''people.list'' | ''{}'' | 当前范围已确认人物和照片路径 | | ''people.rename'' | ''{personId,personName}'' | 重命名可见人物 | | ''faces.confirm'' | ''{faceId,personId}'' 或 ''{faceId,personName}'' | 确认到已有或新人物 | | ''faces.reject'' | ''{faceId}'' | 拒绝本条身份,保留审计状态 | | ''media.similar'' | ''{offset?,limit?}'' | dHash64 汉明距离≤6 的候选组,最多扫描 5000 张索引图;不自动删除 | 人脸特征向量只保存在 NAS,不出现在普通 APP 响应或云端。确认前 NAS 检查文件 etag;原照片已改变时需重新索引。索引同版本可复用,替换后的旧人脸失效。SFace 候选为相似度提示,需要用户确认。 人脸模型路径为 ''/opt/apuos-models/photos/yunet.onnx''、''sface.onnx'',需要可信 root 所有、不可被普通用户改写的固定模型文件和 NAS Python OpenCV 引擎。缺少模型返回 face_models_not_installed,不产生模拟人脸。接口遵循现有 AI 授权和资源控制策略,不另行修改许可模式。 ===== 9. 分享与成员 ===== ^ operation ^ 参数 ^ 结果/规则 ^ | ''shares.create'' | ''{paths:[...],members:[clientId...],permission:"read"或"read-write",expiresAt?}'' | id、仅创建时返回 shareKey、expiresAt;最长 30 天且不超过本次会话授权 | | ''shares.list'' | ''{}'' | 当前客户端创建的分享,不返回 shareKey | | ''shares.members'' | ''{id,members:[...]}'' | 创建者更新成员;成员最多 64 个已知 clientId | | ''shares.resolve'' | ''{id,shareKey}'' | 创建者或列出的客户端、相同 NAS 账号和目录授权、未过期密钥才能读取分享条目 | | ''shares.revoke'' | ''{id}'' | 创建者撤销,后续 resolve 拒绝 | 本版是**已配对设备之间的受限分享**。分享不能给陌生匿名访问者开放 NAS,也不能授予超过原账号/目录 ACL 的权限。read/read-write 记录分享意图,实际文件读写始终受 NAS 既有授权约束;APP 不应把它表示成独立跨账号写权限。密钥不拼 URL。邀请另一个用户访问其他账号目录,需要 NAS 原有账号/共享权限先授权;本接口不绕过它。 ===== 10. 持久上传与自动照片备份 ===== ''uploads.create'': {"requestId":"","path":"手机照片/2026-10","name":"IMG_0001.jpg","size":123456,"sha256":"<整文件64hex>","overwrite":false} 相同 requestId 和完全相同绑定重试返回同一个上传。更换目标、内容或覆盖策略后复用 requestId 返回 409。最大 64GiB,零字节也支持;实际还受 NAS 空间、配额和暂存卷限制。stagingAvailable:false 必须向用户报告存储不可用,不可把只建立清单当备份成功。 ^ operation ^ 参数/用途 ^ | ''uploads.list'' | ''{offset?,limit?}'';默认 16,最大 32,任务持久化 | | ''uploads.get'' | ''{id}'';重新读取 received、prefixDigest、status、receipt | | ''uploads.segment'' | ''{id,offset,data,chunkSha256}'';data 是最多 16384 字节的 base64,offset 为整文件字节偏移 | | ''uploads.action'' | ''{id,action:"pause"或"resume"或"cancel"}'' | | ''uploads.commit'' | ''{id}'';校验整文件并发布,可能先返回 verifying,轮询后再提交 | | ''uploads.forget'' | ''{id,confirm:true}'';仅清理已完成/取消任务及暂存,不删除已发布文件 | 每个持久块 2097152 字节,最后一块可更短。每块所有片段使用相同 chunkSha256,即**整个持久块**的 SHA-256,不是当前 16KiB 片段的哈希。''received'' 代表落盘的持久偏移;''fragmentReceived'' 只是当前内存接收位置,''fragmentDurable:false'' 时进程重启会回退到 received。断线重新查询,不从本机记忆的最后 ACK 盲目续写。 LAN 原生 APP 可用 ''PUT /api/v1/sync-client/v1/uploads/{id}/chunks/{offset}'',头 ''X-Chunk-SHA256'' 和 NAS Bearer,body 原始 2MiB 块;远程优先用 segment。''prefixDigest = SHA256(previousDigestBytes || chunkSHA256Bytes || uint64BE(chunkLength))'',初始值/完整链例子见 NAS 原有持久上传文档。 **只有 status:committed 且 receipt 的最终路径、size、sha256 与预期一致,才算备份完成。** verifying、ready、网络 delivered、100% 进度都不是成功。commit_uncertain 需要查询最终文件并核验,不自动重复覆盖。APP 保存本地资产 ID、任务 ID、requestId、文件指纹、服务器回执和失败原因,才能完成跨重启照片备份队列。 覆盖编辑后的原文件:先 ''files.stat'' 取得 etag,再创建新任务,传 ''overwrite:true,ifMatch:''。校验完成后 NAS 将旧版本移入回收站并发布新版本;etag 不匹配返回冲突,不接受无条件覆盖。普通自动备份建议新文件名,不自动删除 NAS 文件;双向删除传播不在本版协议中。 ===== 11. 客户端最小实现顺序与错误处理 ===== - 持久保存 Ed25519 身份,完成设备码+密钥配对、签名验证和自动授权等待。 - 创建直连,收到 authenticated 后读取 capabilities 与 files.roots/list。 - 本机计算 SHA-256,保存 requestId,分片上传,校验提交回执。 - 添加暂停、恢复、进程重启查询、空间不足和版本冲突提示。 - 调用 photos.scan 建 NAS 索引,再做缩略图、搜索、标签、虚拟相册。 - 按实际模型能力启用人脸索引和人工确认,再加入分享成员和智能相册。 ^ 状态/错误 ^ APP 行为 ^ | 400/invalid_request、reserved_parameter | 修正字段;不要自动重发相同错误 | | 401/unauthorized、sync_authentication_required | 云端用原密钥 resume;若已撤销或绑定无效,提示重新配对 | | 403/permission_denied、sync_scope_denied | 保留失败原因;不得改 root 或切换到云中继绕过 | | 409/etag、binding、checksum 冲突 | 重新读取文件或任务,提示冲突,不能静默覆盖 | | 413/image_pixel_limit、body limit | 降低客户端图片或请求大小;保留原文件 | | 429/capacity、dispatch_busy | 有上限的指数退避,不并发轰炸 NAS | | 503/transfer_storage_unavailable | 提示 NAS 暂存卷/空间/引擎不可用,保留队列后重试 | | face_models_not_installed/decoder unavailable | 隐藏相关操作或显示未安装依赖,不显示伪成功 | | 直连失败 | 明确显示网络限制;保持待传队列,无隐式 TURN/云文件中继 | 语言、APP 外观、相册系统权限和桌面小组件 UI 属于客户端;NAS 已有证书、两步验证和管理员设置仍使用各自受保护的接口,不通过这个目录授权暴露系统管理权。账号停用、权限修订、主账号凭据变化、配对撤销和会话过期都必须使已有文件操作失效。 ===== 12. 联调与实现索引 ===== 云端实现 ''backend/src/sync.rs''、''sync_client.rs'';NAS 实现 ''sync_direct/{mod,cloud,rtc,transfer}.rs''、''sync_client_routes.rs''、''sync_client/uploads.rs''、''storage/sync_library.rs'' 及原文件代理/接收器。机器可读操作目录在 NAS ''docs/backup/app-sync-operations.json''。本文件仅使用占位示例,不包含真实设备凭据。 联调记录应分别写清:本机自动化、Linux 文件代理 UID/ACL、实际 Mac→NAS WebRTC、真实人脸模型、外网不同 NAT 和 APP 原生后台权限。某项未验收不能由“构建通过”替代;具体发布与部署记录见 CHANGELOG。 ===== 文档来源与适用范围 ===== 本页与本机 APP 对接手册保持一致,覆盖云端 v2 配对和 NAS 2.0 能力;运行时仍以 capabilities 为准。旧版接口的兼容细节见 [[developer/cloud|云端信令兼容参考]]、[[developer/webrtc|WebRTC 兼容参考]]、[[developer/transfers|断点上传详解]]。 源文档:''apuos-server/docs/APUSYNC-APP-API.md'';源提交:''260a48606155c34bc6018d6078a83f8900affdbe'';源文档 SHA-256:''78e07a443fa9b447d8337ba1b0bf3054e8b399b1d71bc298c591ad1aa9871fe2''。本页不包含真实设备凭据。