Hopp v1.0.33 API 使用详解

Hopp v1.0.33 服务端 REST API 实战:注册登录与 JWT、团队与房间管理、LiveKit 入房凭证解码、在线状态与错误码,全部 curl 实测回显。

HoppHoppAPI实时协作2026/10/29

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(首个注册账号,团队管理员)

二、前置条件

三、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/websocket WebSocket 端点(升级为 101),承载 presence 等实时事件,供桌面/Web 客户端使用。

端点域一览(实测核对的常用域):

域端点方法认证
健康与指标/api/health、/api/metricsGET无
注册登录/api/sign-up、/api/sign-inPOST无
当前用户/api/auth/user、/api/auth/update-user-nameGET / PUTBearer
团队/api/auth/team、/api/auth/get-invite-uuid、/api/auth/send-team-invites、/api/auth/change-team/{uuid}PATCH / GET / POST / POSTBearer
队友/api/auth/teammates、/api/auth/teammates/{userId}GET / DELETEBearer
房间/api/auth/room、/api/auth/rooms、/api/auth/room/{id}POST / GET / GET·DELETEBearer
在线状态/api/auth/rooms/presence、/api/auth/calls/presenceGETBearer
LiveKit/api/auth/livekit/server-url、/api/auth/websocketGETBearer
邀请/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 无法替代桌面端采集屏幕与注入键鼠输入。

九、参考链接