Hopp v1.0.33 API 使用详解
Hopp v1.0.33 服务端 REST API 实战:注册登录与 JWT、团队与房间管理、LiveKit 入房凭证解码、在线状态与错误码,全部 curl 实测回显。
Hopp v1.0.33 API 使用详解
目标:系统梳理 Hopp 自托管服务端的 REST API——认证方式、各业务域端点、请求/响应结构与错误码,全部请求基于自托管实例
http://localhost:8147逐条实测,响应为真实回显。基于 v1.0.33,配合《Hopp Docker Compose 部署指南》的已部署实例使用。
一、环境信息
- 操作系统:Ubuntu 22.04 LTS(x86_64)
- Hopp 版本:v1.0.33(backend 镜像 2026-10-17 构建)
- API 基址:
http://localhost:8147(生产域名形态为https://<DOMAIN>,路径结构完全一致) - API 规格:官方仓库
backend/api-files/openapi.yaml(OpenAPI 3,1812 行);另有backend/api-files/下 Bruno 集合(Create Room.bru、Sign In.bru等)可直接导入 Bruno 复现 - 客户端工具:curl(本文所有示例)、Bruno/Postman(导入上述集合)
- 演示账号:
admin@hopp.local/q1w2e3r4abcd(首个注册账号,团队管理员)
二、前置条件
- 已部署并可访问服务端(
GET /api/health返回OK),部署步骤见《Hopp Docker Compose 部署指南》; - 认证方式:除健康检查、注册/登录外,全部接口要求
Authorization: Bearer <JWT>请求头,JWT 由注册或登录接口签发; - 参数验证与生效核对方法(配置侧)见《Hopp v1.0.33 配置参数详解》第三节。
三、API 总览
协议与约定:
- 协议:REST + JSON(
Content-Type: application/json),路径统一前缀/api/,无版本号段(版本随镜像演进); - 认证:Bearer JWT。注册返回的 JWT 实测有效期约 1 年(payload
exp比签发时间多约 31555200 秒),房间加入类 LiveKit token 有效期 24 小时(见 5.4 节实测解码); - 限流:登录接口在多次失败后返回
429 Too many sign-in attempts(官方规格定义);注册/登录可带turnstile_token字段,仅在服务端启用 Cloudflare Turnstile 时校验,自托管默认关闭; - 分页:房间、队友等列表接口为全量返回,规格中无分页/过滤/排序参数;
- 实时通道:另有
GET /api/auth/websocketWebSocket 端点(升级为 101),承载 presence 等实时事件,供桌面/Web 客户端使用。
端点域一览(实测核对的常用域):
| 域 | 端点 | 方法 | 认证 |
|---|---|---|---|
| 健康与指标 | /api/health、/api/metrics | GET | 无 |
| 注册登录 | /api/sign-up、/api/sign-in | POST | 无 |
| 当前用户 | /api/auth/user、/api/auth/update-user-name | GET / PUT | Bearer |
| 团队 | /api/auth/team、/api/auth/get-invite-uuid、/api/auth/send-team-invites、/api/auth/change-team/{uuid} | PATCH / GET / POST / POST | Bearer |
| 队友 | /api/auth/teammates、/api/auth/teammates/{userId} | GET / DELETE | Bearer |
| 房间 | /api/auth/room、/api/auth/rooms、/api/auth/room/{id} | POST / GET / GET·DELETE | Bearer |
| 在线状态 | /api/auth/rooms/presence、/api/auth/calls/presence | GET | Bearer |
| LiveKit | /api/auth/livekit/server-url、/api/auth/websocket | GET | Bearer |
| 邀请 | /api/invitation-details/{uuid} | GET | 无 |
四、认证与首次调用
4.1 注册(创建团队管理员)
首个注册账号自动成为团队管理员(与官方文档一致)。请求体必填 first_name、last_name、email、password,建团队时带 team_name(加入已有团队则改传 team_invite_uuid):
curl -s -X POST http://localhost:8147/api/sign-up \
-H 'Content-Type: application/json' \
-d '{"first_name":"Ad","last_name":"Min","email":"admin@hopp.local","password":"q1w2e3r4abcd","team_name":"Hopp Demo Team"}'
实测回显(token 实测总长 141 字符):
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFkbWluQGhvcHAubG9jYWwiLCJleHAiOjE4MjQ4MTM4NDB9.s8pxwS-Hbk203dTsDEr9V63afOOTd4VUWdoSTmhJeWo"}
HTTP 201
两次错误实测——密码过短与邮箱重复:
curl -s -X POST http://localhost:8147/api/sign-up -H 'Content-Type: application/json' \
-d '{"first_name":"T","last_name":"E","email":"t1@hopp.local","password":"short1","team_name":"T1"}'
{"message":"Password must be at least 12 characters long."}
HTTP 400
curl -s -X POST http://localhost:8147/api/sign-up -H 'Content-Type: application/json' \
-d '{"first_name":"Ad","last_name":"Min","email":"admin@hopp.local","password":"q1w2e3r4abcd","team_name":"Hopp Demo Team"}'
{"message":"user with this email already exists"}
HTTP 409
💡 说明:密码策略为 12-72 位(服务端硬校验),演示口令在固定测试口令
q1w2e3r4基础上补足 12 位。JWT 结构见 4.3 节。
4.2 登录
curl -s -X POST http://localhost:8147/api/sign-in \
-H 'Content-Type: application/json' \
-d '{"email":"admin@hopp.local","password":"q1w2e3r4abcd"}'
实测回显(结构与注册一致,仅返回 token):
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImFkbWluQGhvcHAubG9jYWwiLCJleHAiOjE4MjQ4MTM4NDB9.s8pxwS-Hbk203dTsDEr9V63afOOTd4VUWdoSTmhJeWo"}
HTTP 200
错误口令实测:
curl -s -X POST http://localhost:8147/api/sign-in -H 'Content-Type: application/json' \
-d '{"email":"admin@hopp.local","password":"wrongpass12345"}'
{"message":"Invalid email or password"}
HTTP 401
4.3 JWT 结构解析
对登录所得 token 做 base64 解码(payload 段):
echo 'eyJlbWFpbCI6ImFkbWluQGhvcHAubG9jYWwiLCJleHAiOjE4MjQ4MTM4NDB9' | base64 -d
实测回显:
{"email":"admin@hopp.local","exp":1824813840}
会话 JWT 仅含 email 与 exp(约 1 年有效期),无角色字段——管理员身份在服务端按"首个账号"逻辑与 is_admin 存储判定,不进 token。
五、各 API 域详解
5.1 健康与指标
curl -s http://localhost:8147/api/health
实测回显(text/plain):
OK
/api/metrics 暴露 Prometheus 指标(供抓取,非调试接口),实测头部:
# HELP callstate_active_rooms Number of active call rooms in the presence snapshot
# TYPE callstate_active_rooms gauge
callstate_active_rooms 0
# HELP go_gc_duration_seconds A summary of the wall-time pause (stop-the-world) duration in garbage collection cycles.
# TYPE go_gc_duration_seconds summary
go_gc_duration_seconds{quantile="0"} 2.0827e-05
(输出节选,实测首尾;完整输出为标准 Go 进程指标与业务 gauge 混合,可按行过滤 callstate_ 前缀观察房间在线情况。)
5.2 当前用户
curl -s http://localhost:8147/api/auth/user -H "Authorization: Bearer $TOKEN"
实测回显:
{"id":"01a187b0-4e38-754b-81cf-8c7a27405db6","first_name":"Ad","last_name":"Min","email":"admin@hopp.local","is_admin":true,"team_id":1,"avatar_url":"","created_at":"2026-10-29T12:44:00.992566Z","updated_at":"2026-10-29T12:44:00.992566Z","password":"","metadata":null,"email_subscriptions":{"marketing_emails":true,"linux_waitinglist":false},"unsubscribe_id":"92d210a4-dc26-4bb2-906c-056adba5db45","is_pro":true,"is_trial":false}
要点:is_admin:true 与 team_id:1 证实首个账号为管理员;is_pro:true 表明自托管实例不计费限制。
改名(PUT,必填 first_name/last_name):
curl -s -X PUT http://localhost:8147/api/auth/update-user-name \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"first_name":"Admin","last_name":"Demo"}'
实测回显(输出节选,实测首尾):
{"id":"01a187b0-4e38-754b-81cf-8c7a27405db6","first_name":"Admin","last_name":"Demo","email":"admin@hopp.local","is_admin":true,"team_id":1,"avatar_url":"","created_at":"2026-10-29T12:44:00.992566Z","updated_at":"2026-10-29T12:54:07.378366Z"}
HTTP 200
5.3 团队管理
获取团队邀请 UUID(供新成员注册时入队):
curl -s http://localhost:8147/api/auth/get-invite-uuid -H "Authorization: Bearer $TOKEN"
实测回显:
{"invite_uuid":"01a187b9-bf6d-7d76-a15a-cf9b7d365255","team_name":"Hopp Demo Team"}
HTTP 200
团队改名(PATCH——注意不是 GET,实测 GET /api/auth/team 返回 404 {"message":"Not Found"}):
curl -s -X PATCH http://localhost:8147/api/auth/team \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"Hopp Demo Team"}'
实测回显:
{"ID":1,"CreatedAt":"2026-10-29T12:44:00.951661Z","UpdatedAt":"2026-10-29T12:54:19.769522692Z","DeletedAt":null,"name":"Hopp Demo Team","is_manual_upgrade":false,"billing_email":null}
HTTP 200
💡 技术拓展:REST 更新操作的动词选型业界有两派——① PUT 全量替换(幂等,客户端提交完整资源);② PATCH 部分更新(只提交变化字段)。Hopp 实际混用两派:用户名更新用
PUT /api/auth/update-user-name(提交全名两字段),团队更新用PATCH /api/auth/team(可只传name),调用前按端点核对方法即可。
相关端点:POST /api/auth/send-team-invites 批量发邀请(依赖 4.3 节邮件配置,未配置 Resend 时邮件不可达,该接口在纯内网部署中无实际效果);POST /api/auth/change-team/{uuid} 凭邀请 UUID 切换团队;GET /api/invitation-details/{uuid} 免认证查询邀请详情。
5.4 房间管理
创建房间(POST):
curl -s -X POST http://localhost:8147/api/auth/room \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"pairing-room-1"}'
实测回显:
{"id":"01a187b0-c09b-77ef-98af-45962a88f4ff","name":"pairing-room-1","user_id":"01a187b0-4e38-754b-81cf-8c7a27405db6","created_at":"2026-10-29T12:44:30.235528086Z","updated_at":"2026-10-29T12:44:30.235528086Z","team_id":1,"type":"default","temp":false}
HTTP 200
房间列表(GET):
curl -s http://localhost:8147/api/auth/rooms -H "Authorization: Bearer $TOKEN"
实测回显:
[{"id":"01a187b0-c09b-77ef-98af-45962a88f4ff","name":"pairing-room-1","user_id":"01a187b0-4e38-754b-81cf-8c7a27405db6","created_at":"2026-10-29T12:44:30.235528Z","updated_at":"2026-10-29T12:44:30.235528Z","team_id":1,"type":"default","temp":false}]
HTTP 200
获取入房凭证(GET /api/auth/room/{id})——这是客户端进房的核心接口,一次返回三个 LiveKit join token:
curl -s http://localhost:8147/api/auth/room/01a187b0-c09b-77ef-98af-45962a88f4ff -H "Authorization: Bearer $TOKEN"
实测回显(token 实测很长,此处节选并解码说明):
{"audioToken":"eyJhbGciOi...9wrTs","videoToken":"eyJhbGciOi...wCHUKA9Y","cameraToken":"eyJhbGciOi...QbiUofNo","participant":"01a187b0-4e38-754b-81cf-8c7a27405db6"}
HTTP 200
解码 audioToken 的 payload(实测):
{"exp":1793364859,"identity":"room:01a187b0-c09b-77ef-98af-45962a88f4ff:01a187b0-4e38-754b-81cf-8c7a27405db6:audio","iss":"457r6TsKLci8qj15fA2X8l6/38fWCGhtek8oe/BEQeE=","name":"Admin Demo","nbf":1793278459,"sub":"room:01a187b0-c09b-77ef-98af-45962a88f4ff:01a187b0-4e38-754b-81cf-8c7a27405db6:audio","video":{"canUpdateOwnMetadata":true,"room":"01a187b0-c09b-77ef-98af-45962a88f4ff","roomJoin":true}}
要点:iss 即 .env 中 LIVEKIT_API_KEY(凭证链路贯通的直接证据);nbf→exp 实测相差 86400 秒(24 小时有效期);音频/视频/摄像头三个身份各持独立 token,房间屏幕共享与摄像头画面对应不同媒体轨道。
删除房间(DELETE,实测返回 204 无响应体):
curl -s -X DELETE http://localhost:8147/api/auth/room/01a187b0-c09b-77ef-98af-45962a88f4ff -H "Authorization: Bearer $TOKEN" -w '\nHTTP %{http_code}\n'
实测回显:
HTTP 204
删除后复查列表(实测):
[]
HTTP 200
5.5 LiveKit 集成
curl -s http://localhost:8147/api/auth/livekit/server-url -H "Authorization: Bearer $TOKEN"
实测回显:
{"url":"ws://localhost:7880"}
HTTP 200
该返回值即客户端建立 WebRTC 信令的目标地址,取值来源于部署配置 LIVEKIT_SERVER_URL(配置详解见 4.1 节)。GET /api/auth/websocket 为客户端实时通道(WebSocket 升级),curl 实测会挂起等待协议升级,常规排查用 curl -i -N -H 'Connection: Upgrade' -H 'Upgrade: websocket' ... 观察 101 响应。
5.6 队友与在线状态
curl -s http://localhost:8147/api/auth/teammates -H "Authorization: Bearer $TOKEN"
实测回显(单管理员团队暂无队友):
[]
HTTP 200
房间在线状态(room → 参与者 ID 数组的映射):
curl -s http://localhost:8147/api/auth/rooms/presence -H "Authorization: Bearer $TOKEN"
实测回显(建房后、无人进房时):
{"rooms":{"01a187b0-c09b-77ef-98af-45962a88f4ff":[]}}
HTTP 200
GET /api/auth/calls/presence 返回队友的 1:1 通话与房间通话在线情况,结构同为映射类型;两者共同驱动客户端侧的"谁在线、在哪个房间"指示。
六、错误码与限流约定
实测与规格汇总的错误行为:
| 状态码 | 触发场景 | 实测/规格响应 |
|---|---|---|
| 400 | 请求体非法(如密码 <12 位) | {"message":"Password must be at least 12 characters long."}(实测) |
| 401 | 未带 token / token 无效 | {"message":"missing or malformed jwt"}(实测) |
| 401 | 凭据错误 | {"message":"Invalid email or password"}(实测) |
| 404 | 方法/路径不匹配(如 GET /api/auth/team) | {"message":"Not Found"}(实测) |
| 409 | 注册邮箱已存在 | {"message":"user with this email already exists"}(实测) |
| 429 | 登录失败次数过多 | Too many sign-in attempts; retry after the window elapses(官方规格) |
| 500 | 服务端内部错误 | Error 结构(官方规格) |
错误响应统一为 {"message": "..."} 单字段结构;认证失败一律 401 且不区分"用户不存在/密码错误",避免账号枚举。
七、分页/过滤/排序通用约定
实测房间与队友列表接口均无 page/limit/排序参数,为团队维度全量返回(官方规格亦未定义分页结构)。Hopp 的数据模型是"团队内小集合"(房间数、队友数为团队级少量数据),API 设计上未引入分页;如自托管实例房间数量增长到千级需要分页,需关注上游仓库对该接口的演进。
八、常见问题(FAQ)
Q1:请求一直 401 missing or malformed jwt?
检查三处:请求头是 Authorization: Bearer <token>(Bearer 后有空格);token 取的是注册/登录响应里的 token 字段而非 LiveKit 三件套 join token(后者是房间媒体凭证,只对 LiveKit 信令有效);token 是否过期(会话 token 约 1 年,join token 24 小时)。
Q2:GET /api/auth/team 返回 404?
该路径只支持 PATCH(改团队名)。查团队信息走 GET /api/auth/user 的 team_id 与 get-invite-uuid 的 team_name 字段,规格中没有独立"读团队"端点。
Q3:创建房间成功但 Web 端看不到?
房间列表按团队维度返回,确认当前账号 team_id 与建房账号一致;多团队账号用 POST /api/auth/change-team/{uuid} 切换后重查。
Q4:能直接用 API 全程完成结对吗? 不能。REST API 覆盖账号/团队/房间/凭证管理,屏幕共享与远程控制本体由桌面客户端经 LiveKit 媒体面(UDP 50000-60000)完成,API 拿到的三个 join token 就是客户端与媒体面之间的桥——服务端 API 无法替代桌面端采集屏幕与注入键鼠输入。