v2 · 開發者指南
DOC / 03開放排行榜 API
遊戲級賽季、十維積分寫入、區域與全球排名及 Debug 沙箱。
開放排行榜 API 供已透過 NBTiktok 帳號與遊戲授權登入的用戶端探索榜單、查詢賽季、寫入十維積分,並讀取區域或全球排名。公開 Base URL 為:
https://<platform-origin>/openapi/v2
所有請求都使用登入回應中的短期 credentials.gameApi.token,穩定驗證錯誤格式為 { "error": { "code": "ERROR_CODE", "requestId": "018f1f6d-7b2a-7e34-a6cc-5e5f3fb70420" } }。不得直接存取容器埠、/internal/* 或 /admin/api/*。登入、Session、refresh 與 WebSocket 合同請參閱互動遊戲串接 API。
#快速串接
- 以
mode: "production"或mode: "debug"呼叫POST /api/game-client/v2/login。 - 讀取
credentials.gameApi.token與credentials.gameApi.expiresAt。 - 呼叫
GET /openapi/v2/leaderboards探索榜單。 - 只有
currentSeason存在時才寫入積分。 - 每名玩家都提交
score1~score10,並使用獨立 UUIDrequestId。 - 查詢排行榜或單一玩家名次。
- 到達 Session 的
refreshAt時,原子切換到新的Game API Token。
#公開接口與驗證
| 能力 | Method / Path |
|---|---|
| 探索榜單 | GET /openapi/v2/leaderboards |
| 查詢模擬玩家 | GET /openapi/v2/leaderboards/simulation-players |
| 查詢賽季 | GET /openapi/v2/leaderboards/:boardKey/seasons |
| 寫入十維積分 | POST /openapi/v2/leaderboards/:boardKey/score-mutations |
| 查詢排名列表 | GET /openapi/v2/leaderboards/:boardKey/seasons/:seasonKey/rankings |
| 查詢單一玩家排名 | GET /openapi/v2/leaderboards/:boardKey/seasons/:seasonKey/players/:playerId/rank |
| 查詢目前週榜 | GET /openapi/v2/leaderboards/:boardKey/weeks/current/rankings |
| 查詢單一玩家目前週排名 | GET /openapi/v2/leaderboards/:boardKey/weeks/current/players/:playerId/rank |
Authorization: Bearer <gameApiToken>
Accept: application/json
Token 是 Core 簽署的短期 JWT,綁定遊戲、主播與模式,並由 Game 本機驗簽;不得記錄或持久化。Production 對應 production 積分空間,Debug 對應 sandbox。
Token 鎖定 gameId、space、streamerId、regionCode、authorizationId 與 Session,用戶端不得覆蓋。經銷商成員的寫入區域來自經銷商;個人授權來自帳號區域。支援 SG、MY、TH、ID、PH、TW,寫入請求不能指定區域。Token 最長 10 分鐘,並可能受 Session 絕對期限縮短。
credentials.gameApi.token 與 Session access Token、refresh Token、WebSocket Ticket 都不同。Refresh 後必須原子安裝新憑證並立即停止主動使用舊 Game API Token;舊 Token 的安全有效性持續到自身 10 分鐘 exp,或更早收到 Session 撤銷投影。
#資料模型
每個榜單都有穩定 boardKey、名稱、排序方向、支援的積分欄位、區域與可能為空的 currentSeason。升冪榜以較小分數優先,降冪榜以較大分數優先。
寫入接口沒有 seasonKey,伺服器會解析請求當下的活動賽季;沒有活動賽季時回傳 409 CURRENT_SEASON_UNAVAILABLE。
每次 mutation 必須恰好包含:
score1, score2, score3, score4, score5,
score6, score7, score8, score9, score10
積分是有符號 64 位元整數,以十進位字串傳輸,應以 64 位整數或 BigInt 解析,不能使用 JSON number。值、運算結果和全球彙總都必須在 -9223372036854775808~9223372036854775807。
寫入只修改 Token regionCode。每個積分欄位的全球值是玩家最佳區域值:desc 取最大、asc 取最小,區域並列時取 regionCode 字典序最小者;十個最佳來源在同一交易重算。全球排名項目回傳該來源 regionCode,區域查詢回傳指定區域。
Production 區域分依遊戲 × 榜單 × 賽季 × 玩家 × regionCode 隔離,再推導玩家全球最佳值;Sandbox 額外依主播 × 遊戲隔離。兩個空間不共用分數或冪等紀錄。
賽季是遊戲級權威時間線,遊戲內所有榜單同時切季並共用 scoreRetentionBasisPoints(0..10000,預設 50 即 0.5%)。Production 十個積分欄位依比例繼承並向零取整;全球最佳值與來源區域會重新計算。同一交易也會按已發佈 seasonReset 重置六個區域的 Production 玩家屬性。Debug 沙箱積分不複製,Sandbox 玩家屬性不重置;只提前關閉賽季不會重置任何資料。管理員儲存自動規則時,只有自動賽季依新規則更新時間;目前手動賽季保留明確結束時間,結束後才由自動規則接管。
#探索榜單
GET /openapi/v2/leaderboards
Authorization: Bearer <gameApiToken>
{
"space": "production",
"scoreFields": [
"score1", "score2", "score3", "score4", "score5",
"score6", "score7", "score8", "score9", "score10"
],
"gameId": "game_one",
"boards": [
{
"boardKey": "main",
"displayName": "總積分榜",
"scoreOrder": "desc",
"currentSeason": {
"seasonId": "018f2474-0c41-7bd8-9caf-2676c116b112",
"seasonKey": "auto-20260819000000-r3",
"startsAt": "2026-08-19T00:00:00.000Z",
"endsAt": "2026-08-20T00:00:00.000Z",
"status": "active"
}
},
{
"boardKey": "speedrun",
"displayName": "最快通關",
"scoreOrder": "asc",
"currentSeason": null
}
]
}
boards 依 boardKey 排序。currentSeason: null 表示可讀歷史賽季但不能寫入。登入或 refresh 後應重新探索,不要寫死榜單、排序或賽季時間。
#模擬玩家與賽季
Debug 用戶端可呼叫 GET /openapi/v2/leaderboards/simulation-players 取得目前主播 × 遊戲的預設與自訂模擬玩家。Production Token 也能呼叫,但回傳始終是 Sandbox 玩家。Production 寫入使用 canonical sourcePlayerId 並另傳 playerName;排行榜回傳 playerId 與來源 regionCode,不回直播暱稱。
{
"players": [
{ "playerId": "debug-player-1", "displayName": "Debug Player 1" }
]
}
賽季請求為 GET /openapi/v2/leaderboards/main/seasons?limit=50。limit 可選 1–100,預設 50;cursor 是同榜單上一頁的不透明游標。回應 seasons 依 startsAt 新到舊,含 seasonKey、startsAt、endsAt、status(scheduled、active、ended)、source(manual、automatic)及建立賽季時保存的 scoreRetentionBasisPoints,另有 nextCursor。路徑可用真實 seasonKey 或 current;歷史賽季可查但不能公開寫入。
#寫入積分
POST /openapi/v2/leaderboards/main/score-mutations
Authorization: Bearer <gameApiToken>
Content-Type: application/json
{
"mutations": [
{
"requestId": "533b2994-b638-45a8-9db2-f0fc926fe9e5",
"playerId": "tiktok-user-id",
"playerName": "Alice",
"operation": "increment",
"scores": {
"score1": "100",
"score2": "0",
"score3": "0",
"score4": "0",
"score5": "0",
"score6": "0",
"score7": "0",
"score8": "0",
"score9": "0",
"score10": "0"
}
}
]
}
mutations 必須包含 1–20 名玩家,整個本文最大 128 KiB。每個 requestId 是 UUID v1–v5,同批不得重複,建議全域唯一;playerId 是非空、不超過 256 字元的不透明值;scores 恰好含 score1~score10 的帶可選負號十進位字串。operation 只能是 increment、set 或 max。
Production 每名玩家必須提供 1–200 字元 playerName,Game 會保存該區域名稱;Sandbox 必須省略,名稱使用已登記模擬玩家。
一次 operation 套用全部十欄。只增加部分欄位時,其餘用 "0";set 覆蓋全部,max 逐欄比較,不存在省略欄位保持不變。舊版單欄 scoreField、value 形狀會回傳 400 INVALID_MUTATIONS。
整批在同一資料庫交易中序列處理;任一 mutation、積分或冪等檢查失敗都會回滾。相同玩家與區域的並行寫入會被序列化,區域分與十個全球最佳來源一起更新。
{
"season": {
"seasonId": "018f2474-0c41-7bd8-9caf-2676c116b112",
"seasonKey": "auto-20260819000000-r3",
"startsAt": "2026-08-19T00:00:00.000Z",
"endsAt": "2026-08-20T00:00:00.000Z",
"status": "active"
},
"mutations": [
{
"requestId": "533b2994-b638-45a8-9db2-f0fc926fe9e5",
"playerId": "tiktok-user-id",
"displayName": "Alice",
"regionCode": "SG",
"operation": "increment",
"scores": {
"score1": "1280",
"score2": "0",
"score3": "0",
"score4": "0",
"score5": "0",
"score6": "0",
"score7": "0",
"score8": "0",
"score9": "0",
"score10": "0"
}
}
]
}
season.seasonKey 是實際寫入賽季;每項包含已保存 displayName 與 Token regionCode,scores 是該區域寫入後的十個新值,不是全球最佳值。
#冪等與重試
requestId 代表單一玩家的一次十維 mutation。Production 的冪等範圍涵蓋全部正式資料;Sandbox 的範圍是遊戲 × 主播。兩種模式都應為新 mutation 產生全域唯一 UUID。
- 完全相同的重送會回傳原結果,不重複寫入。
- 在同一範圍重用
requestId,但改變遊戲、玩家、操作、區域或任何積分,回傳409 IDEMPOTENCY_CONFLICT。 - 逾時或暫時性錯誤後,以相同
requestId重送完全相同的內容。 - 不得以新 UUID 掩蓋可能已提交的資料。
新請求會解析當時的 current 賽季。即使重試跨過賽季邊界,完全相同的 requestId 與內容仍回傳最初解析的原賽季和原 mutation 結果,不會寫入新賽季。批次若混合全新 mutation 與歷史重播,或重播來自不同原賽季,整批回傳 409 IDEMPOTENCY_CONFLICT 並回滾。同一 HTTP 批次重複 requestId 回傳 400 INVALID_MUTATIONS。必須保存原請求並核對,不能換 UUID。可靠直播事件影響單玩家時可用 eventId 作為 requestId;多玩家需為每人確定性產生不同 UUID。積分寫入成功後才能 ACK 原事件。
#排名查詢
GET /openapi/v2/leaderboards/main/seasons/current/rankings?scoreField=score2&scope=global&limit=100
Authorization: Bearer <gameApiToken>
scoreField 必填,只接受 score1~score10。scope 為 global 或 region;區域查詢還要提供合法 region。limit 最大 100;下一頁使用不透明 cursor。
{
"scoreField": "score2",
"scoreOrder": "desc",
"rankings": [
{
"rank": 1,
"playerId": "tiktok-user-id",
"regionCode": "SG",
"score": "980"
}
],
"nextCursor": null,
"space": "production"
}
每組空間 + 遊戲 + Sandbox 主播 + 榜單 + 賽季 + 範圍 + 區域 + 積分欄位最多公開排序後前 100 行;limit 僅在其中分頁。相同分數使用 SQL RANK 共用名次,後續名次可跳號,以 playerId 穩定同分順序;scoreOrder=asc 時低分較前。游標綁定原查詢上下文,不得解析、修改或換條件重用,否則 400 INVALID_CURSOR。
單一玩家接口:
GET /openapi/v2/leaderboards/main/seasons/current/players/tiktok-user-id/rank?scoreField=score2&scope=region®ion=SG
此接口的 scoreField 預設為 score1,scope 預設全球。ranking.regionCode 在全球範圍代表最佳來源區域,在區域範圍等於查詢區域。單玩家接口在完整榜單上計算名次,可超過 100;沒有資料時為 { "ranking": null }。
#目前週榜
目前週固定依 Asia/Shanghai,從週一 00:00 到下週一 00:00。週分每週從 0 開始,獨立執行每次成功寫入的相同 increment、set 或 max;Production 管理員調分以週分 increment 計入,冪等重放不會重複累計。週分跨賽季延續,不受切季繼承或清零影響。
列表使用 GET /openapi/v2/leaderboards/main/weeks/current/rankings?scoreField=score2&scope=global&limit=100,單玩家使用 GET /openapi/v2/leaderboards/main/weeks/current/players/tiktok-user-id/rank?scoreField=score2。篩選、前 100 名分頁、SQL RANK、排序方向與最佳來源區域都沿用賽季榜;單玩家 scoreField 預設 score1 並在完整週榜計算。
兩個回應都包含 space 與 week,其欄位為 startsAt、endsAt、timeZone、isPartial、trackingStartedAt。若追蹤在本週週一之後才上線,部署首週 isPartial 為 true;下一個完整週自動變為 false,平台不猜測或回填舊 mutation。Production、Sandbox 仍由 Token 決定並隔離,讀取週榜不要求目前存在活動賽季。
#Debug 沙箱
Debug 登入的回應空間固定是 sandbox。沙箱分數、全球彙總與 requestId 依主播 × 遊戲隔離;相同主播與遊戲重新登入或 refresh 後仍可存取,同遊戲不同主播或同主播不同遊戲不共享。榜單與賽季仍由平台管理員配置。
#錯誤與恢復
| HTTP | 錯誤 | 處理 |
|---|---|---|
| 400 | INVALID_REGION | 使用支援區域,寫入區域來自帳號權威 |
| 400 | INVALID_LIMIT | 使用接口規定的分頁範圍 |
| 400 | INVALID_SCORE_FIELD | 使用 score1~score10 |
| 400 | INVALID_MUTATIONS / INVALID_SCORE | 修正完整十欄 mutation |
| 400 | REGION_REQUIRED | 區域排名提供 region |
| 400 | INVALID_CURSOR | 丟棄游標,從第一頁開始 |
| 401 | INVALID_ACCESS_TOKEN / INVALID_ACCESS_TOKEN | 透過 refresh 或登入更換憑證 |
| 404 | SEASON_NOT_FOUND | 重新整理賽季並檢查 seasonKey |
| 409 | CURRENT_SEASON_UNAVAILABLE | 等待活動賽季或聯絡管理員 |
| 409 | SEASON_NOT_ACTIVE | 重新整理榜單與賽季 |
| 409 | IDEMPOTENCY_CONFLICT | 核對原請求,不得重複寫入 |
| 413 | 本文過大 | 拆成最多 20 人且不超過 128 KiB |
| 500 | INTERNAL_ERROR | 保留 requestId 與上下文並聯絡平台 |
讀取遇到網路或 5xx 使用指數退避加隨機抖動;寫入只有原上下文、內容、requestId 完全相同才能重試。Token 失效時先序列 refresh,結果不確定時不能重用一次性 refresh Token。
#上線檢查清單
- 只呼叫公開 HTTPS Origin 的
/openapi/v2/*,且只使用Game API Token; - 登入與 refresh 後重新探索榜單,寫入前確認
currentSeason; - 每名玩家提交十個字串積分,以 64 位整數或 BigInt 處理;
- 持久化唯一
requestId、請求與結果; - Production 使用 canonical
sourcePlayerId,Debug 使用模擬玩家; - 區分
production與sandbox;游標只用於原查詢下一頁; - 衝突時核對原請求與賽季,不換 UUID 重複寫入;
- 日誌僅記錄
gameId、boardKey、實際seasonKey、狀態碼、requestId,絕不記錄 Authorization Header。