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: stringView on GitHub (pinned to d3aa8bea5b)
Solutions
- Restart the OAuth flow: generate a fresh authorization code and exchange it immediately, once.
- Verify clientId/clientSecret and redirect_uri exactly match the Bilibili open-platform app config.
- Inspect Bilibili's response body/logs (message/code fields) for the platform-side rejection reason.
- 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
- Exchange the code immediately in the callback; never reuse it
- Keep clientId/clientSecret/redirect_uri identical across authorize and token requests
- Log Bilibili's raw response body for the platform-side error message
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
- ChannelAuthRefreshTokenMissing
- ChannelRefreshTokenFailed
- ChannelAccessTokenFailed
- ChannelAuthRefreshTokenMissing
- ChannelAuthRefreshTokenMissing
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/5c46603d423be569.
Report an issue: GitHub.