yikart/AiToEarn · error · BilibiliPlatformException

ChannelAccessTokenFailed

ChannelAccessTokenFailed

Error message

ResponseCode.ChannelAccessTokenFailed

What it means

BilibiliService.exchangeCode swaps the OAuth authorization code for tokens at api.bilibili.com/x/account-oauth2/v1/token. If the response has no data payload, it throws BilibiliPlatformException with code ChannelAccessTokenFailed, category Auth, cause Platform — i.e. Bilibili did not return tokens.

Source

Thrown at project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/bilibili/bilibili.service.ts:86

    refreshToken: string
    expiresAt: Date
  }> {
    const body = new URLSearchParams({
      client_id: this.cfg.clientId,
      client_secret: this.cfg.clientSecret,
      grant_type: BilibiliOAuthGrantType.AuthorizationCode,
      code,
    })
    const response = await this.platformHttp.post<BilibiliApiResponse<{
      access_token: string
      refresh_token: string
      expires_in: number
    }>>('https://api.bilibili.com/x/account-oauth2/v1/token', body.toString(), {
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    })

    if (!response.data.data) {
      throw new BilibiliPlatformException({
        code: ResponseCode.ChannelAccessTokenFailed,
        category: PlatformErrorCategory.Auth,
        context: { endpoint: 'POST https://api.bilibili.com/x/account-oauth2/v1/token' },
        cause: { type: PlatformErrorCauseType.Platform },
      })
    }

    const { access_token, refresh_token, expires_in } = response.data.data

    return {
      accessToken: access_token,
      refreshToken: refresh_token,
      expiresAt: new Date(Date.now() + expires_in * 1000),
    }
  }

  async refreshAccessToken(refreshToken: string): Promise<{
    accessToken: string

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Restart the OAuth flow: generate a fresh authorization code and exchange it immediately, once.
  2. Verify clientId/clientSecret and redirect_uri exactly match the Bilibili open-platform app config.
  3. Inspect Bilibili's response body/logs (message/code fields) for the platform-side rejection reason.
  4. Handle ChannelAccessTokenFailed by redirecting the user to re-authorize rather than retrying the same code.

Example fix

// before
// reusing a stored code
await bilibiliService.exchangeCode(oldCode)

// after
// always exchange a fresh code from the callback
const result = await bilibiliService.exchangeCode(query.code) // one-time use
Defensive patterns

Strategy: try-catch

Validate before calling

if (!query.code) return redirect('/auth/bilibili') // no code at all
// codes are single-use and short-lived: never cache or replay them

Try / catch

try {
  tokens = await bilibiliService.exchangeCode(code)
} catch (e) {
  if (e.code === 'ChannelAccessTokenFailed') {
    // invalid/expired/replayed code or bad app credentials
    return redirect('/auth/bilibili?restart=1') // force fresh OAuth flow
  }
  throw e
}

Prevention

When it happens

Trigger: OAuth callback invokes exchangeCode and Bilibili's token endpoint responds with success envelope but empty/missing data — invalid or expired authorization code, code already redeemed, mismatched clientId/clientSecret, or redirect URI mismatch.

Common situations: User retries the callback URL (code single-use); app credentials changed in Bilibili open platform; redirect_uri differs between authorize and token requests; clock drift or expired code due to slow processing.

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/5c46603d423be569. Report an issue: GitHub.