yikart/AiToEarn · error · BadRequestException

交换授权码失败: ${error.response?.data?.error_description || error.

Error message

交换授权码失败: ${error.response?.data?.error_description || error.message}

What it means

A BadRequestException thrown by exchangeCodeForTokens' outer catch. When the axios call to TikTok's token endpoint throws (network error or non-2xx status), the handler prefers TikTok's error_description from the response body, falling back to error.message, and wraps it in a Chinese-language message.

Source

Thrown at project/aitoearn-electron/server/src/modules/plat/tiktok/tiktok.auth.service.ts:294

        })
      );

      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}`);
    }
  }

  /**
   * 获取TikTok用户资料
   * @param accessToken 访问令牌
   * @param openId 用户开放ID
   * @returns TikTok用户资料
   */
  async getTikTokUserProfile(accessToken: string, openId: string): Promise<TikTokUser> {
    try {
      const { data } = await firstValueFrom(
        this.httpService.get(`${this.apiBaseUrl}/v2/user/info/`, {
          params: {
            fields: 'open_id,union_id,avatar_url,bio_description,profile_deep_link,is_verified,follower_count,following_count,likes_count,video_count,username, display_name',
            // open_id: openId
          },
          headers: {

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Read error_description in the message for TikTok's own explanation; fix the named parameter.
  2. Confirm outbound HTTPS connectivity to open.tiktokapis.com from the server (proxy/firewall rules).
  3. Re-check client_key, client_secret, code, grant_type=authorization_code and redirect_uri in the request body.
  4. Restart the OAuth flow to get a fresh single-use code.

Example fix

// before
throw new BadRequestException(`交换授权码失败: ${error.response?.data?.error_description || error.message}`);
// after
const desc = error.response?.data?.error_description || error.message;
this.logger.error(`TikTok token endpoint failure (status=${error.response?.status}): ${desc}`);
throw new BadRequestException(`交换授权码失败: ${desc}`);
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight connectivity check at service startup
const res = await fetch('https://open.tiktokapis.com/v2/oauth/token/', { method: 'HEAD' }).catch(() => null);
if (!res) throw new Error('TikTok token endpoint unreachable from this host');

Type guard

function isAxiosLikeError(e: unknown): e is { response?: { status: number; data: { error_description?: string } }; code?: string; message: string } {
  return typeof e === 'object' && e !== null && ('response' in e || 'code' in e);
}

Try / catch

try {
  await api.exchangeTikTokCode(code);
} catch (e) {
  if (!(e as any).response) {
    // transport-level failure: retry with backoff
    await sleep(1000);
    return retryExchange(code);
  }
  throw new Error(`交换授权码失败: ${(e as any).response?.data?.error_description ?? (e as Error).message}`);
}

Prevention

When it happens

Trigger: The POST to TikTok's oauth/token endpoint fails at the transport level (DNS, timeout, TLS) or returns 400/401/403 (bad client credentials, invalid code, mismatched redirect_uri), and the axios promise rejects instead of returning a body with error.

Common situations: Network egress blocked from the server to open.tiktokapis.com, expired/rotated client secret, user refreshed the callback page causing code reuse, missing client_key parameter in the form body.

Related errors


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