NBTiktokDOCS / 開發文件

v2 · 開發者指南

DOC / 01

互動遊戲串接 API

Email 密碼登入、用戶端 Session、WebSocket 標準事件、可靠 ACK 與 Debug 測試。

本 API 供遊戲用戶端以 Email 與密碼建立 Session、選擇性登入獨立遊戲業務後台、透過 WebSocket 接收直播互動事件,以及在 Debug 模式測試相同流程。

請只使用 NBTiktok 提供的公開 HTTPS Origin;不得直接連線容器、診斷埠、/internal/* 或管理路由。

#串接流程

  1. 呼叫 POST /api/game-client/v2/login,提交 Email、密碼、gameIdmodeclientBuild;不使用 CAPTCHA。
  2. 直接串接排行榜或玩家屬性時不送 backend,回應提供 credentials.gameApi
  3. 啟用獨立後台時,登入回傳 backendAuthcredentials.gameBackend;URL 由用戶端可信設定選擇。
  4. credentials.gameBackend 只能傳到目前 build 選定的可信 URL。
  5. 使用一次性 WebSocket Ticket 連線並驗證 ready;Production 持久化後 ACK,Debug 回覆 debug_command
  6. refreshAt 輪換整組憑證並續簽獨立後台 WSS;平台事件連線斷線時申請新 Ticket,最後以 DELETE /api/game-client/v2/session 結束 Session。

#公開路由

能力Method / Path驗證
登入並建立 SessionPOST /api/game-client/v2/login僅 Email、密碼、遊戲、模式與 build
已移除的裝置授權POST /api/game-client/v2/device-authorizations/token回傳 410 DEVICE_AUTHORIZATION_REMOVED
已移除的 CAPTCHAGET /api/game-client/v2/captcha回傳 410 CAPTCHA_LOGIN_REMOVED
查詢 SessionGET /api/game-client/v2/sessionBearer <sessionAccessToken>
輪換憑證POST /api/game-client/v2/session/refreshBearer <refreshToken>;空本文
申請 WebSocket TicketPOST /api/game-client/v2/session/websocket-ticketBearer <sessionAccessToken>;空本文
登出DELETE /api/game-client/v2/sessionBearer <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用戶端可信 URLHTTP 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 建立錯誤

HTTPerror處理
400MODE_INVALID修正 mode
400CLIENT_BUILD_INVALID提交至少為 1 的安全整數
401INVALID_CREDENTIALSEmail 不存在、密碼錯誤或帳號暫時鎖定;顯示一致訊息
403DEALER_UNAVAILABLE檢查經銷商與成員狀態
403GAME_DISABLED停止登入已停用遊戲
403GAME_NOT_AUTHORIZED取得有效遊戲授權
404GAME_NOT_FOUND修正 gameId
409ROOM_ID_REQUIRED在個人資料填寫直播間 ID
409PROFILE_INCOMPLETE補齊區域資料
409GIFT_CREDIT_EXHAUSTED恢復禮物額度後再登入
409BILLING_CONFIG_REQUIRED等待 Production 計費設定完成
426CLIENT_BUILD_UNSUPPORTED升級至回應的 minimumClientBuild
429LOGIN_RATE_LIMITED / RATE_LIMITED遵循 Retry-After 並退避
503AUTH_UNAVAILABLE / BILLING_BACKLOG_PAUSED指數退避並提示使用者

426 回應使用 V2 envelope:error.codeCLIENT_BUILD_UNSUPPORTEDerror.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_URLNBT_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
}

逐項比對 sessionIdstreamerIdauthorizationIdgameIdmoderoomIdclientBuildminimumClientBuild,並要求 userId 是 UUID、leaseGeneration 是正整數。任何不符都拒絕連線。所有事件與 ACK 佇列要綁定 gameIdleaseGeneration;舊連線狀態不得重用。心跳使用 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;gameIdleaseGeneration 僅存在外層 envelope。eventType 包含 commentlikegiftstream_end。留言使用 payload.playercontent;按讚使用 payload.playercount;禮物使用上述巢狀 playergift

[
  {
    "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 是本事件增量;遊戲發獎應使用 deltaCountgroupId 可為 null,repeatEnd 表示連擊結束。unitDiamondCount 可為 0 且 missingPrice: true;遊戲不得從事件推斷平台扣費,也不能把 ack_result.consumed 當成鑽石數。

只有 commentgift 持久化重播並需要 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 包含 commandIdrequiresAck: truetransient: true,以及具有 eventIdeventTypeoccurredAtpayload 的完整 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"statusprocessedfailed。Debug 用戶端不得傳 Production pullack

#錯誤、禮物目錄與登出

協定錯誤格式是 { "type": "error", "error": "ERROR_CODE" }SESSION_FENCE_STALEACK_EVENT_INVALIDACK_FAILED 或 Debug 圍欄錯誤都應斷線並由持久化狀態恢復。

關閉碼原因是否續用 Session行為
4001SESSION_REPLACED停止舊用戶端並清除憑證
4002CLIENT_LOGOUT正常退出
4003SESSION_INVALID / SESSION_EXPIRED / SESSION_REAUTH_REQUIRED重新登入
4004AUTHORIZATION_INVALID / AUTHORIZATION_EXPIRED修復授權後登入
4005GAME_DELETED / GAME_DISABLED停止遊戲
4006CLIENT_BUILD_UNSUPPORTED升級用戶端
4007STREAM_ENDED結束本場 Production
4008SOURCE_BINDING_CHANGED更新個人資料後登入
4009管理或發布下線顯示 reason 並視情況登入
4010SLOW_CONSUMER降低阻塞,退避重連
4011SOURCE_RELEASED退避後申請新 Ticket
4012BALANCE_EXHAUSTED清除憑證並補充額度
4013HEARTBEAT_TIMEOUT檢查網路後退避重連
4014REFRESH_TOKEN_REUSED清除全部憑證再登入

只有 401040114013 可用有上限的指數退避加隨機抖動重連;其他皆為 Session 終止。

禮物目錄使用 GET /api/games/v2/game_one/gifts,無需驗證。回應含 game.gameIdgame.displayName,每個有效禮物含必填 keycatalogGiftIdproviderGiftIddisplayNamediamondCount、可為 null 的 iconUrlkey 是當前遊戲內唯一的效果觸發識別,不同遊戲可重複使用;載入或更新目錄時應建立 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
  • commentgift 以持久化 eventId 冪等,失敗不得 ACK;
  • ACK 使用 25ms / 100 筆,單連線只有一個在途批次;
  • Debug 區分 debug_command 與 Production event
  • 區分三個可重連碼與所有終止碼;
  • 正常退出呼叫 DELETE 並清除記憶體憑證。