RankHub

API 目前处于 Early Preview 阶段, 可能存在诸多不完善与错误之处,欢迎加群交流反馈:加入官方交流群(1073434398)

游戏档案

在 /v1/games/** 上读档案、推档案建号、看只读标记,以及第三方唯一能做的删除动作——提交申请。

一个 RankHub 用户可以绑定多个游戏的档案;每个档案属于一个用户 + 一个游戏档案 ID。 第三方应用读写档案的前提是拿到该用户的 access_token,并且令牌具备 game:read (或 game:write,写覆盖读)。

档案接口在 /v1/games/** 上

旧文档里的 /v0/games/** 本期只服务 RankHub 自有客户端:第三方令牌打它会得到 403 game.firstPartyOnly。本页所有路径都是 /v1 形状(envelope + camelCase + 游标分页)。

支持的游戏

路径里的 {game} 是小写游戏键,不是控制器名(maimai 的路径是 maimai,不是 maimai-dx)。

游戏键读推档案建号推成绩删除申请
maimai支持支持支持支持
arcaea支持支持支持支持
phigros支持支持支持支持
milthm支持支持支持支持
notanote支持支持支持支持
musedash支持支持支持支持
rizline支持支持支持支持
osu支持支持支持支持
chunithm支持支持支持支持
orzmic支持支持支持支持

四列对 10 个游戏全部为真:读、建档、推成绩、申请删除都可用。

  • 建档 = 填身份字段:只写 player 与可选 scores,没有别的材料;
  • 推成绩 = 填评分值:单曲评分由你在 scores[].rating 里给;maimai / arcaea 例外, 这两族服务端自己算分。

这张表只是今天的状态,权威是 GET /v1/games 的能力矩阵(见业务 API 总览)。

每个游戏的字段在哪

这张表只回答"能不能访问"。每个游戏的身份字段、统一字段、native 原生字段、最佳成绩桶见 游戏格式;也可以直接读 GET /v1/games/{game}/format(机读版本)。

读取档案

# 能力矩阵:先看这个游戏支不支持写
curl -sS https://api.rankhub.cc/v1/games \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# 列表(游标分页,limit 最大 100)
curl -sS "https://api.rankhub.cc/v1/games/maimai/profiles?limit=50" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# 单个档案
curl -sS https://api.rankhub.cc/v1/games/maimai/profiles/$PROFILE_ID \
  -H "Authorization: Bearer $ACCESS_TOKEN"

列表只返回该用户在当前游戏下未解绑的档案,形状是 { items, nextCursor, hasMore }。未绑定 / 不属于当前用户 / 已解绑的档案一律 404 common.notFound (不区分"不存在"与"不是你的",避免被拿来探测)。

档案对象(各游戏一致的外壳 + 游戏原生字段进 player.native):

{
  "profileId": "8d1f4a0e-6c2b-4a1e-9d3f-2b7c5e9a0f11",
  "game": "maimai",
  "player": {
    "name": "Player",
    "rating": 15234,
    "native": { "userId": 10000001, "friendCode": "1234567890123", "courseRank": 0, "classRank": 0, "star": 12 }
  },
  "isPublic": true,
  "origin": "app",
  "createdByClientId": "cli_7Q2…",
  "syncCapable": false,
  "lastSyncedAt": null,
  "createdAt": "2026-10-21T18:40:00Z",
  "updatedAt": "2026-10-21T18:40:00Z"
}
字段说明
profileId档案 ID(后续所有 /profiles/{profileId}/** 都用它)
game游戏键
player.name / player.rating归一后的显示名与该游戏"总分";没有总分概念时为 null
player.native游戏原生玩家对象(未归一,只用于展示/调试)
origin / createdByClientId建档来源(user / app)与第三方应用公开 id
syncCapable / lastSyncedAt只读标记:自动更新开关、最近一次更新时间
isPublic / createdAt / updatedAt公开性、创建与更新时间(RFC3339)

完整字段语义见游戏数据格式总览; 每个游戏 player.native 里有什么,见各游戏格式页或 GET /v1/games/{game}/format。

null 不是 0

某游戏没有某个概念时字段是 null(例如没有总分概念的游戏 player.rating 为 null)。 请按 null 渲染占位,不要把它当 0 显示。

档案上的只读标记

字段说明
origin / createdByClientId档案由谁创建:user(用户在 RankHub 内建)/ app(第三方建)
syncCapable布尔,只读。不是写权限:false 的档案在 10 个游戏里都能推成绩
lastSyncedAt时间戳或 null,只读

推档案建号

要抄请求体?看写入格式页

身份字段逐游戏不同;player 超集、scores[] 共享字段与可直接抄的 JSON 见 写入格式(建档 / 推成绩),单游戏细节见 各游戏格式页的「写入(第三方)」。

10 个游戏都支持(能力矩阵 pushProfile 全为真)。建档只有一条口径: 调用方自己拿到玩家在该游戏里的身份与资料,直接把字段填进来 —— 服务端只落库,不做别的。

身份字段

player 是超集:字段按游戏生效,不适用字段被忽略;name 恒必填(展示名)。

身份字段逐游戏不同(maimai 的 friendCode / userId、arcaea 的 arcaeaId、 osu 的 osuUserId、chunithm 的街机好友码…)。完整表在 各游戏格式页的「身份字段」,或 GET /v1/games/{game}/format 的 profile.identityFields(含 required 与 example)。 缺失按该游戏既有语义返回 400。

player.rating 只是"总分提示":服务端会用同一请求里的 scores 重算(没有成绩则 0)。 返回的 player.rating 一律以存量为准,不要把你传进去的数字当成已生效。

例子

# maimai:身份是机台好友码;评分由服务端按成绩算
curl -sS -X POST https://api.rankhub.cc/v1/games/maimai/profiles \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "player": { "name": "Player", "friendCode": "1234567890123" },
    "scores": [
      { "songId": 1234, "type": "dx", "levelIndex": 3, "achievements": 100.5125 }
    ],
    "idempotencyKey": "b4c0f0d6-8f6f-4d6a-a0d3-3c6f0c4a11"
  }'
# phigros:身份是 objectId;单曲评分(rks)由你给
curl -sS -X POST https://api.rankhub.cc/v1/games/phigros/profiles \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{
    "player": { "name": "Player", "objectId": "a1b2c3d4-…" },
    "scores": [
      { "songId": "…", "difficulty": "IN", "score": 1000000, "accuracy": 100, "rating": 15.2 }
    ]
  }'

建档之后的这张档案

  • 成功是 201 + 档案对象(data 就是上面那张档案);
  • 建号会写 origin: "app" 与 createdByClientId;syncCapable 为 false (只读标记,见上文)。此后这张档案只能靠你推成绩更新;
  • scores 可选,对 10 个游戏都生效(与推成绩走同一套"只升不降"合并,见 成绩读取与写入),受单批条数上限约束(超限 400 common.invalidArgument);
  • idempotencyKey 的作用域是「当前用户 + 当前应用」:重复提交返回同一张档案; 不传就没有幂等保护;
  • 重复建号返回 409:同一用户在同一游戏已有未解绑档案、或该身份已被占用时, 按该游戏既有唯一索引语义返回 409 common.conflict(个别游戏沿用既有 400; 文案也沿用既有服务,如 … is already linked)。这不是"重试就能过"—— 正确做法是更新已有档案(推成绩),不要反复新建。

修改档案(PATCH)

PATCH /v1/games/{game}/profiles/{profileId}

可改展示与可见性字段,至少给一个:

Prop

Type

  • 成功后返回更新后的档案对象(与 GET .../profiles/{profileId} 同形);
  • 身份字段不可改:native 里出现身份字段(userId / friendCode / arcaeaId / osuUserId…)且与现有值不同 ⇒ 400 common.invalidArgument;
  • 需要 game:write;不是你的档案 / 游戏不存在 ⇒ 404;
  • 改 isPublic 只影响别人能否读到,不影响你自己读。

删除档案:第三方只能申请

第三方应用不能直接删除档案。删除是"用户对自己数据的处置",必须用户本人在 RankHub 里点头。

直接对档案发 DELETE(不管 /v1 还是 /v0 的档案路径)会得到 403 game.deletionConsentRequired。

提交申请

curl -sS -X POST https://api.rankhub.cc/v1/games/maimai/profiles/$PROFILE_ID/deletion-requests \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{ "reason": "用户在我们这里注销了" }'

reason 可选(≤ 500 字)。成功是 202 + code: "common.accepted":

{
  "code": "common.accepted",
  "data": {
    "requestId": "pdr_9f2c1e4a77b34d0e",
    "status": "pending",
    "approveUrl": "rankhub://profiles/deletion?token=pdr_tok_…",
    "expiresAt": "2026-10-25T18:40:00Z"
  }
}
  • 平台会给档案 owner 推一条通知,approveUrl 是那条通知里的 deep link(带一次性令牌);
  • 申请默认 72 小时过期,过期即作废(status: "expired"),需要重新提交;
  • 幂等的是"申请",不是"令牌":同一档案 + 同一应用已有 pending 申请时,再次 POST 仍返回同一条 202,但 approveUrl 里的令牌会轮换(旧链接立即失效)。 这样重复申请不会堆出第二张待办,同时旧链接也不会长期可用。

查询状态

curl -sS https://api.rankhub.cc/v1/games/maimai/profiles/$PROFILE_ID/deletion-requests/$REQUEST_ID \
  -H "Authorization: Bearer $ACCESS_TOKEN"
{
  "code": "common.ok",
  "data": {
    "requestId": "pdr_9f2c1e4a77b34d0e",
    "status": "pending",
    "requestedAt": "2026-10-22T18:40:00Z",
    "expiresAt": "2026-10-25T18:40:00Z",
    "decidedAt": null
  }
}
status含义
pending等用户处理(可继续轮询;别把轮询做得比一分钟还密)
approved用户同意,档案已软解绑(不再出现在列表里,历史成绩仍保留)
denied用户拒绝,档案不动
expired超时未处理,作废;需要就重新提交

查询只能看到你自己发起的那一条:不存在的 requestId、别人的申请、以及别的应用对同一档案发的申请 一律 404 common.notFound(不区分,避免被拿来探测)。 没有集合查询(一次列出该档案所有申请):请自己保存 requestId。

用户侧的表态

用户在 RankHub App 里批准/拒绝(.../decision)是第一方专用:第三方调用返回 403 game.firstPartyOnly,不随灰度开关变化。你只需提交申请并轮询状态。

公开档案(社交面)

用户把档案设为公开后,其他登录用户可以在 RankHub 社交面读到同一份数据 (GET /v0/social/users/{uid}/game-profiles/{game} 等)。

社交面本期不开放给第三方

/v0/social/** 只服务第一方(谱面评论是唯一的过渡例外)。第三方令牌打这些路径会拿到 403 game.firstPartyOnly —— 你只能读授权用户本人的档案,读不到其他人的公开档案。 需要"看别人档案"的产品形态,本轮请不要基于社交面设计。

写操作会检查账号状态

新建 / 推成绩 / 申请删除都要求账号没有处于注销流程(accountDeletionRequestedAt 或 accountDeletedAt 非空 ⇒ 403)。这类失败不要重试,也不要循环刷 —— 注销中的账号,用户已经明确表示不再使用。

下一步

本页内容

官方交流群二维码官方交流群问题反馈 · 版本资讯 · 成绩交流群号 1073434398