权限范围与可用 API
17 个 scope 与它们解锁的接口族、第三方与第一方的能力边界、令牌能在哪些域名上调用,以及 scope 不够时会看到什么。
Scope 目录
授权页上给用户看的中文文案由 scope 决定 —— 申请得越多,用户越可能点「拒绝」。
| scope | 授权页文案 | 能拿到什么 |
|---|---|---|
openid | 身份标识 | sub 与 id_token |
profile | 基本资料 | 显示名等公开资料 |
email | 邮箱 | 用户邮箱与验证状态(绑定了才有) |
phone | 手机号 | 用户手机号与验证状态(绑定了才有) |
offline_access | 离线访问 | 语义上的长期续期(实际授权码总会发 refresh token) |
profile:read / profile:write | 个人资料 · 读取 / 管理 | /v0/auth/me、收藏、动态、头像横幅上传 |
account_security:read / :write | 账号安全 · 读取 / 管理 | 会话列表、MFA、Passkey、邮箱/手机换绑 |
game:read / game:write | 游戏资料 · 读取 / 管理 | /v1/games/**(档案、成绩、最佳成绩、推数据、删除申请) |
social:read / social:write | 社区与社交 · 读取 / 管理 | 谱面评论(过渡期还在 /v0/social/**);社交面本身只服务第一方 |
event:read / event:write | 赛事 · 读取 / 管理 | 当前无可用接口(赛事模块未启用) |
development:read / development:write | 开发者应用 · 读取 / 管理 | /v1/developer/**(你自己的 API Key / OAuth 应用 / 统计) |
Scope 与接口族的对应
| scope 域 | 接口族 | 例子 |
|---|---|---|
profile | /v0/auth/me、/v0/collections/me/**、/v0/activities/**、/v0/assets/uploads/** | GET /v0/auth/me |
account_security | /v0/auth/me/sessions**、/v0/auth/me/email、/v0/auth/me/phone、/v0/mfa/**、/v0/passkeys** | GET /v0/auth/me/sessions |
game | /v1/games/** | GET /v1/games/maimai/profiles |
social | /v0/social/**(谱面评论过渡例外) | 给谱面写评论 |
development | /v1/developer/** | GET /v1/developer/oauth-apps |
写操作需要对应的 :write;有 :write 就隐含 :read(只判 read 的接口也放行)。
每个接口的具体读写语义、请求体与响应示例见业务 API 总览。
account_security 的写操作要求浏览器会话
撤销会话、改密码、换绑邮箱/手机号这类操作不接受第三方令牌,即使你有
account_security:write:服务端会回 403 This endpoint requires a browser session。
这类操作只能由用户在 RankHub 自己的页面/App 上完成。
event:* 目前拿不到东西
赛事模块还没启用,event:read / event:write 现在申请了也没有接口可调。别申请。
game:read / game:write 的边界
这两个 scope 是游戏数据的全部权限,但它们只在 /v1/games/** 上生效,
而且不越过"仅第一方"的线:
| 你想要的 | 第三方应用怎么做 |
|---|---|
| 读授权用户的档案 / 成绩 / 最佳成绩 | GET /v1/games/**,需要 game:read |
| 建档案、推成绩 | POST /v1/games/{game}/profiles、POST .../{profileId}/scores,需要 game:write |
| 删档案 | 不能删,只能 POST .../deletion-requests 由用户确认 |
| 从游戏平台导入 / 同步 | 做不到 —— 需要用户凭据,只能由用户在 RankHub 内完成 |
| 读别人的公开档案 | 做不到 —— 社交面本期只服务第一方 |
game:write 覆盖 game:read,但不会把上面两个"做不到"变成能做到:
| 仅第一方的形状 | 第三方调用的结果 |
|---|---|
POST /v1/games/{game}/profiles/create(票据建档) | 403 game.firstPartyOnly |
POST /v1/games/{game}/profiles/import | 403 game.firstPartyOnly |
POST /v1/games/{game}/profiles/{profileId}/sync | 403 game.firstPartyOnly |
/v1/tasks/** | 403 game.firstPartyOnly |
/v1/games/maimai/relay/**(机台中转) | 403 game.firstPartyOnly |
POST .../deletion-requests/{requestId}/decision(用户表态) | 403 game.firstPartyOnly |
/v0/games/**、/v0/social/** | 403 game.firstPartyOnly(别名 / 评论为过渡例外) |
直接 DELETE 档案 | 403 game.deletionConsentRequired |
第一方(RankHub App 与浏览器会话)不受这张表的限制 —— 它本来就是"用户本人"在用。
补 scope 解决不了 403 game.firstPartyOnly
403 game.firstPartyOnly 是形状层面的拒绝,不是 scope 不足。
重新授权、加 game:write 都不会让它通过;请改用 /v1/games/** 上对第三方开放的形状。
过渡例外:别名与谱面评论
曲目别名与谱面评论的 v1 形状还没设计,所以这两族在过渡期继续留在 /v0,
第三方仍可调用(scope 仍是 game:read|write / social:read|write):
GET|POST /v0/games/{game}/song-aliases/**、.../proposals/**;/v0/social/chart-reviews/**、/v0/social/posts/**/comments。
除此之外的 /v0/games/**、/v0/social/** 对第三方关闭。过渡期结束后这两族会迁到 v1,
届时会另行公告 —— 新代码不要围绕 /v0 的形状做长期设计。
调用方式
业务接口只有一个入口:https://api.rankhub.cc。
# 游戏档案(需要 game:read)
curl -s https://api.rankhub.cc/v1/games/maimai/profiles \
-H "Authorization: Bearer $ACCESS_TOKEN"
# 开发者面(需要 development:read,返回 v1 信封)
curl -s https://api.rankhub.cc/v1/developer/oauth-apps \
-H "Authorization: Bearer $ACCESS_TOKEN"/v1/** 的响应带统一信封:
{
"code": "common.ok",
"message": "OK",
"data": [],
"requestId": "req_…"
}/v0/** 的响应是直接的对象/数组(没有信封)。
只使用 api.rankhub.cc
业务接口只认 https://api.rankhub.cc。换成其他地址/端口会得到 401 —— 那不是
"令牌没生效",而是这个入口不参与令牌鉴权。不要把这些地址写进配置。
401 / 403 / 428 / 429 怎么区分
| 你会看到 | 含义 | 处理 |
|---|---|---|
| 401(无 body 细节) | 没带令牌 / 令牌无效 / 过期 / 已被撤销 | 先刷新;刷新失败重新授权 |
403 + OIDC access token requires <scope> | 令牌有效,但缺这个 scope | 去开发者平台补 scope,用户需要重新授权(scope 变了旧令牌不会自动升级) |
403 + game.firstPartyOnly | 这个形状只给第一方 | 换用 /v1/games/** 上对第三方开放的形状,不要重试 |
403 + game.writeSuspended | 该应用的写权限被暂停(只拦写,读与登录不受影响) | 停下排查数据来源,联系平台恢复;换令牌也没用 |
403 + game.deletionConsentRequired | 想直接删档案 | 改用 POST .../deletion-requests,由用户确认 |
403 + This endpoint requires a browser session | 该操作只允许浏览器会话 | 换成让用户自己在 RankHub 里操作 |
428 + game.writeConfirmationRequired | 这次写入要用户先确认 | 把 data.confirmation.approveUrl 给用户,确认后用 confirmationToken 重放同一份 payload |
429 + common.rateLimited | 限流 | 按 Retry-After 退避 |
| 503 | 判定服务暂时不可用 | 可重试,不要清用户会话 |
刷新后 scope 会变吗
不会自动变。刷新返回的 scope 是当初授权时授予的那一组。要新增 scope,必须让用户重新走一次授权
(新请求里带上新的 scope 列表),旧的 refresh token 不会因此升级权限。
CORS
- 协议端点(
auth.rankhub.cc的/v1/oauth/**、/v1/oidc/**):会反射请求的Origin并带Access-Control-Allow-Credentials: true,所以浏览器可以直接调(换令牌、userinfo、撤销)。 - 开发者 API(
api.rankhub.cc/v1/developer/**):只允许白名单来源 (OPEN_ALLOWED_ORIGINS,生产含https://open.rankhub.cc)。第三方网页不要指望直连它 —— 那是给开发者平台自己用的。 - 业务接口(
/v0/**、/v1/games/**)不保证对任意来源开放;需要浏览器直连的场景请先联系我们。