19 KiB
设备注册与绑定接口简版
按当前 SaaS 代码整理,适用协议 ONBOARDING-1.2-SAAS。
正常流程:后台登记并放行 → 设备联网认证 → 打开手机绑定页 → 用户登录并提交绑定 → 设备按键确认 → 保存绑定结果 → 激活语音服务。
register 成功表示设备身份认证通过;binding_state=BOUND 才表示绑定成功;activation_state=READY 表示语音服务已就绪。
阅读约定
- 每个接口只列用途、输入和输出。输入示例为请求 JSON;输出示例为响应
data的主要字段,已省略部分状态和时间字段。 <…>都是占位值,须替换为实际返回值或设备生成值;UUID、密钥、签名不能照抄占位值。版本号也必须使用最新返回值。- 路径前加实际 SaaS 域名,例如
https://saas.example。请求使用Content-Type: application/json;下面 POST 请求统一携带X-Request-ID(UUID v4)。 - 设备接口除校时外,需要设备签名请求头;手机接口使用浏览器 Cookie,兑换入口后的 POST 还需
X-CSRF-Token,并按同源请求调用;运维接口需要运维登录凭证和X-Tenant-ID。具体签名见设备注册接口说明。 - 这是代码中的接口清单,实际可用性取决于部署开关;短信登录需要已配置并开启短信服务。
响应外层统一如下,后文的“输出”放在 data 中:
{"ok":true,"code":"OK","message":"","request_id":"<本次请求UUID>","server_time_ms":1789092000000,"recovery_action":"NONE","retry_after_ms":0,"data":{}}
一、后台预登记:运维后台或生产工装调用
1. 登记一台生产设备
POST /api/v1/ops/device-inventory
用途: 在平台建立设备档案,生成设备编号和凭据交付任务。MAC、型号、硬件版本用于识别设备;批次用于追溯。另需请求头 Idempotency-Key,避免重复建档。
输入:
{"factory_mac":"AABBCCDDEEFF","hardware_model":"KP01","hardware_revision":"A1","batch_no":"20260911-01"}
输出:设备生产编号、平台编号、交付任务和是否放行。
{"device_id":"KP01-AABBCCDDEEFF","platform_device_id":"<平台设备UUID>","enrollment_job_id":"<交付任务UUID>","released":false}
2. 获取设备认证凭据交付包
POST /api/v1/ops/device-inventory/{id}/credential-deliveries
用途: 将设备认证密钥加密交给生产工装。{id} 使用上一步的 platform_device_id;公钥属于接收工装。
输入:
{"enrollment_job_id":"<交付任务UUID>","receiver_public_key_pem":"<工装RSA公钥PEM>"}
输出:加密交付包。工装解密后可取得 device_id、device_key_b64、key_version 等设备认证资料。
{"algorithm":"RSA-OAEP-SHA256+A256GCM","enrollment_job_id":"<交付任务UUID>","wrapped_key_b64":"<加密的数据密钥>","ciphertext_b64":"<凭据密文>","expires_at_ms":1789092600000}
3. 确认凭据写入并放行
POST /api/v1/ops/device-inventory/{id}/release
用途: 确认设备凭据已写入并读回校验,允许设备开始联网认证。evidence_ref 是校验记录的引用。
输入:
{"enrollment_job_id":"<交付任务UUID>","evidence_ref":"factory-check/20260911/001","expected_key_version":1}
输出:该设备已放行。
{"device_id":"KP01-AABBCCDDEEFF","platform_device_id":"<平台设备UUID>","enrollment_job_id":"<交付任务UUID>","released":true}
二、设备认证与绑定:设备调用
4. 获取平台时间和注册挑战
POST /api/v1/device/time-challenges
用途: 取得可信时间和本次注册需要的一次性凭证。boot_id 标识本次启动,client_nonce 是设备生成的 16 字节随机数,以 Base64URL 无填充编码。
输入:
{"device_id":"KP01-AABBCCDDEEFF","key_version":1,"boot_id":"<本次启动UUID>","client_nonce":"<16字节随机数的Base64URL>"}
输出:带签名的校时数据包。设备验签并解码后读取 challenge_id、server_time_ms、expires_at_ms,同时核对设备编号、启动编号和随机数。
{"payload_b64":"<校时数据的Base64URL>","mac_hex":"<平台响应签名>"}
5. 设备注册/启动认证
POST /api/v1/device/register
用途: 校验设备身份,记录本次启动和软件版本,返回绑定及激活状态。设备编号通过请求头 X-Device-ID 传入。首次联网和重新启动都调用。
输入:
{"challenge_id":"<上一步challenge_id>","boot_id":"<本次启动UUID>","hardware_model":"KP01","hardware_revision":"A1","firmware_version":"1.0.0","sdk_version":"1.0.0"}
输出:协议版本和当前设备状态。下面表示认证通过,但尚未绑定。
{"contract_version":"ONBOARDING-1.2-SAAS","state":{"device_id":"KP01-AABBCCDDEEFF","binding_state":"UNBOUND","activation_state":"NOT_STARTED","next_action":"WAIT_BIND"}}
6. 创建一次绑定设置会话
POST /api/v1/device/setup-sessions
用途: 为本次手机绑定建立临时会话。local_setup_id 由设备生成,表示本次设置入口;transport_profile 固定为 LOCAL_WEB。
输入:
{"boot_id":"<本次启动UUID>","local_setup_id":"<本次设置入口UUID>","transport_profile":"LOCAL_WEB"}
输出:云端会话编号,有效期最长 15 分钟;poll_after_ms 表示建议多久后查询状态。
{"setup_id":"<设置会话UUID>","expires_at_ms":1789092900000,"poll_after_ms":2000,"mode":"ENROLL"}
7. 获取手机绑定页面入口
POST /api/v1/device/handoffs
用途: 获取手机绑定页地址和一次性入口令牌。设备生成 verifier,将其 SHA256 结果以 Base64URL 无填充编码后,作为 code_challenge。
输入:
{"setup_id":"<设置会话UUID>","local_setup_id":"<本次设置入口UUID>","code_challenge":"<verifier的SHA256结果的Base64URL>"}
输出:页面地址、一次性令牌和到期时间。令牌最长 90 秒有效,只能兑换一次;verifier 保留在设备侧,由设备一并交给手机。
{"entry_url":"https://saas.example/onboarding/entry","handoff_token":"<一次性令牌>","expires_at_ms":1789092090000}
手机实际打开:
https://saas.example/onboarding/entry#token=<handoff_token>&verifier=<设备生成的verifier>
8. 查询绑定进度/领取平台命令
POST /api/v1/device/poll
用途: 设备定期查询是否有人申请绑定,以及是否有激活配置。known_revision 是设备已知的状态版本,首次可传 0。
输入:
{"setup_id":"<设置会话UUID>","boot_id":"<本次启动UUID>","known_revision":0}
输出示例:手机已经提交绑定申请,平台要求设备提示用户按键确认。payload 中的确认字段用于下一接口。
{"state":{"binding_state":"CLAIM_PENDING"},"command":{"command_id":"<绑定申请UUID>","type":"CONFIRM_CLAIM","expires_at_ms":1789092120000,"payload":{"claim_id":"<绑定申请UUID>","challenge":"<平台按键挑战>","binding_digest":"<平台绑定摘要>","expected_binding_generation":0,"masked_account":"尾号1234"}},"poll_after_ms":2000}
没有命令时 command=null。绑定后可能返回 APPLY_ACTIVATION,其中包含激活任务、配置版本、配置包及校验值;见第四部分。
9. 设备按键确认绑定
POST /api/v1/device/claim-confirmations
用途: 用户真实按下设备确认键后,设备上报确认,平台正式建立归属关系。前四个字段取自上一步命令,press_event_id 标识本次实际按键。
输入:
{"claim_id":"<绑定申请UUID>","challenge":"<平台按键挑战>","binding_digest":"<平台绑定摘要>","expected_binding_generation":0,"press_event_id":"<本次按键事件UUID>"}
输出:BOUND 表示正式绑定成功;binding_generation 是绑定代数,绑定或解绑发生后会变化。
{"claim_id":"<绑定申请UUID>","already_applied":false,"state":{"binding_state":"BOUND","binding_generation":1,"next_action":"RESUME_ACTIVATION"}}
10. 设备确认已保存绑定结果
POST /api/v1/device/state-acks
用途: 设备将最新平台状态保存到本地后回执。状态版本、绑定代数和摘要使用平台返回值;local_generation 是本次启动的本地保存序号,不能倒退。
输入:
{"device_revision":5,"binding_generation":1,"local_generation":1,"state_hash":"<平台返回的state_hash>"}
输出:平台已收到保存回执;设备继续查询状态,等允许后关闭临时设置入口。
{"accepted_revision":5,"setup_can_close":false,"close_after_ms":30000,"next_action":"POLL_FOR_CLOSE"}
11. 关闭本次临时设置入口
POST /api/v1/device/setup-close
用途: 绑定流程收尾,关闭临时设置入口。正常完成时等待 poll 返回 setup_can_close=true,再传 SUCCESS;其他原因可为 TIMEOUT、CANCELLED、NETWORK_SWITCH。
输入:
{"setup_id":"<设置会话UUID>","reason":"SUCCESS","final_revision":5}
输出:允许关闭;关闭入口不等于语音激活完成,激活可继续进行。
{"close_allowed":true,"state":{"binding_state":"BOUND"}}
三、手机绑定页面:SaaS 前端调用
用户看到的页面是 /onboarding/setup,正常由带一次性凭证的 /onboarding/entry 进入。登录账号及目标设备由会话确定,无需前端在绑定请求中再传手机号或设备编号。
12. 兑换设备提供的一次性入口
POST /api/v1/web/handoffs/exchange
用途: 手机用设备提供的令牌和校验值建立绑定会话。服务端通过 Cookie 保持该会话,并返回后续 POST 使用的 csrf_token。
输入:
{"token":"<设备提供的handoff_token>","verifier":"<设备提供的verifier>"}
输出:注意此接口的响应 data 内还有一层 data,这是当前代码的实际结构。
{"csrf_token":"<后续请求使用的CSRF值>","data":{"setup_id":"<设置会话UUID>","device_id":"KP01-AABBCCDDEEFF","sms_enabled":true,"state":{"binding_state":"UNBOUND","binding_generation":0}}}
13. 发送手机登录验证码
POST /api/v1/web/auth/sms/send
用途: 给用户手机号发送登录验证码。phone_e164 含国家区号,purpose 固定为 LOGIN。
输入:
{"phone_e164":"+8613800000000","purpose":"LOGIN"}
输出:验证码任务编号、过期时间、多久后可重发;不会在接口响应中返回验证码。
{"sms_challenge_id":"<短信验证UUID>","expires_at_ms":1789092300000,"resend_after_ms":60000}
14. 验证短信并登录
POST /api/v1/web/auth/sms/verify
用途: 验证用户输入的短信验证码,建立登录状态。
输入:
{"sms_challenge_id":"<短信验证UUID>","code":"123456"}
输出:登录成功,并返回更新后的 CSRF 值;前端后续请求改用该值。
{"csrf_token":"<更新后的CSRF值>","data":{"authenticated":true,"setup_id":"<设置会话UUID>"}}
15. 查询可以绑定的家庭和使用者
POST /api/v1/web/profiles
用途: 列出当前用户有权选择的家庭和设备使用者。
输入:
{}
输出:家庭和使用者列表。选择目标后,后续绑定使用 elder_id。
{"items":[{"household_id":"<家庭UUID>","household_name":"我的家庭","elder_id":"<使用者UUID>","preferred_name":"爸爸"}]}
16. 创建家庭/设备使用者
POST /api/v1/web/profiles/create
用途: 没有可选使用者时创建档案。新建家庭传 household_name;已有家庭则改传 household_id。preferred_name 是使用者称呼。
输入示例:新建家庭和使用者。
{"household_name":"我的家庭","preferred_name":"爸爸"}
输出:新建的家庭和使用者编号。
{"household_id":"<家庭UUID>","elder_id":"<使用者UUID>"}
17. 提交设备绑定申请
POST /api/v1/web/claims
用途: 申请把当前设备绑定给选中的使用者。elder_id 来自上面两个接口,expected_binding_generation 来自最新设备状态。
输入:
{"elder_id":"<使用者UUID>","expected_binding_generation":0}
输出:申请编号和状态。CLAIM_PENDING 表示等待设备按键确认,还没有正式绑定。
{"claim_id":"<绑定申请UUID>","expires_at_ms":1789092120000,"state":{"binding_state":"CLAIM_PENDING","binding_generation":0}}
18. 查询手机页面上的绑定进度
POST /api/v1/web/setup/status
用途: 手机刷新等待确认、已绑定和激活进度。device_saved 表示设备是否已确认保存绑定结果。
输入:
{}
输出示例:设备已经绑定并保存结果。
{"binding_id":"<正式绑定UUID>","device_saved":true,"authenticated":true,"state":{"device_revision":5,"binding_generation":1,"binding_state":"BOUND"}}
19. 手机确认已收到绑定成功结果
POST /api/v1/web/setup/ack
用途: 告诉平台手机页面已收到绑定成功结果,配合设备完成设置收尾。三个字段均取自上一步响应。
输入:
{"binding_id":"<正式绑定UUID>","binding_generation":1,"received_revision":5}
输出:回执已接收。
{"accepted":true}
四、绑定后的语音激活:设备继续调用
配置由平台后台任务准备,通过前面的 POST /api/v1/device/poll 下发。设备不用直接调用供应商注册接口。
poll 的激活命令输出示例(仍是响应 data 内的字段):
{"command":{"command_id":"<激活任务UUID>","type":"APPLY_ACTIVATION","payload":{"activation_id":"<激活任务UUID>","config_version":1,"config_digest":"<配置摘要>","config_b64":"<编码后的配置包>","config_mac":"<配置签名>","key_version":1,"config_saved":false,"engine_initialized":false}}}
配置包解码后包含设备编号、绑定代数、配置版本、到期时间、IoT 连接资料、单设备密钥、机器人及 RTC 配置。设备校验后保存,用于连接语音服务。
20. 确认已保存激活配置
POST /api/v1/device/activation-acks
用途: 设备校验并保存配置后回执。任务、版本和摘要取自激活命令;绑定代数取自当前状态。
输入:
{"activation_id":"<激活任务UUID>","config_version":1,"binding_generation":1,"local_generation":2,"config_digest":"<配置摘要>"}
输出:配置保存已确认。
{"accepted":true,"config_version":1}
21. 申请执行初始化/首次通话测试
POST /api/v1/device/activation-attempts
用途: 执行 SDK 初始化或首次通话测试前,申请本次执行许可。attempt_id 由设备生成;stage 为 ENGINE_INIT 或 CONVERSATION_TEST。首次通话必须由用户主动发起,并传 user_initiated=true。
输入示例:申请初始化 SDK。
{"activation_id":"<激活任务UUID>","config_version":1,"attempt_id":"<本次执行UUID>","stage":"ENGINE_INIT","user_initiated":false}
输出:本次执行许可和到期时间。许可最长 120 秒,首次通话测试最长 60 秒。
{"activation_id":"<激活任务UUID>","attempt_id":"<本次执行UUID>","stage":"ENGINE_INIT","permit_token":"<短时执行许可>","permit_expires_at_ms":1789092120000,"maximum_test_seconds":60}
22. 上报初始化/首次通话测试结果
POST /api/v1/device/activation-results
用途: 上报执行结果。outcome 为 SUCCESS、DEFINITE_FAILURE 或 UNKNOWN;成功时 sdk_result 必须为 0。
输入示例:真实首次通话完成后上报成功,evidence 引用平台已记录的会话和事件。初始化结果使用 stage=ENGINE_INIT,evidence 可传 {}。
{"activation_id":"<激活任务UUID>","attempt_id":"<首次通话执行UUID>","stage":"CONVERSATION_TEST","sdk_result":0,"outcome":"SUCCESS","evidence":{"conversation_id":"<测试会话UUID>","started_event_id":"<开始事件ID>","first_audio_event_id":"<首音频事件ID>","ended_event_id":"<结束事件ID>"}}
输出示例:首次通话验收通过,设备可正常使用。仅初始化成功不会直接返回 READY。
{"duplicate":false,"state":{"binding_state":"BOUND","activation_state":"READY","next_action":"ENTER_READY"}}
首次通话本身复用既有 POST /device/v3/agent-sessions 及会话事件接口;不能只调用本节的结果接口就完成验收。原有通话字段和新增测试许可字段见实施说明中的首通流程。
五、页面恢复和可选操作
这些操作按需要使用,不是每次首次绑定都必须调用。
| 接口 | 用途 | 输入示例 | 输出示例(响应 data,节选) |
|---|---|---|---|
GET /api/v1/web/setup/session |
页面刷新后恢复设置会话,取得新的 CSRF 值 | 无正文,浏览器携带设置 Cookie | {"csrf_token":"<新CSRF值>","data":{"authenticated":true,"state":{"binding_state":"CLAIM_PENDING"}}} |
POST /api/v1/web/setup/auth-link |
使用已有 OIDC 登录状态,作为短信登录的另一种方式 | 正文 {},另带 Authorization: Bearer <登录令牌> |
{"csrf_token":"<新CSRF值>","data":{"authenticated":true,"setup_id":"<设置会话UUID>"}} |
POST /api/v1/web/claims/cancel |
用户取消自己的待确认绑定申请 | {"claim_id":"<绑定申请UUID>","expected_binding_generation":0} |
{"state":{"binding_state":"UNBOUND"}} |
GET /api/v1/web/onboarding/devices |
设置完成后,通过登录账号查询已绑定设备 | 无正文,携带账号 Cookie 或 OIDC 登录令牌 | {"items":[{"device_id":"KP01-AABBCCDDEEFF","activation_state":"READY","household_name":"我的家庭","preferred_name":"爸爸"}]} |
最常用字段怎么理解
| 字段 | 含义/来源 |
|---|---|
device_id |
生产设备编号,例如 KP01-AABBCCDDEEFF |
platform_device_id |
平台设备 UUID,用于后台设备接口中的 {id} |
boot_id |
设备本次启动编号,重启后重新生成 |
setup_id |
本次临时绑定流程编号,由平台创建 |
elder_id |
使用者档案编号,由手机选择或创建 |
claim_id |
待设备按键确认的绑定申请编号 |
binding_id |
已正式生效的绑定记录编号,由手机状态接口返回 |
binding_generation |
绑定代数,用于区分绑定、解绑后的不同归属状态 |
device_revision |
平台状态版本,用于判断拿到的是哪一版状态 |
activation_id |
激活任务编号,通过设备 poll 命令取得 |
attempt_id |
设备生成的一次初始化或首次通话执行编号 |
接口路径与请求字段依据当前 HTTP 路由、设备流程、手机流程、短信登录、激活流程及生产登记核对。完整 31 项首次接入操作(含管理、退出和异常处理)见字段清单。