yikart/AiToEarn · error · BadRequestException
交换令牌失败: ${data}
Error message
交换令牌失败: ${data} What it means
A BadRequestException raised inside exchangeCodeForTokens when TikTok's OAuth token endpoint responds with HTTP 200 but the JSON body contains an error field. The whole response body is stringified into the message, so the raw TikTok error payload (error, error_description, error_uri) is visible to the caller.
Source
Thrown at project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.auth.service.ts:280
grant_type: 'authorization_code',
redirect_uri: `${this.redirectUri}/api/plat/tiktok/auth/callback`,
// 添加PKCE code_verifier
// code_verifier: codeVerifier // Required for mobile and desktop app only.
});
// const base64Credentials = Buffer.from(`${this.clientId}:${this.clientSecret}`).toString('base64');
const { data } = await firstValueFrom(
this.httpService.post(this.tokenUrl, params.toString(), {
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
// 'Authorization': `Basic ${base64Credentials}`,
}
})
);
if (data.error) {
throw new BadRequestException(`交换令牌失败: ${data}`);
}
// return {
// access_token: data.access_token,
// refresh_token: data.refresh_token,
// expires_in: data.expires_in,
// token_type: data.token_type,
// scope: data.scope,
// open_id: data.open_id
// };
return data;
} catch (error) {
this.logger.error('交换TikTok授权码失败:', error);
throw new BadRequestException(`交换授权码失败: ${error.response?.data?.error_description || error.message}`);
}
}
/**View on GitHub (pinned to d3aa8bea5b)
Solutions
- Inspect the stringified data in the message — TikTok's error/error_description pinpoints the rejected parameter.
- Verify redirect_uri sent in the token exchange is identical to the one used in the authorize URL and registered on the TikTok app.
- Check TIKTOK_CLIENT_KEY / TIKTOK_CLIENT_SECRET for the correct environment (aitoearn.cn vs aitoearn.ai).
- Retry the full OAuth flow with a fresh code; never reuse a consumed authorization code.
Example fix
// before
if (data.error) {
throw new BadRequestException(`交换令牌失败: ${data}`);
}
// after
if (data.error) {
this.logger.error(`TikTok token exchange rejected: ${data.error} - ${data.error_description}`);
throw new BadRequestException(`交换令牌失败: ${data.error} (${data.error_description})`);
} Defensive patterns
Strategy: validation
Validate before calling
function assertTokenExchangeParams(p: { clientKey: string; clientSecret: string; code: string; redirectUri: string }) {
if (!p.clientKey || !p.clientSecret) throw new Error('TikTok client credentials missing');
if (!p.code) throw new Error('Authorization code missing');
if (!p.redirectUri) throw new Error('redirect_uri missing');
} Type guard
function isTokenError(data: unknown): data is { error: string; error_description?: string } {
return typeof data === 'object' && data !== null && typeof (data as any).error === 'string';
} Try / catch
try {
await api.exchangeTikTokCode(code);
} catch (e) {
if (isTokenError((e as any).response?.data)) {
const { error, error_description } = (e as any).response.data;
if (error === 'invalid_grant') restartOAuthFlow();
}
throw e;
} Prevention
- Compare redirect_uri in the token request byte-for-byte with the authorize URL and app registration.
- Validate client_key/client_secret at startup, not at request time.
- Consume the code exactly once, immediately after redirect.
- Log the full TikTok error body before wrapping.
When it happens
Trigger: POST to https://open.tiktokapis.com/v2/oauth/token/ returns { error: ... } because client_key/client_secret is wrong, redirect_uri doesn't match the registered one, the code was already consumed or expired, or grant_type/parameters are malformed.
Common situations: Wrong redirect_uri (must match byte-for-byte), swapped client key/secret, code re-use after page refresh of the callback URL, using a CN-environment key against the international endpoint or vice versa.
Related errors
- No response from Gemini
- userId是必需的
- 授权TikTok账户失败: ${error.response?.data?.error_description || e
- 交换授权码失败: ${error.response?.data?.error_description || error.
- ChannelAccessTokenFailed
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/9c01723995c2bcac.
Report an issue: GitHub.