RankHub

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

写入格式(建档 / 推成绩)

第三方写入到底要传什么:POST profiles 与 POST scores 的请求体结构、共享字段表、每个游戏的必填字段与单位、成功响应、校验与 400/422/428/403。

本页是写入侧的统一契约。读侧(返回什么)见游戏数据格式总览; 每个游戏的具体字段表与可直接抄的 JSON见各游戏格式页的「写入(第三方)」一节。

两个写入端点:

动作端点说明
建档POST /v1/games/{game}/profiles填身份字段建一张档案;可选地同时带一批成绩
推成绩POST /v1/games/{game}/profiles/{profileId}/scores往已有档案批量写成绩

两者都是 application/json、Bearer 鉴权、v1 envelope;写操作需要 game:write(覆盖 game:read)。

交互式 schema 在 API Reference

每个写入端点的完整 JSON Schema(含所有可选字段与校验标记)见 API Reference 与 建档端点,可直接在页面上试调。

写入代表用户修改其 RankHub 档案

只提交用户在你这一侧真实产生的成绩;不要伪造、不要批量灌库。所有写入进成绩历史, 可能触发评分重算与通知;批量或连续异常会暂停该应用的写权限。

不确定该传 RankHub 字段还是游戏原生数据?先看怎么传数据(速查)。

1. 建档请求体

{
  "player": { "name": "Player", "friendCode": "1234567890123" },
  "idempotencyKey": "b4c0f0d6-8f6f-4d6a-a0d3-3c6f0c4a11"
}
顶层字段类型必填说明
playerobject支持玩家字段,见下
scoresarray可选建档时一并写入的成绩(与推成绩的 scores[] 同结构),受单批上限约束。⚠️ chunithm / orzmic 会静默忽略,这两个游戏建档后需另行 POST .../scores
idempotencyKeystring ≤128可选幂等键,作用域 = 当前用户 + 当前应用;重复提交返回同一张档案

1.1 player 是超集:字段按游戏生效

字段类型必填说明
namestring ≤64支持展示名(10 个游戏都要)
ratingnumber可选只是提示:服务端会按 scores 重算(没有成绩则 0),返回的 player.rating 以服务端存量为准
身份字段见下表支持每个游戏不同,缺失/不合法 ⇒ 400
其它原生字段见各游戏页可选不适用的字段被忽略(不会报错)

身份字段(10 个游戏):

游戏身份字段备注
maimaifriendCode 或 userId二者至少一个;另有 courseRank/classRank/star 可选
arcaeaarcaeaId + character❗ character 缺失 ⇒ 400;arcaeaId 缺失不报错(落 0,身份就错了)
phigrosobjectId可选 shortId
milthmmilthmUserId + regionregion 取 cn/global,缺省 cn
notanotenotanoteUid数字串按字符串传,避免前导零/大整数失真
musedashmusedashUid1~32 位 [0-9A-Za-z_-](云存档解析器另要求 32 位 hex)
rizlineplayerId
osuosuUserIdbancho 数字 id,按字符串传
chunithmfriendCode街机好友码,不是 LXNS id
orzmicexternalId本地游戏无官方账号体系,值由你与用户约定

建档只写 player(身份字段)与可选 scores;建档后的档案 syncCapable: false, 之后靠你推成绩更新。

2. 推成绩请求体

{
  "scores": [
    {
      "songId": "1234",
      "difficulty": "IN",
      "score": 1000000,
      "accuracy": 100,
      "rating": 15.2,
      "playTime": "2026-10-20T12:00:00Z"
    }
  ],
  "idempotencyKey": "0b6f0f1e-2f3a-4c5d-8e9f-1a2b3c4d5e6f",
  "confirmationToken": "(仅 428 重放时带)"
}
顶层字段类型必填说明
scoresarray支持1~单批上限条(默认 500,硬上限 2000),见 §5
idempotencyKeystring ≤128可选同批重放去重;不传也不会重复计分("只升不降"本身保证重放无害)
confirmationTokenstring ≤256仅 428 后用户确认后原样重放时带,见 §6

2.1 scores[] 共享字段

字段是超集:按游戏生效,不适用的字段被忽略。每个游戏"哪几个必须给"见各游戏页的速查表。

字段类型含义谁用
songIdstring | number曲目 id(osu 是 beatmap id);arcaea 守卫开启时必须是字符串全部(必填)
typestring谱面类型 standard/dx/utage,缺省 dxmaimai
difficultystring | number难度标识(各游戏自己的集合;osu 是模式;maimai 不用,用 levelIndex)除 maimai
levelIndexint难度下标 0~4maimai
levelstring难度标签(展示用)可选
achievementsnumber达成率(maimai 0~100.5/101;多数游戏作为 accuracy 的兜底;osu 的 achievements ≤1 时按 0~1 乘 100)maimai;其余可作 accuracy 兜底
scorenumber原始得分arcaea 0~10,010,000(大 Pure 可略超 1,000 万);phigros/milthm/musedash/rizline 0~1,000,000;osu total_score;本地游戏比较值
ratingnumber单曲评分:phigros rks / milthm reality / notanote nrk / musedash RL / rizline rks / osu pp6 个游戏;maimai/arcaea 忽略(服务端算)
accuracynumber准确率 0~100 百分数(osu 不做 0~1 换算,传 0.98 会被当成 0.98%)phigros/milthm/notanote/musedash/rizline/osu
constantnumber谱面定数 0~100phigros/milthm/notanote/rizline/musedash
comboint连击数musedash/notanote/rizline/osu
clearedbool是否通关(缺省 false;osu 的 passed 取 cleared !== false)本地/rizline/notanote/musedash/osu
fullCombobool是否全连rizline/musedash/phigros(也认 fc)
fc / fsstring全连 / 全同步档位maimai;phigros/rizline 用 fc 的存在性表示全连
dxScore / dxStarintDX 分数 / 星级(缺省 0)maimai
evaluateint结算评级档(0=D 1=C …)musedash
rankstring评级文本(SH/XH/S…);osu 缺省 Fosu / phigros / maimai 等
modestring模式 osu/taiko/fruits/mania(❗ 不是 catch)osu
upstreamIdstring该行在上游榜单里的成绩 idosu
positionint榜内位次osu
playTimestringRFC3339;缺省用服务端时间目前仅 osu 读取;其它游戏传了不生效
chartIdstring谱面 id(缺省用 songId)milthm
chartKey / color / nativestring谱面键 / 轨道颜色 / 来源(common/fanmade,其它值归 common)notanote
trackIdstring谱面 id(缺省用 songId)rizline
uidstringsongUid(缺省用 songId)musedash
artiststring曲师(展示用)phigros/milthm/rizline
realityFormulastringv3 = Reality V3 公式,其它按 v2milthm
maxHit / rizHitint最大击打 / Riz 击打rizline
bucketstring落到哪个桶(chunithm best30/selection10/new20,orzmic best30;缺省 best30)本地游戏
nativeobject原始成绩对象(原样存、原样用)chunithm / orzmic
scoreBelowMax / shinyPerfectCount / perfectCount / nearCount / missCount / health / clearType / bestClearTypeintArcaea 判定计数与通关类型(缺省 0)arcaea

缺省值说明:计数类字段"缺省 0"是有意的诚实默认(未知就是 0),不是服务端帮你补全。 需要准确分数时请把真实值给全。

2.2 原生数据(native)

手里已经是游戏原生对象时,整块放进 native 即可(建档 player.native、成绩 scores[].native), 服务端按该游戏唯一一套换算规则归一;native 优先于顶层同名字段,不合法 ⇒ 400 common.invalidArgument。

  • 哪些游戏支持 native 建档 / native 成绩、每个游戏必给什么:见怎么传数据(速查);
  • 每游戏的 native 字段表:见对应游戏格式页;
  • 机器可读版本:GET /v1/games/{game}/format 的 profile.nativeFields / score.nativeFields。

3. 响应

3.1 建档 → 一张档案

data 与 GET /v1/games/{game}/profiles/{profileId} 完全相同(见总览 §2)。 第三方建的档案会带 origin: "app"、createdByClientId、syncCapable: false。

3.2 推成绩 → 写入结果

{
  "code": "common.ok",
  "data": {
    "rating": { "before": { "total": 15100 }, "after": { "total": 15234 }, "delta": 134 },
    "applied": [ { "songId": "1234", "improved": true, "currentAchievements": 100.5125 } ],
    "ignored": [ { "songId": "5678", "reason": "notImproved" } ]
  }
}

rating.before / after 的键集合随游戏:maimai 是 total/latest/archive, arcaea 是 potential/b30Avg/b50Avg(没有 total,delta 是 potential 差), 其余多为 total。applied[].currentAchievements 对 arcaea 装的是分数 score、 本地游戏(chunithm/orzmic)恒为 null;ignored[].reason 本地游戏是 not-improved(带连字符)。

字段说明
rating.before / after本次写入前后该游戏的"总力"(键集合随游戏,至少 total)
rating.deltaafter.total - before.total;无 total 概念时为 null
applied[]处理过的成绩:songId / improved(是否抬高)/ currentAchievements(写入后达成率,无此概念为 null)
ignored[]被忽略的提交:reason 如 notImproved(没变好)、unknownChart(谱面不存在)

一条成绩可能同时出现在 applied(improved: false)与 ignored 里,便于两条流都渲染。

4. 常见 400 文案

场景返回
该游戏不支持这种写形状400 common.invalidArgument,description = <game> does not support …
phigros difficulty 缺失/非法400,Phigros score difficulty is required / … is invalid
身份字段缺失(如 musedashUid)400,文案随游戏(musedashUid/notanoteUid … is required for … profile creation、objectId is required、chunithm profile creation requires player.friendCode);❗ arcaea 缺 arcaeaId 不报错(落 0)
maimai 两个身份都没给400,Either user_id or friend_code is required
arcaea 缺 character400,character is required
单批超限400 common.invalidArgument
数值越界 / 谱面不存在 / 组合矛盾400(非法值)或 422 common.unprocessable(语义矛盾)

通用上界受写入守卫开关影响

上面的"越界 400/422"分两类:

  1. 各游戏服务自身的校验(如 phigros 的 difficulty、rizline 的 score、身份字段)——总是生效;
  2. 通用/谱面级上界(数值范围、谱面存在性、组合自洽)挂在写入守卫上, writeGuardMode 默认 off 时不拦。不要把"没报错"当成"数值一定合法"。

5. 批量与限流

  • scores 不能为空(ArrayNotEmpty);单批上限按应用档位配置,默认 500,DTO 硬上限 2000;
  • 超限 → 400 common.invalidArgument,不会部分写入;
  • 批量写入限流按 tier(见业务 API 总览)。

6. 428 确认与 403 停写

写入守卫会对可疑写入返回 428 game.writeConfirmationRequired(需要用户确认后原样重放)或 403 game.writeSuspended(应用写权限被暂停)。两种语义、payload 与重放规则见 成绩读取与写入 · 428 / 403。

7. 幂等

端点幂等键语义
建档idempotencyKey作用域 = 用户 + 应用;重复提交返回同一张档案(不会重复建号)
推成绩idempotencyKey只用于重试去重;不传也安全("只升不降"保证重放不重复计分)

8. 相关

本页内容

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