由
scripts/gen-api-docs.mjs自动生成,请勿手改——改该文件后重新运行node scripts/gen-api-docs.mjs。
https://api.xhsr.org.cn(Vercel Node 24,区域 hkg1)Authorization: Bearer <jwt>。API 已独立部署,跨域场景必须用 Bearer(Cookie 仅同源有效)。{ "error": "文案", "code": "机器码" }。生产环境 error 为固定文案,不泄露内部信息;前端请按 code 分支。maxDuration 30 秒。三步走:登录拿 token → 带 token 调接口 → 过期用 refresh 换新。下面每条命令都可直接复制运行。
# 1) 登录(公开接口,无需 token)
curl -X POST 'https://api.xhsr.org.cn/api/v2/auth?action=login' \
-H 'Content-Type: application/json' \
-d '{"login":"demo","password":"********"}'
# → { "token": "eyJ...", "refreshToken": "...", "user": { ... } }
# 2) 带 token 调需要登录的接口
TOKEN='第 1 步拿到的 token'
curl 'https://api.xhsr.org.cn/api/v2/user?action=me' -H "Authorization: Bearer $TOKEN"
# 3) token 过期(401)时用 refreshToken 换新
curl -X POST 'https://api.xhsr.org.cn/api/v2/auth?action=refresh' \
-H 'Content-Type: application/json' \
-d '{"refreshToken":"<refreshToken>"}'
错误响应示例(所有非 2xx 统一此结构,前端按 code 分支):
{ "error": "Internal Server Error", "code": "internal_error" }
| 标记 | 含义 |
|---|---|
public | 公开 |
user | 需登录 |
self | 需登录(仅本人) |
privileged | 需 reviewer / moderator |
moderator | 需 admin / moderator |
admin | 需 admin |
/api/v2/auth/api/v2/user/api/v2/charts/api/v2/scores/api/v2/records/api/v2/resource_packs/api/v2/studio/api/v2/tickets/api/v2/content/api/v2/upload/api/v2/convert-zip/api/v2/admin共 81 个接口,分布在 12 个端点。
/api/v2/auth
POST /api/v2/auth?action=login账号密码登录 · 鉴权:公开
| 参数 | 说明 |
|---|---|
login | 邮箱或用户名 |
password | 密码 |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/auth?action=login' \
-H 'Content-Type: application/json' \
-d '{"login":"demo","password":"********"}'
成功响应:{ token, refreshToken, user }
响应示例
{
"token": "<JWT,7 天有效>",
"refreshToken": "<refreshToken>",
"user": {
"id": 1,
"username": "星鸿",
"role": "user",
"status": "active"
}
}
账号未激活时返回 403 email_unverified 并补发验证码;被封禁返回 403。
POST /api/v2/auth?action=register注册新账号 · 鉴权:公开
| 参数 | 说明 |
|---|---|
email | 邮箱 |
username | 用户名 |
password | 密码 |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/auth?action=register' \
-H 'Content-Type: application/json' \
-d '{"email":"user@example.com","username":"demo","password":"********"}'
成功响应:{ message, user }
创建后状态为 pending,需经 verify/activate 激活。
POST /api/v2/auth?action=verify发送邮箱验证码 · 鉴权:公开
| 参数 | 说明 |
|---|---|
email | 邮箱 |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/auth?action=verify' \
-H 'Content-Type: application/json' \
-d '{"email":"user@example.com"}'
成功响应:{ message }
同一邮箱 30 秒一次(429)。非生产环境响应会附带 code 方便本地调试。
POST /api/v2/auth?action=activate校验验证码并激活账号 · 鉴权:公开
| 参数 | 说明 |
|---|---|
email | 邮箱 |
code | 验证码 |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/auth?action=activate' \
-H 'Content-Type: application/json' \
-d '{"email":"user@example.com","code":"123456"}'
成功响应:{ message }
同一邮箱 10 分钟内最多尝试 10 次,超出返回 429。
POST /api/v2/auth?action=reset-password重置密码 · 鉴权:公开
| 参数 | 说明 |
|---|---|
email | 邮箱 |
code | 验证码(第二步) |
newPassword | 新密码(第二步) |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/auth?action=reset-password' \
-H 'Content-Type: application/json' \
-d '{"email":"user@example.com","code":"123456","newPassword":"********"}'
成功响应:{ message }
两阶段:只传 email = 发送验证码;传 email+code+newPassword = 完成重置。邮箱不存在时也返回 200,避免账号枚举。
POST /api/v2/auth?action=refresh用 refreshToken 换新 token · 鉴权:公开
| 参数 | 说明 |
|---|---|
refreshToken | 刷新令牌(body 或 cookie) |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/auth?action=refresh' \
-H 'Content-Type: application/json' \
-d '{"refreshToken":"<refreshToken>"}'
成功响应:{ token, refreshToken }
原子消费:旧 refreshToken 用后即失效,并发重放会拿到 401。
POST /api/v2/auth?action=logout登出并撤销 refreshToken · 鉴权:需登录
| 参数 | 说明 |
|---|---|
refreshToken | 可选 |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/auth?action=logout' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"refreshToken":"<refreshToken>"}'
成功响应:{ message }
POST /api/v2/auth?action=oauth-tokenOAuth 授权码换 token(PKCE S256) · 鉴权:公开
| 参数 | 说明 |
|---|---|
grant_type | authorization_code |
code | 授权码 |
redirect_uri | 回调地址 |
code_verifier | PKCE 校验串 |
client_id | 客户端 ID |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/auth?action=oauth-token' \
-H 'Content-Type: application/json' \
-d '{"grant_type":"authorization_code","code":"123456","redirect_uri":"https://app.example.com/callback","code_verifier":"<code_verifier>","client_id":"<client_id>"}'
成功响应:{ access_token, token_type, expires_in, user }
POST /api/v2/auth?action=github-callbackGitHub OAuth 回调换 token / 绑定 · 鉴权:公开
| 参数 | 说明 |
|---|---|
code | GitHub 授权码 |
state | state |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/auth?action=github-callback' \
-H 'Content-Type: application/json' \
-d '{"code":"123456","state":"<state>"}'
成功响应:{ token, refreshToken, user, created }
携带本站 JWT 时为「绑定」,未携带时为「登录/自动注册」。
GET /api/v2/auth?action=oauth-authorizeOAuth 授权端点(PKCE S256) · 鉴权:需登录
| 参数 | 说明 |
|---|---|
client_id | 客户端 ID |
redirect_uri | 回调地址 |
response_type | code |
code_challenge | PKCE 挑战 |
code_challenge_method | S256 |
state | 状态串 |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/auth?action=oauth-authorize&client_id=<client_id>&redirect_uri=https://app.example.com/callback&response_type=code&code_challenge=<code_challenge>&code_challenge_method=S256&state=<state>' \
-H "Authorization: Bearer $TOKEN"
成功响应:302 重定向到 redirect_uri?code=...
GET /api/v2/auth?action=milkloud-login发起 Milkloud 登录 · 鉴权:公开
请求示例
curl 'https://api.xhsr.org.cn/api/v2/auth?action=milkloud-login'
成功响应:302 重定向到 Milkloud 授权页
GET /api/v2/auth?action=milkloud-callbackMilkloud OAuth 回调 · 鉴权:公开
| 参数 | 说明 |
|---|---|
code | 授权码 |
state | state |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/auth?action=milkloud-callback&code=123456&state=<state>'
成功响应:{ token, refreshToken, user }
GET /api/v2/auth?action=github-login发起 GitHub 登录 · 鉴权:公开
请求示例
curl 'https://api.xhsr.org.cn/api/v2/auth?action=github-login'
成功响应:302 重定向到 GitHub 授权页
未配置 GITHUB_CLIENT_ID/SECRET 时返回 500 github_not_configured。
/api/v2/user
GET /api/v2/user?action=me获取当前登录用户 · 鉴权:需登录
请求示例
curl 'https://api.xhsr.org.cn/api/v2/user?action=me' \
-H "Authorization: Bearer $TOKEN"
成功响应:{ user }
GET /api/v2/user?action=list用户列表(分页/搜索/排序) · 鉴权:公开
| 参数 | 说明 |
|---|---|
page | 页码 |
limit | 每页条数(≤200) |
sort | created_at |
order | asc |
q | 搜索关键字 |
role | 角色筛选(仅管理员) |
status | 状态筛选(仅管理员) |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/user?action=list&page=1&limit=20&sort=newest&order=desc&q=关键词&role=user&status=active'
成功响应:{ data, page, limit, total }
公开请求绝不返回 email/role/status,防止全站邮箱枚举。
GET /api/v2/user?action=detail用户详情(含其谱面) · 鉴权:公开
| 参数 | 说明 |
|---|---|
id | 用户 ID |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/user?action=detail&id=1'
成功响应:{ data: { ...user, charts[] } }
公开视角只展示已上架谱面。
GET /api/v2/user?action=get用户简要信息 · 鉴权:公开
| 参数 | 说明 |
|---|---|
id | 用户 ID |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/user?action=get&id=1'
成功响应:{ data }
响应示例
{
"data": {
"id": 1,
"username": "星鸿",
"avatar_url": "https://file.xhsr.org.cn/avatar-1-….png",
"bio": "你好喵~",
"created_at": "2025-12-08T21:38:48.004Z",
"followers_count": 4,
"following_count": 0,
"is_following": false
}
}
GET /api/v2/user?action=followers粉丝列表 · 鉴权:公开
| 参数 | 说明 |
|---|---|
id | 用户 ID |
page | 页码 |
limit | 每页条数 |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/user?action=followers&id=1&page=1&limit=20'
成功响应:{ data, page, limit, total }
GET /api/v2/user?action=following关注列表 · 鉴权:公开
| 参数 | 说明 |
|---|---|
id | 用户 ID |
page | 页码 |
limit | 每页条数 |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/user?action=following&id=1&page=1&limit=20'
成功响应:{ data, page, limit, total }
POST /api/v2/user?action=update更新资料 · 鉴权:需登录(仅本人)
| 参数 | 说明 |
|---|---|
username | 用户名 |
bio | 简介 |
avatar_url | 头像 |
id | 目标用户 ID(仅管理员) |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/user?action=update' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"username":"demo","bio":"<bio>","avatar_url":"<avatar_url>","id":"1"}'
成功响应:{ message, user, usernameChange }
用户名每 365 天只能改一次(管理员可绕过);github_id 只能置空,绑定必须走 GitHub 授权。
POST /api/v2/user?action=change-password修改密码 · 鉴权:需登录(仅本人)
| 参数 | 说明 |
|---|---|
oldPassword | 旧密码 |
newPassword | 新密码 |
id | 目标用户 ID(仅管理员) |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/user?action=change-password' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"oldPassword":"********","newPassword":"********","id":"1"}'
成功响应:{ message }
POST /api/v2/user?action=follow关注 / 取关 · 鉴权:需登录
| 参数 | 说明 |
|---|---|
userId | 目标用户 ID |
action | follow |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/user?action=follow' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"userId":"1"}'
成功响应:{ message, followers_count, following_count, is_following }
POST /api/v2/user?action=send-email-code向新邮箱发送换绑验证码 · 鉴权:需登录
| 参数 | 说明 |
|---|---|
email | 新邮箱 |
password | 当前密码(身份确认) |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/user?action=send-email-code' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"email":"user@example.com","password":"********"}'
成功响应:{ message, email }
必须校验当前密码,防止会话被盗后直接改绑邮箱。
POST /api/v2/user?action=change-email校验验证码并完成换绑 · 鉴权:需登录
| 参数 | 说明 |
|---|---|
email | 新邮箱 |
code | 验证码 |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/user?action=change-email' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"email":"user@example.com","code":"123456"}'
成功响应:{ message, user }
/api/v2/charts
GET /api/v2/charts?action=list谱面列表(分页/搜索/筛选) · 鉴权:公开
| 参数 | 说明 |
|---|---|
page | 页码 |
limit | 每页条数(≤200) |
sort | oldest |
q | 搜索(标题/艺术家/谱师/上传者) |
uploader_id | 按上传者过滤 |
status | approved |
published | 1 |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/charts?action=list&page=1&limit=20&sort=newest&q=关键词&uploader_id=1&status=active&published=1'
成功响应:{ data: Chart[] }
响应示例
{
"data": [
{
"id": 8,
"title": "Endtime",
"artist": "Cres.",
"charter": "Heronmony",
"difficulty": "Cloudburst",
"level": "11+",
"bpm": "180",
"cover_url": "https://file.xhsr.org.cn/charts/1771149808591-image.png",
"chart_url": "https://file.xhsr.org.cn/charts/1771149806393-chart.json",
"audio_url": "https://file.xhsr.org.cn/charts/1771149807796-audio.mp3",
"meta_url": "https://file.xhsr.org.cn/charts/recovered/8/meta.json",
"status": "approved",
"uploader_id": 68,
"like_count": 0,
"created_at": "2026-02-15T02:03:30.005Z"
}
]
}
普通用户默认只看已上架 + 自己的;hidden 谱面一律不可见。
GET /api/v2/charts?action=detail谱面详情 · 鉴权:公开
| 参数 | 说明 |
|---|---|
id | 谱面 ID |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/charts?action=detail&id=1'
成功响应:{ data: Chart }
响应示例
{
"data": {
"id": 8,
"title": "Endtime",
"artist": "Cres.",
"charter": "Heronmony",
"difficulty": "Cloudburst",
"level": "11+",
"bpm": "180",
"offset_val": "-40.000",
"cover_url": "https://file.xhsr.org.cn/charts/1771149808591-image.png",
"chart_url": "https://file.xhsr.org.cn/charts/1771149806393-chart.json",
"audio_url": "https://file.xhsr.org.cn/charts/1771149807796-audio.mp3",
"meta_url": "https://file.xhsr.org.cn/charts/recovered/8/meta.json",
"status": "approved",
"uploader_id": 68,
"description": "本人第一张Milthm自制谱",
"play_count": 0,
"like_count": 0,
"created_at": "2026-02-15T02:03:30.005Z"
}
}
未上架谱面仅上传者本人与特权角色可见;hidden 仅特权角色可见(对外 404)。
GET /api/v2/charts?action=history谱面流转历史(投票/上架申请/审计) · 鉴权:公开
| 参数 | 说明 |
|---|---|
id | 谱面 ID |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/charts?action=history&id=1'
成功响应:{ data: Event[] }
POST /api/v2/charts?action=like点赞 / 取消点赞 · 鉴权:需登录
| 参数 | 说明 |
|---|---|
chartId | 谱面 ID |
action | like |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/charts?action=like' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"chartId":"1"}'
成功响应:{ message, is_liked, like_count }
POST /api/v2/charts?action=moderate管理谱面状态 · 鉴权:需 reviewer / moderator
| 参数 | 说明 |
|---|---|
chartId | 谱面 ID |
action | hide |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/charts?action=moderate' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"chartId":"1"}'
成功响应:{ message, status }
re-review 会清空该谱面历史投票。
POST /api/v2/charts?action=delete软删除谱面(置为 hidden) · 鉴权:需登录(仅本人)
| 参数 | 说明 |
|---|---|
id | 谱面 ID |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/charts?action=delete' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"id":"1"}'
成功响应:{ message, status }
仅隐藏,投票/成绩/点赞/评论等关联数据全部保留,可恢复。
DELETE /api/v2/charts?action=delete软删除谱面(同 POST action=delete) · 鉴权:需登录(仅本人)
| 参数 | 说明 |
|---|---|
id | 谱面 ID |
请求示例
curl -X DELETE 'https://api.xhsr.org.cn/api/v2/charts?action=delete' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"id":"1"}'
成功响应:{ message, status }
/api/v2/scores
GET /api/v2/scores?action=recent最近成绩 · 鉴权:公开
请求示例
curl 'https://api.xhsr.org.cn/api/v2/scores?action=recent'
成功响应:{ data }
GET /api/v2/scores?action=leaderboard谱面排行榜 · 鉴权:公开
| 参数 | 说明 |
|---|---|
chartId | 谱面 ID |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/scores?action=leaderboard&chartId=1'
成功响应:{ data }
GET /api/v2/scores?action=user某用户的成绩列表 · 鉴权:公开
| 参数 | 说明 |
|---|---|
user_id | 用户 ID |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/scores?action=user&user_id=1'
成功响应:{ data }
POST /api/v2/scores?action=upload上传成绩 · 鉴权:需登录
| 参数 | 说明 |
|---|---|
chartId | 谱面 ID |
score | 0–1010000 |
acc | 准确率 |
perfect_big | — |
perfect_small | — |
great | — |
good | — |
bad | — |
miss | — |
max_combo | — |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/scores?action=upload' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"chartId":1,"score":986500,"acc":99.42,"perfect_big":812,"perfect_small":64,"great":3,"good":0,"bad":0,"miss":0,"max_combo":512}'
成功响应:{ data, achievement }
rank/reality/achievement(R/AP/FC)由服务端按分数与谱面等级计算,不接受客户端传入。
/api/v2/records
GET /api/v2/records?action=getsong我的某个谱面的记录(按分数排序) · 鉴权:需登录
| 参数 | 说明 |
|---|---|
chartId | 谱面 ID 或 chart hash |
page | — |
limit | ≤100 |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/records?action=getsong&chartId=1&page=1&limit=20' \
-H "Authorization: Bearer $TOKEN"
成功响应:{ data: { items, total } }
响应示例
{
"data": {
"items": [
{
"id": 101,
"props": {
"chart_hash": "<hash>",
"score": 986500,
"score_accuracy": 0.9942,
"grade": "S",
"played_at": "2026-10-08T00:00:00Z"
},
"file_url": "https://file.xhsr.org.cn/records/…",
"create_time": 1791400000
}
],
"total": 4
}
}
GET /api/v2/records?action=getall我的全部记录(按时间排序) · 鉴权:需登录
| 参数 | 说明 |
|---|---|
page | — |
limit | ≤100 |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/records?action=getall&page=1&limit=20' \
-H "Authorization: Bearer $TOKEN"
成功响应:{ data: { items, total } }
GET /api/v2/records?action=download下载记录文件 · 鉴权:公开
| 参数 | 说明 |
|---|---|
id | 记录 ID |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/records?action=download&id=1'
成功响应:二进制流或 302
明文记录直出;仍是密文的用本地 RSA 私钥解密后返回,并回写明文。
POST /api/v2/records?action=upload上传游玩记录 · 鉴权:需登录
| 参数 | 说明 |
|---|---|
chartId | 谱面 ID(query) |
body | 原始二进制(RSA 加密的 asar) |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/records?action=upload&chartId=1' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/octet-stream' \
--data-binary @record.asar
成功响应:{ data: { id } }
请求体是 RSA 加密的 asar 原始二进制(
Content-Type: application/octet-stream),不是 JSON;chartId走 query。
每个 (用户, 谱面) 最多保留 4 条,超出按分数从低到高清理。
/api/v2/resource_packs
GET /api/v2/resource_packs?action=list资源包列表 · 鉴权:公开
| 参数 | 说明 |
|---|---|
page | — |
limit | — |
q | 搜索 |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/resource_packs?action=list&page=1&limit=20&q=关键词'
成功响应:{ data }
响应示例
{
"data": {
"items": [
{
"id": 1,
"meta": {
"name": "Bruh",
"description": "…",
"version": "0.1.0",
"author": "qaqFei",
"license": "",
"createTime": null,
"updateTime": null
},
"file_url": "https://file.xhsr.org.cn/resource-packs/1787361512490-11-bruh.mrp",
"status": "approved",
"download_count": 6,
"created_at": "2026-08-21T17:18:33.625Z"
}
],
"total": 1,
"page": 1,
"limit": 20
}
}
meta是读取时由扁平列(name/description/version/author/license/other_information/create_time/update_time)现拼的对象,库里没有meta列。
GET /api/v2/resource_packs?action=detail资源包详情 · 鉴权:公开
| 参数 | 说明 |
|---|---|
id | 资源包 ID |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/resource_packs?action=detail&id=1'
成功响应:{ data }
GET /api/v2/resource_packs?action=download下载资源包 · 鉴权:公开
| 参数 | 说明 |
|---|---|
id | 资源包 ID |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/resource_packs?action=download&id=1'
成功响应:二进制流或 302
POST /api/v2/resource_packs?action=init初始化上传(获取直传签名) · 鉴权:需登录
| 参数 | 说明 |
|---|---|
filename | — |
size | — |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/resource_packs?action=init' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"filename":"pack.mrp","size":"1048576"}'
成功响应:{ data }
POST /api/v2/resource_packs?action=finalize完成上传并登记 · 鉴权:需登录
| 参数 | 说明 |
|---|---|
key | 对象键 |
meta | 资源包元信息 |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/resource_packs?action=finalize' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"key":"<对象键>","meta":"<meta>"}'
成功响应:{ data }
POST /api/v2/resource_packs?action=upload上传资源包 · 鉴权:需登录
| 参数 | 说明 |
|---|---|
file | 文件 |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/resource_packs?action=upload' \
-H "Authorization: Bearer $TOKEN" \
-F 'file=@pack.mrp'
成功响应:{ data }
multipart 上传,字段名
file。服务端解析 MRP0 元数据后登记,form 里的其他字段一律忽略;成功后状态为 pending。
POST /api/v2/resource_packs?action=review审核资源包 · 鉴权:需 reviewer / moderator
| 参数 | 说明 |
|---|---|
id | — |
approve | boolean |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/resource_packs?action=review' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"id":1,"status":"approved"}'
成功响应:{ data }
不能审核自己上传的资源包(403)。
/api/v2/studio
GET /api/v2/studio?action=pending待审核谱面(含投票数) · 鉴权:需 reviewer / moderator
| 参数 | 说明 |
|---|---|
page | — |
limit | ≤200 |
q | 搜索 |
sort | oldest |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/studio?action=pending&page=1&limit=20&q=关键词&sort=newest' \
-H "Authorization: Bearer $TOKEN"
成功响应:{ data, page, limit, total }
GET /api/v2/studio?action=publish-requests待处理上架申请 · 鉴权:需 reviewer / moderator
请求示例
curl 'https://api.xhsr.org.cn/api/v2/studio?action=publish-requests' \
-H "Authorization: Bearer $TOKEN"
成功响应:{ data }
GET /api/v2/studio?action=queue统一审核队列 · 鉴权:需 reviewer / moderator
| 参数 | 说明 |
|---|---|
filter | pending_queue |
page | — |
limit | — |
q | — |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/studio?action=queue&filter=pending&page=1&limit=20&q=关键词' \
-H "Authorization: Bearer $TOKEN"
成功响应:{ data, page, limit }
POST /api/v2/studio?action=vote谱面审核投票 · 鉴权:需 admin / moderator
| 参数 | 说明 |
|---|---|
chartId | — |
vote | approve |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/studio?action=vote' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"chartId":8,"vote":"approve"}'
成功响应:{ message, approves, rejects, status }
双人通过制:approve≥2 → approved;reject≥1 → rejected。
POST /api/v2/studio?action=approve-publish上架申请投票 · 鉴权:需 admin / moderator
| 参数 | 说明 |
|---|---|
requestId | — |
approve | boolean |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/studio?action=approve-publish' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"requestId":1,"approve":true}'
成功响应:{ message, approves, rejects, processed, approved }
POST /api/v2/studio?action=request-publish申请上架自己的谱面 · 鉴权:需登录(仅本人)
| 参数 | 说明 |
|---|---|
chartId | 谱面 ID |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/studio?action=request-publish' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"chartId":"1"}'
成功响应:{ message }
仅上传者本人,且谱面状态须已 approved。
/api/v2/tickets
GET /api/v2/tickets?action=list工单列表 · 鉴权:需登录
| 参数 | 说明 |
|---|---|
scope | mine |
status | — |
type | — |
page | — |
limit | ≤200 |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/tickets?action=list&scope=mine&status=active&page=1&limit=20' \
-H "Authorization: Bearer $TOKEN"
成功响应:{ data, page, limit, total }
GET /api/v2/tickets?action=detail工单详情(含消息) · 鉴权:需登录
| 参数 | 说明 |
|---|---|
id | 工单 ID |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/tickets?action=detail&id=1' \
-H "Authorization: Bearer $TOKEN"
成功响应:{ data: { ticket, messages, can_manage } }
非版主只能看自己的单,且看不到 is_internal 内部备注。
GET /api/v2/tickets?action=stats工单统计 · 鉴权:需 admin / moderator
请求示例
curl 'https://api.xhsr.org.cn/api/v2/tickets?action=stats' \
-H "Authorization: Bearer $TOKEN"
成功响应:{ data: counts }
POST /api/v2/tickets?action=create创建工单 / 举报 · 鉴权:需登录
| 参数 | 说明 |
|---|---|
type | report_user |
subject | 标题(general 必填) |
content | 正文(≤5000) |
targetType | — |
targetId | — |
attachments | URL 数组(≤10) |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/tickets?action=create' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"type":"report_chart","content":"描述问题…","targetType":"chart","targetId":8,"attachments":["https://example.com/evidence.png"]}'
成功响应:{ message, data }
每用户每天 10 条;同一目标未关闭的重复举报返回 409。
POST /api/v2/tickets?action=reply回复工单 · 鉴权:需登录
| 参数 | 说明 |
|---|---|
id | 工单 ID |
content | 正文 |
internal | 内部备注(仅版主) |
attachments | — |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/tickets?action=reply' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"id":"1","content":"正文内容","internal":false,"attachments":["https://example.com/evidence.png"]}'
成功响应:{ message }
POST /api/v2/tickets?action=claim认领工单 · 鉴权:需 admin / moderator
| 参数 | 说明 |
|---|---|
id | 工单 ID |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/tickets?action=claim' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"id":"1"}'
成功响应:{ message, data }
POST /api/v2/tickets?action=resolve标记已解决 · 鉴权:需 admin / moderator
| 参数 | 说明 |
|---|---|
id | — |
note | 备注 |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/tickets?action=resolve' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"id":"1","note":"处理备注"}'
成功响应:{ message, data }
POST /api/v2/tickets?action=reject驳回工单 · 鉴权:需 admin / moderator
| 参数 | 说明 |
|---|---|
id | — |
note | 备注 |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/tickets?action=reject' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"id":"1","note":"处理备注"}'
成功响应:{ message, data }
/api/v2/content
GET /api/v2/content公告列表 / 详情 · 鉴权:公开
| 参数 | 说明 |
|---|---|
type | announcements |
id | 按 ID 查 |
slug | 按 slug 查 |
admin | 1 时需管理员且返回正文 |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/content?type=announcements'
成功响应:{ data }
GET /api/v2/content团队成员(按分组) · 鉴权:公开
| 参数 | 说明 |
|---|---|
type | team |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/content?type=team'
成功响应:{ data }
GET /api/v2/content作品列表 / 详情 · 鉴权:公开
| 参数 | 说明 |
|---|---|
type | creations |
id | — |
author | 按用户名 |
authorId | 按用户 ID |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/content?type=creations&author=demo'
成功响应:{ data }
GET /api/v2/content客户端下载列表 · 鉴权:公开
| 参数 | 说明 |
|---|---|
type | downloads |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/content?type=downloads'
成功响应:{ data }
GET /api/v2/content评论列表 · 鉴权:公开
| 参数 | 说明 |
|---|---|
type | comments |
target | chart |
targetId | — |
user_id | 查某人的评论 |
page | — |
limit | — |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/content?type=comments&target=chart&targetId=8&page=1&limit=20'
成功响应:{ data, page, limit, total }
POST /api/v2/content发表评论 · 鉴权:需登录
| 参数 | 说明 |
|---|---|
target | — |
targetId | — |
content | — |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/content?type=comments' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"target":"chart","targetId":8,"content":"写得真好!"}'
成功响应:{ message }
邮箱未验证或封禁返回 403。
POST /api/v2/content新建内容 · 鉴权:需 admin
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/content?type=announcements' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{…按 type 对应的字段…}'
成功响应:{ message }
PUT /api/v2/content编辑评论 · 鉴权:需登录(仅本人)
| 参数 | 说明 |
|---|---|
id | — |
content | — |
请求示例
curl -X PUT 'https://api.xhsr.org.cn/api/v2/content?type=comments' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"id":1,"content":"编辑后的内容"}'
成功响应:{ message }
PUT /api/v2/content更新内容 · 鉴权:需 admin
| 参数 | 说明 |
|---|---|
id | — |
请求示例
curl -X PUT 'https://api.xhsr.org.cn/api/v2/content' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"id":"1"}'
成功响应:{ message }
DELETE /api/v2/content删除评论(软删除) · 鉴权:需登录(仅本人)
| 参数 | 说明 |
|---|---|
id | — |
请求示例
curl -X DELETE 'https://api.xhsr.org.cn/api/v2/content?type=comments&id=1' \
-H "Authorization: Bearer $TOKEN"
成功响应:{ message }
DELETE /api/v2/content删除内容 · 鉴权:需 admin
| 参数 | 说明 |
|---|---|
id | — |
请求示例
curl -X DELETE 'https://api.xhsr.org.cn/api/v2/content?type=announcements&id=1' \
-H "Authorization: Bearer $TOKEN"
成功响应:{ message }
/api/v2/upload
按 query.type 分发:avatar / evidence / file / chart-zip / chart。注意区分:POST 上传用 ?type=,GET 拉头像用 ?action=avatar。
GET /api/v2/upload?action=avatar获取用户头像(公开) · 鉴权:公开
| 参数 | 说明 |
|---|---|
userId | 用户 ID |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/upload?action=avatar&userId=1'
成功响应:图片二进制直出 / 302 跳 CDN
默认头像从磁盘直出并缓存 7 天;自定义头像 302 重定向到 CDN 地址。
POST /api/v2/upload上传头像(multipart) · 鉴权:需登录
| 参数 | 说明 |
|---|---|
type | avatar |
file | 图片文件(≤8MB,multipart 字段名 file) |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/upload?type=avatar' \
-H "Authorization: Bearer $TOKEN" \
-F 'file=@avatar.png'
成功响应:{ url }
multipart 上传,字段名
file,≤8MB。成功返回{ "url": "https://file.xhsr.org.cn/…" }。
POST /api/v2/upload?action=init初始化谱面包直传(分资源预签名) · 鉴权:需登录
| 参数 | 说明 |
|---|---|
type | chart-zip |
resources | 资源数组 [{path, size, contentType?}] |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/upload?action=init' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"resources":[{"path":"chart.json","size":12345},{"path":"audio.mp3","size":6789012}]}'
成功响应:{ packageId, prefix, resources: [{path, key, size, contentType, inline, presignedUrl?}], expiresAt }
响应示例
{
"packageId": "1791401113381-abc123def456",
"prefix": "charts/1791401113381-abc123def456",
"resources": [
{
"path": "chart.json",
"key": "charts/1791401113381-abc123def456/chart.json",
"size": 12345,
"contentType": "application/json",
"inline": true
},
{
"path": "audio.mp3",
"key": "charts/1791401113381-abc123def456/audio.mp3",
"size": 6789012,
"contentType": "audio/mpeg",
"inline": false,
"presignedUrl": "https://…<预签名 PUT 地址>"
}
],
"expiresAt": 1791401713381
}
inline: true的资源走 finalize 的 multipart 内嵌;inline: false的资源客户端拿presignedUrl直传 R2(绕过 Vercel 4.5MB 限制),签名 10 分钟有效。
走 R2 预签名直传,绕过 Vercel 4.5MB 请求体限制;签名 10 分钟有效。
POST /api/v2/upload?action=finalize合并资源并完成谱面登记 · 鉴权:需登录
| 参数 | 说明 |
|---|---|
packageId | init 返回的 packageId |
resources | 资源清单(与 init 返回一致) |
title/artist/charter/difficulty/level/bpm/offset/description | 谱面元数据(multipart 字段) |
allow_ai_learning | boolean |
inline_<base64url(path)> | 小文件内嵌(multipart) |
请求示例
curl -X POST 'https://api.xhsr.org.cn/api/v2/upload?action=finalize' \
-H "Authorization: Bearer $TOKEN" \
-F 'packageId=1791401113381-abc123def456' \
-F 'resources=[{"path":"chart.json","key":"charts/1791401113381-abc123def456/chart.json","size":12345},{"path":"audio.mp3","key":"charts/1791401113381-abc123def456/audio.mp3","size":6789012}]' \
-F 'title=Endtime' -F 'artist=Cres.' -F 'charter=Heronmony' \
-F 'difficulty=Cloudburst' -F 'level=11+' -F 'bpm=180' \
-F 'allow_ai_learning=false' \
-F 'inline_YXVkaW8ubXAz=@audio.mp3'
成功响应:{ data: Chart }
multipart 表单。
inline_<base64url(path)>字段名内嵌小文件(例中YXVkaW8ubXAz即 base64url("audio.mp3"));直传资源由服务端回拉核对 size。成功后谱面状态为 pending,进入审核。
/api/v2/convert-zip
GET /api/v2/convert-zip获取谱面游玩元数据(本地库或 PhiZone) · 鉴权:公开
| 参数 | 说明 |
|---|---|
chartId | 谱面 ID,PhiZone 用 pz- |
source | local |
includeChartJson | 1 时附带谱面 JSON(较慢) |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/convert-zip?chartId=1&source=local&includeChartJson=<includeChartJson>'
成功响应:{ data: { chartUrl, audioUrl, coverUrl, metaUrl, extraUrl, title, isZip } }
响应示例
{
"data": {
"chartUrl": "https://file.xhsr.org.cn/charts/1771149806393-chart.json",
"chartJson": null,
"audioUrl": "https://file.xhsr.org.cn/charts/1771149807796-audio.mp3",
"coverUrl": "https://file.xhsr.org.cn/charts/1771149808591-image.png",
"coverData": null,
"metaUrl": "https://file.xhsr.org.cn/charts/recovered/8/meta.json",
"extraUrl": null,
"title": "Endtime",
"isZip": false
}
}
未上架谱面仅上传者本人与特权角色可查,防止枚举 pending 谱面的下载地址;PhiZone 上游异常返回 502。
/api/v2/admin
GET /api/v2/admin?action=stats站点统计 · 鉴权:需 admin
请求示例
curl 'https://api.xhsr.org.cn/api/v2/admin?action=stats' \
-H "Authorization: Bearer $TOKEN"
成功响应:{ data }
GET /api/v2/admin?action=users用户管理列表 · 鉴权:需 admin
| 参数 | 说明 |
|---|---|
page | — |
limit | ≤500 |
sort | — |
order | — |
role | — |
status | — |
q | — |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/admin?action=users&page=1&limit=20&sort=newest&order=desc&role=user&status=active&q=关键词' \
-H "Authorization: Bearer $TOKEN"
成功响应:{ data, page, limit, total }
GET /api/v2/admin?action=charts谱面管理列表 · 鉴权:需 admin
| 参数 | 说明 |
|---|---|
status | pending |
请求示例
curl 'https://api.xhsr.org.cn/api/v2/admin?action=charts&status=active' \
-H "Authorization: Bearer $TOKEN"
成功响应:{ data }
PUT /api/v2/admin?action=users修改用户角色 / 状态 · 鉴权:需 admin
| 参数 | 说明 |
|---|---|
userId | — |
role | user |
status | active |
请求示例
curl -X PUT 'https://api.xhsr.org.cn/api/v2/admin?action=users' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"userId":"1","role":"user","status":"active"}'
成功响应:{ message, user }
PUT /api/v2/admin?action=charts修改谱面状态 · 鉴权:需 admin
| 参数 | 说明 |
|---|---|
chartId | 或 id |
status | pending |
请求示例
curl -X PUT 'https://api.xhsr.org.cn/api/v2/admin?action=charts' \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"chartId":"1","status":"active"}'
成功响应:{ message }
| HTTP | code 示例 | 含义 |
|---|---|---|
| 400 | invalid_request | 参数缺失或不合法 |
| 401 | unauthorized | 未登录 / token 无效或过期 |
| 403 | account_banned / email_unverified / forbidden | 权限不足或账号状态受限 |
| 404 | not_found | 资源不存在 |
| 409 | duplicate_report / ticket_closed | 资源冲突 |
| 422 | — | 上传内容未通过校验(仅 records) |
| 429 | rate_limited | 触发频率限制 |
| 500 | internal_error | 服务端错误(生产不返回内部细节) |
| 502 | github_upstream_error | 上游(GitHub / Milkloud)异常 |
| 503 | — | 依赖功能未就绪(如未执行数据库迁移) |