v2 · 開發者指南
DOC / 01互動遊戲串接 API
Email 密碼登入、用戶端 Session、WebSocket 標準事件、可靠 ACK 與 Debug 測試。
本 API 供遊戲用戶端以 Email 與密碼建立 Session、選擇性登入獨立遊戲業務後台、透過 WebSocket 接收直播互動事件,以及在 Debug 模式測試相同流程。
請只使用 NBTiktok 提供的公開 HTTPS Origin;不得直接連線容器、診斷埠、/internal/* 或管理路由。
#串接流程
- 呼叫
POST /api/game-client/v2/login,提交 Email、密碼、gameId、mode與clientBuild;不使用 CAPTCHA。 - 直接串接排行榜或玩家屬性時不送
backend,回應提供credentials.gameApi。 - 啟用獨立後台時,登入回傳
backendAuth與credentials.gameBackend;URL 由用戶端可信設定選擇。 credentials.gameBackend只能傳到目前 build 選定的可信 URL。- 使用一次性 WebSocket Ticket 連線並驗證
ready;Production 持久化後 ACK,Debug 回覆debug_command。 - 在
refreshAt輪換整組憑證並續簽獨立後台 WSS;平台事件連線斷線時申請新 Ticket,最後以DELETE /api/game-client/v2/session結束 Session。
#公開路由
| 能力 | Method / Path | 驗證 |
|---|---|---|
| 登入並建立 Session | POST /api/game-client/v2/login | 僅 Email、密碼、遊戲、模式與 build |
| 已移除的裝置授權 | POST /api/game-client/v2/device-authorizations 與 /token | 回傳 410 DEVICE_AUTHORIZATION_REMOVED |
| 已移除的 CAPTCHA | GET /api/game-client/v2/captcha | 回傳 410 CAPTCHA_LOGIN_REMOVED |
| 查詢 Session | GET /api/game-client/v2/session | Bearer <sessionAccessToken> |
| 輪換憑證 | POST /api/game-client/v2/session/refresh | Bearer <refreshToken>;空本文 |
| 申請 WebSocket Ticket | POST /api/game-client/v2/session/websocket-ticket | Bearer <sessionAccessToken>;空本文 |
| 登出 | DELETE /api/game-client/v2/session | Bearer <sessionAccessToken> |
| 事件連線 | GET /ws/game-client/v2?ticket=... | 60 秒、僅可使用一次的 Ticket |
| 瀏覽公開遊戲 | GET /api/games/v2 | 無 |
| 查詢本地化玩法指南 | GET /api/games/v2/:gameId/guide?locale=... | 無 |
| 遊戲禮物目錄 | GET /api/games/v2/:gameId/gifts | 無 |
| 遊戲截圖 | GET /api/games/v2/:gameId/screenshots/:position | 無;position 為 1~5 |
| 排行榜與玩家屬性 | /openapi/v2/* | Bearer <credentials.gameApi.token> |
| 獨立後台 HTTP/WSS | 用戶端可信 URL | HTTP Bearer;WSS nbt.auth 首幀 |
| 後台驗簽公鑰 | GET /.well-known/game-backend-jwks.json | 無;依 Cache-Control 快取 |
除 204 與成功的截圖二進位回應外,HTTP 本文都是 JSON。登入使用扁平格式 { "error": "INVALID_CREDENTIALS" };Session、WebSocket Ticket、/openapi/v2/* 與網關限流使用 { "error": { "code": "ERROR_CODE", "requestId": "018f1f6d-7b2a-7e34-a6cc-5e5f3fb70420" } }。
#Email 密碼登入
POST /api/game-client/v2/login
Content-Type: application/json
{
"email": "streamer@example.com",
"password": "account-password",
"gameId": "game_one",
"mode": "production",
"clientBuild": 120
}
密碼只提交給 NBTiktok 平台,請求結束後立即從記憶體清除,絕不可寫入設定、日誌、URL、當機報告或遙測。平台永遠不會把 Email、密碼、登入 Token 或 Cookie 傳給獨立後台。Email 密碼決定帳號及其 Sandbox 歸屬。
#登入成功回應
{
"session": {
"sessionId": "6fe3890b-bb7c-4e45-b2b2-1db2d77751ce",
"streamerId": "9f833c5b-ec62-4530-9ae4-0af8130a26b6",
"authorizationId": "20637113-6d4e-490c-ac6d-74242cd1de85",
"gameId": "game_one",
"mode": "production",
"credentialSource": "user_session",
"credentialId": null,
"roomId": "creator-room",
"clientBuild": 120,
"minimumClientBuild": 100,
"expiresAt": "2026-08-20T12:00:00.000Z",
"absoluteExpiresAt": "2026-08-26T12:00:00.000Z"
},
"credentials": {
"sessionAccess": {
"token": "opaque-session-access-token",
"expiresAt": "2026-08-19T12:30:00.000Z"
},
"gameApi": {
"token": "signed-game-api-token",
"expiresAt": "2026-08-19T12:10:00.000Z"
},
"gameBackend": {
"token": "signed-game-backend-token",
"expiresAt": "2026-08-19T12:30:00.000Z"
},
"refresh": {
"token": "opaque-single-use-refresh-token",
"expiresAt": "2026-08-20T12:00:00.000Z",
"refreshAt": "2026-08-19T12:08:00.000Z"
},
"websocket": {
"url": "/ws/game-client/v2",
"ticket": "opaque-single-use-ticket",
"expiresAt": "2026-08-19T12:01:00.000Z"
}
},
"backendAuth": {
"audience": "urn:nbtiktok:game-backend:game_one",
"operatorName": "Example Studio",
"privacyPolicyUrl": "https://game.example.com/privacy",
"configVersion": 3
}
}
伺服器回傳的 ID、房間、模式、版本策略、期限與路徑皆為權威資料。密碼、Token 與 Ticket 不得寫入 PlayerPrefs、設定檔、日誌、URL、當機報告或遙測。
session access Token 最長 30 分鐘,Game API Token 最長 10 分鐘;獨立後台 JWT 最長 30 分鐘。由於 refreshAt 取所有短期憑證刷新點的最早值,整組憑證通常仍在簽發後約 8 分鐘 refresh。refresh Token 僅可使用一次,Session 滑動期限最長 24 小時;Ticket 最長 60 秒且僅可成功消耗一次。
同帳號同時只允許一個 Production Session,新 Production 登入即使是不同遊戲也會撤銷舊 Session。Debug 依 authorizationId 保留一個 Session,可與 Production 同時存在;同一授權的新 Debug 登入會替換舊 Session。
#登入與 Session 建立錯誤
| HTTP | error | 處理 |
|---|---|---|
| 400 | MODE_INVALID | 修正 mode |
| 400 | CLIENT_BUILD_INVALID | 提交至少為 1 的安全整數 |
| 401 | INVALID_CREDENTIALS | Email 不存在、密碼錯誤或帳號暫時鎖定;顯示一致訊息 |
| 403 | DEALER_UNAVAILABLE | 檢查經銷商與成員狀態 |
| 403 | GAME_DISABLED | 停止登入已停用遊戲 |
| 403 | GAME_NOT_AUTHORIZED | 取得有效遊戲授權 |
| 404 | GAME_NOT_FOUND | 修正 gameId |
| 409 | ROOM_ID_REQUIRED | 在個人資料填寫直播間 ID |
| 409 | PROFILE_INCOMPLETE | 補齊區域資料 |
| 409 | GIFT_CREDIT_EXHAUSTED | 恢復禮物額度後再登入 |
| 409 | BILLING_CONFIG_REQUIRED | 等待 Production 計費設定完成 |
| 426 | CLIENT_BUILD_UNSUPPORTED | 升級至回應的 minimumClientBuild |
| 429 | LOGIN_RATE_LIMITED / RATE_LIMITED | 遵循 Retry-After 並退避 |
| 503 | AUTH_UNAVAILABLE / BILLING_BACKLOG_PAUSED | 指數退避並提示使用者 |
426 回應使用 V2 envelope:error.code 為 CLIENT_BUILD_UNSUPPORTED、error.requestId 為追蹤 ID,error.details 帶有送出的 clientBuild 與目前 minimumClientBuild。
#獨立後台路由
NBTiktok 不保存也不回傳業務 API URL。本機、測試與正式版本使用相同的 credentials.gameBackend JWT、精確遊戲 Audience 與 JWKS 驗證。用戶端從可信 build 設定讀取 URL;開發 build 可在本機覆寫。登入請求不傳 URL 或額外 Secret。
回應僅含 backendAuth 的 Audience、營運方、隱私政策與設定版本,不含路由。JWT 只能傳給用戶端選定的可信 Origin,並拒絕跨 Origin 重新導向。
HTTP 以 Authorization: Bearer 傳送 JWT。獨立後台 WebSocket 使用固定子協定 nbt.game-backend.v1,只在第一個 {"type":"nbt.auth","token":"..."} 文字幀傳送同一 JWT,絕不放入 URL;Session refresh 後以新 JWT 在原連線再次傳送該幀。這不是平台 /ws/game-client/v2,後者仍只使用一次性 credentials.websocket.ticket。
可信 build 應分別設定 NBT_GAME_BACKEND_HTTP_URL 與 NBT_GAME_BACKEND_WS_URL。明文 http/ws 僅允許 localhost、127.0.0.0/8 與 [::1],遠端測試及正式環境必須使用 https/wss。
瀏覽器 Portal 使用 NBTiktok Email 密碼重新驗證及 Authorization Code + PKCE。正式回呼精確比對已登記 HTTPS URI;本機回呼只允許 127.0.0.1 或 [::1] 的 /__nbt/callback。
#已退役端點
#Session 與憑證輪換
GET /api/game-client/v2/session
Authorization: Bearer <sessionAccessToken>
所有 Session 都保留 credentialSource="user_session" 與 credentialId=null。請核對身份、模式、直播間、build 與到期欄位;後台路由仍由用戶端設定持有。Sandbox 的 roomId 可為 null。
POST /api/game-client/v2/session/refresh
Authorization: Bearer <refreshToken>
空本文成功回應會提供新的 session、權威 backendAuth、access、Game API、可選 game backend、一次性 refresh Token,但不含 websocket。整組憑證需原子替換、停止用舊 Token 發起 HTTP、以新 Backend JWT 續簽獨立後台 WebSocket,並丟棄未使用的平台 Ticket。若不再回傳 gameBackend,立即關閉獨立後台連線並停止呼叫。
舊 refresh Token 重複使用會回傳 REFRESH_TOKEN_REUSED,撤銷整個 Session 並以 4014 關閉 WebSocket。未知或過期 Token 回傳 401 REFRESH_TOKEN_INVALID;Session 絕對期限或上下文失效時回傳 401 SESSION_REAUTH_REQUIRED。若網路錯誤導致無法判定是否已消耗,不得重送同一 Token;應清除記憶體憑證並重新登入。refreshAt 永遠是 ISO 8601 時間;若用戶端恢復時已到期,應立即執行一次串行 refresh;收到 SESSION_REAUTH_REQUIRED 時清除憑證並重新登入。
#WebSocket 與連線圍欄
將公開 HTTPS Origin 轉成 wss://,再附加相對路徑:
wss://<platform-origin>/ws/game-client/v2?ticket=<single-use-ticket>
URL 只能放 Ticket。Ticket 無效、已用或過期時握手為 401;計費狀態可能回傳 409;容量或直播來源問題可能回傳 503。斷線後,以仍有效的 access Token 呼叫 POST /api/game-client/v2/session/websocket-ticket 取得新 Ticket。
第一個業務訊息必須是:
{
"type": "ready",
"sessionId": "6fe3890b-bb7c-4e45-b2b2-1db2d77751ce",
"userId": "account-user-id",
"streamerId": "9f833c5b-ec62-4530-9ae4-0af8130a26b6",
"authorizationId": "20637113-6d4e-490c-ac6d-74242cd1de85",
"gameId": "game_one",
"mode": "production",
"credentialSource": "user_session",
"credentialId": null,
"roomId": "creator-room",
"clientBuild": 120,
"minimumClientBuild": 100,
"leaseGeneration": 4
}
逐項比對 sessionId、streamerId、authorizationId、gameId、mode、roomId、clientBuild 與 minimumClientBuild,並要求 userId 是 UUID、leaseGeneration 是正整數。任何不符都拒絕連線。所有事件與 ACK 佇列要綁定 gameId 與 leaseGeneration;舊連線狀態不得重用。心跳使用 WebSocket 協定 Ping/Pong,不要傳送自訂 { "type": "pong" }。
#Production 事件與可靠 ACK
ready 後拉取未確認事件;limit 預設 100、最大 200:
{
"type": "pull",
"gameId": "game_one",
"leaseGeneration": 4,
"limit": 100
}
事件 envelope:
{
"type": "event",
"gameId": "game_one",
"leaseGeneration": 4,
"event": {
"eventId": "2d2a0ef5-2388-45de-a255-2dcc73543d3b",
"eventType": "gift",
"occurredAt": "2026-08-19T12:00:00.000Z",
"payload": {
"player": { "sourcePlayerId": "viewer-id", "displayName": "Viewer" },
"gift": {
"providerGiftId": "5655",
"displayName": "Rose",
"totalCount": 1,
"deltaCount": 1,
"unitDiamondCount": 5,
"giftCoins": 5,
"missingPrice": false,
"groupId": "gift-group",
"repeatEnd": true
}
}
}
}
eventId 是 UUID,occurredAt 是 ISO 8601;gameId 與 leaseGeneration 僅存在外層 envelope。eventType 包含 comment、like、gift、stream_end。留言使用 payload.player 與 content;按讚使用 payload.player 與 count;禮物使用上述巢狀 player、gift。
[
{
"eventId": "b59fc69c-4c6a-4c09-990b-e0d6514b03fd",
"eventType": "comment",
"occurredAt": "2026-08-19T12:00:01.123Z",
"payload": { "player": { "sourcePlayerId": "viewer-id", "displayName": "Viewer" }, "content": "join" }
},
{
"eventId": "3a83d2f9-2095-4821-926d-ff8a80a0e771",
"eventType": "like",
"occurredAt": "2026-08-19T12:00:02.000Z",
"payload": { "player": { "sourcePlayerId": "viewer-id", "displayName": "Viewer" }, "count": 12 }
}
]
禮物連擊的 totalCount 是累計、deltaCount 是本事件增量;遊戲發獎應使用 deltaCount。groupId 可為 null,repeatEnd 表示連擊結束。unitDiamondCount 可為 0 且 missingPrice: true;遊戲不得從事件推斷平台扣費,也不能把 ack_result.consumed 當成鑽石數。
只有 comment、gift 持久化重播並需要 ACK;like 僅即時、不 ACK;stream_end 不重播、不 ACK,之後以 4007 關閉。未知新增欄位應忽略,但必須以權威 eventId 做冪等。
ACK 表示效果與處理狀態都已可靠保存,而非只收到 WebSocket。失敗事件不得 ACK,讓 Core 重播;已完成的重播不得重做效果,只重新 ACK 同一 eventId。
{
"type": "ack",
"gameId": "game_one",
"leaseGeneration": 4,
"eventIds": ["2d2a0ef5-2388-45de-a255-2dcc73543d3b"]
}
單批硬上限為 500 個不重複 UUID;建議 25ms 或累計 100 個事件即送出,以先到者為準,且同連線只保留一個在途批次。
{
"type": "ack_result",
"gameId": "game_one",
"leaseGeneration": 4,
"acknowledged": 1,
"consumed": 1,
"duplicate": 0
}
acknowledged 是首次確認數,consumed 是首次進入禮物消費稽核的事件數(不是鑽石),duplicate 是先前已確認數。必須滿足 acknowledged + duplicate === 本批 eventIds 數量 與 consumed <= acknowledged;整批重播可為 acknowledged = 0, duplicate = N。無法匹配或計數錯誤代表協定失步:關閉連線、清空記憶體 ACK 佇列、申請新 Ticket,再以重播與本機狀態恢復。
#Debug 協定
Debug 的登入、refresh、Ticket、ready 與圍欄相同。debug_command 包含 commandId、requiresAck: true、transient: true,以及具有 eventId、eventType、occurredAt、payload 的完整 event。指令不持久化、不重播、不進入 Production 事件、消費稽核或扣費,通常應在 15 秒內回覆。
{
"type": "debug_command",
"commandId": "da6c668b-9e00-48d0-8113-97fbd52dc43f",
"gameId": "game_one",
"leaseGeneration": 4,
"requiresAck": true,
"transient": true,
"event": {
"eventId": "75c93fa7-3e1e-4fbc-b736-37b21c49e3d6",
"eventType": "gift",
"occurredAt": "2026-08-19T12:00:03.000Z",
"payload": {}
}
}
執行後必須明確回覆:
{
"type": "debug_command_ack",
"gameId": "game_one",
"leaseGeneration": 4,
"commandId": "79ac82bb-a842-4817-aeb8-cf345db37639",
"outcome": "processed"
}
失敗時使用 "outcome": "failed",並提供 1–128 字元的 errorCode,例如 EFFECT_REJECTED;成功時必須省略 errorCode。Core 回傳 type: "debug_command_ack_received",status 為 processed 或 failed。Debug 用戶端不得傳 Production pull 或 ack。
#錯誤、禮物目錄與登出
協定錯誤格式是 { "type": "error", "error": "ERROR_CODE" }。SESSION_FENCE_STALE、ACK_EVENT_INVALID、ACK_FAILED 或 Debug 圍欄錯誤都應斷線並由持久化狀態恢復。
| 關閉碼 | 原因 | 是否續用 Session | 行為 |
|---|---|---|---|
4001 | SESSION_REPLACED | 否 | 停止舊用戶端並清除憑證 |
4002 | CLIENT_LOGOUT | 否 | 正常退出 |
4003 | SESSION_INVALID / SESSION_EXPIRED / SESSION_REAUTH_REQUIRED | 否 | 重新登入 |
4004 | AUTHORIZATION_INVALID / AUTHORIZATION_EXPIRED | 否 | 修復授權後登入 |
4005 | GAME_DELETED / GAME_DISABLED | 否 | 停止遊戲 |
4006 | CLIENT_BUILD_UNSUPPORTED | 否 | 升級用戶端 |
4007 | STREAM_ENDED | 否 | 結束本場 Production |
4008 | SOURCE_BINDING_CHANGED | 否 | 更新個人資料後登入 |
4009 | 管理或發布下線 | 否 | 顯示 reason 並視情況登入 |
4010 | SLOW_CONSUMER | 是 | 降低阻塞,退避重連 |
4011 | SOURCE_RELEASED | 是 | 退避後申請新 Ticket |
4012 | BALANCE_EXHAUSTED | 否 | 清除憑證並補充額度 |
4013 | HEARTBEAT_TIMEOUT | 是 | 檢查網路後退避重連 |
4014 | REFRESH_TOKEN_REUSED | 否 | 清除全部憑證再登入 |
只有 4010、4011、4013 可用有上限的指數退避加隨機抖動重連;其他皆為 Session 終止。
禮物目錄使用 GET /api/games/v2/game_one/gifts,無需驗證。回應含 game.gameId、game.displayName,每個有效禮物含必填 key、catalogGiftId、providerGiftId、displayName、diamondCount、可為 null 的 iconUrl。key 是當前遊戲內唯一的效果觸發識別,不同遊戲可重複使用;載入或更新目錄時應建立 providerGiftId → key 對照。WebSocket 禮物事件本身仍只帶 providerGiftId,不帶 key;若事件 ID 不在目錄中,不得猜測或拼接 key,應安全略過效果或使用遊戲定義的通用降級效果。只回傳已啟用且身份、價格完整的禮物;Production canonical gift event 仍是執行時事實來源。
結束時呼叫:
DELETE /api/game-client/v2/session
Authorization: Bearer <sessionAccessToken>
成功為 204 No Content,WebSocket 以 4002 CLIENT_LOGOUT 關閉;隨後清除密碼、Token 與 Ticket。排行榜必須使用獨立 credentials.gameApi.token,不能使用遊戲 access Token;完整合同請參閱開放排行榜 API。
#上線檢查清單
- 只連線平台公開 HTTPS/WSS 與 build 設定的可信獨立後台,不存取
/internal/*; - 驗證密碼登入、refresh、Ticket、
ready和事件結構; - 驗證獨立後台 HTTP Bearer、
nbt.auth、原連線續簽、到期與可信 URL 規則; - 憑證只在記憶體,日誌與遙測脫敏;
- refresh 序列執行並處理不確定網路結果;
- 每次重連取得新 Ticket、更新
leaseGeneration; comment、gift以持久化eventId冪等,失敗不得 ACK;- ACK 使用 25ms / 100 筆,單連線只有一個在途批次;
- Debug 區分
debug_command與 Productionevent; - 區分三個可重連碼與所有終止碼;
- 正常退出呼叫 DELETE 並清除記憶體憑證。