NBTiktokDOCS / 开发文档

v2 · 开发者指南

DOC / 01

交互游戏接入 API

邮箱密码登录、客户端 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、房间、模式、版本策略、期限与路径均为权威数据。启用独立后台时 backendAuth 为对象且提供 credentials.gameBackend;未启用时 backendAuthnull 且省略该凭证。密码、Token 与 Ticket 不得写入 PlayerPrefs、配置文件、日志、URL、崩溃报告或遥测。

session access Token 最长 30 分钟,Game API Token 最长 10 分钟,独立后台 JWT 最长 30 分钟。由于 refreshAt 取所有短期凭证刷新点的最早值,整组凭证通常仍在签发后约 8 分钟 refresh。refresh Token 仅可使用一次,Session 滑动期限最长 24 小时,absoluteExpiresAt 最晚为登录后 7 天;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 登录错误仍使用扁平响应:{ "error": "CLIENT_BUILD_UNSUPPORTED" };客户端升级到当前 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,连接后 5 秒内只在首个 {"type":"nbt.auth","token":"..."} 文本帧发送同一 JWT,绝不写入 URL、Cookie 或子协议。平台 Session refresh 返回新 Backend JWT 后,在原连接再次发送同一认证帧完成续签;若不再返回 gameBackend,立即关闭第三方连接。

第三方后台 WSS 与平台 /ws/game-client/v2 完全独立:前者使用 Backend JWT,后者继续使用一次性 credentials.websocket.ticket。可信构建分别配置 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

#已退役端点

POST /api/game-client/v2/device-authorizationsPOST /api/game-client/v2/device-authorizations/tokenGET /api/game-client/v2/captcha 已退役,分别固定返回 410 DEVICE_AUTHORIZATION_REMOVED410 CAPTCHA_LOGIN_REMOVED。新客户端只能使用 Email 密码登录,不得回退到设备码或 CAPTCHA 流程。

#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、权威 backendAuthcredentials.sessionAccesscredentials.gameApi、可选的 credentials.gameBackend 与一次性 credentials.refresh,但不含 websocket。整组凭证需原子替换,停止使用旧 Token 发起 HTTP,以新 Backend JWT 在原第三方 WSS 连接发送 nbt.auth 续签,并丢弃未使用的平台 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": "cf8cbe61-95c3-45f8-a883-2555e99d408f",
  "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.lengthconsumed <= 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管理或发布下线显示原因并视情况重新登录
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;若事件的 providerGiftId 不在目录中,不得猜测或拼接 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,不能使用 Session access Token;完整协议请参阅开放积分榜 API

#上线检查清单

  • 仅连接平台公开 HTTPS/WSS 与构建配置中的可信独立后台,不访问 /internal/*
  • 验证密码登录、refresh、Ticket、ready 和事件结构;
  • 验证独立后台 HTTP Bearer、nbt.auth、原连接续签、到期与可信 URL 规则;
  • 凭证只在内存,日志与遥测脱敏;
  • refresh 串行执行并处理不确定网络结果;
  • 每次重连获取新 Ticket、更新 leaseGeneration
  • commentgift 以持久化 eventId 幂等,失败不得 ACK;
  • ACK 使用 25ms / 100 条,单连接只有一个在途批次;
  • Debug 区分 debug_command 与 Production event
  • 区分三个可重连码与所有终止码;
  • 正常退出时调用 DELETE 并清除内存凭证。