{"record":{"id":"c5835225364a3b7f","repo":"yikart/AiToEarn","slug":"15070-c58352","errorCode":"15070","errorMessage":"{{platform}} platform API request failed","messagePattern":"(.+?)\\} platform API request failed","errorType":"exception","errorClass":"DouyinPlatformException","httpStatus":null,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/douyin-miniapp.service.ts","lineNumber":311,"sourceCode":"    for (const record of orderedRecords) {\n      if (record.total_fans !== undefined) {\n        return Number(record.total_fans)\n      }\n    }\n    return undefined\n  }\n\n  private createHttpClient(): AxiosInstance {\n    const http = axios.create({ timeout: 10000 })\n    http.interceptors.response.use(\n      (response) => {\n        if (DouyinPlatformException.hasPlatformError(response)) {\n          throw DouyinPlatformException.fromPlatformResponse(response)\n        }\n        return response\n      },\n      (error: AxiosError<DouyinPlatformResponseBody>) => {\n        throw DouyinPlatformException.fromAxiosError(error)\n      },\n    )\n    return http\n  }\n\n  private get miniAppConfig(): DouyinMiniAppConfigValue {\n    const miniApp = this.cfg.miniApp\n    if (!miniApp?.clientId || !miniApp.clientSecret) {\n      throw new AppException(ResponseCode.ChannelPlatformApiFailed, { platform: AccountType.Douyin, reasonCode: 'missing_miniapp_config' })\n    }\n\n    return miniApp\n  }\n\n  private get endpoints() {\n    return this.miniAppConfig.sandbox\n      ? DOUYIN_MINIAPP_ENDPOINTS.sandbox\n      : DOUYIN_MINIAPP_ENDPOINTS.official","sourceCodeStart":293,"sourceCodeEnd":329,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/douyin/douyin-miniapp.service.ts#L293-L329","documentation":"DouyinMiniAppService builds its Axios client in createHttpClient with response interceptors that convert any failure into a DouyinPlatformException (ChannelPlatformException) with the '{{platform}} platform API request failed' message (code 15070). fromAxiosError handles axios rejections (network errors, 4xx/5xx) and fromPlatformResponse handles HTTP 200 bodies carrying a Douyin error code (error_code/err_no/extra.error_code). The exception records endpoint, httpStatus, platformCode, retryability, and a category, preserving the raw Douyin failure for callers.","triggerScenarios":"Any request through this mini-app client to Douyin open-platform endpoints (jscode2session code exchange, token endpoints, mini-app publishing APIs) that gets a non-2xx HTTP status, times out, fails DNS/connection, or returns 200 with a non-zero error_code such as invalid js_code, expired access_token, or appid/secret mismatch.","commonSituations":"Wrong Douyin mini-app appid/secret in env config making code2session calls fail; the user's login code already consumed or expired (js_code is single-use); access_token cache expired mid-call; Douyin platform 5xx incidents; server egress blocked so axios rejects without a response.","solutions":["Inspect exception.cause.platformCode and platformMessage: Douyin-specific codes (e.g. invalid code, invalid appid) indicate config or expired login-code problems — fix appid/secret env values or have the client fetch a fresh js_code.","If retryable is true (5xx or network-level error), retry with backoff; token-expiry codes should trigger token refresh, not blind retry.","Use exception.context.endpoint to identify which mini-app API failed and validate that endpoint's required params.","Verify outbound connectivity from the server to Douyin open-platform domains; reproduce with curl from the same host."],"exampleFix":"// before: blind retry burns the single-use js_code\ntry {\n  session = await miniAppService.code2session(code)\n} catch {\n  return this.retryWithBackoff(code)\n}\n\n// after: only retry when the exception is classified retryable\ntry {\n  session = await miniAppService.code2session(code)\n} catch (e) {\n  if (e instanceof ChannelPlatformException && e.retryable) {\n    return this.retryWithBackoff(e)\n  }\n  throw e // e.g. invalid js_code / appid — needs new code or config fix\n}","handlingStrategy":"try-catch","validationCode":"// validate js_code before exchange and config before boot\nif (!code || code.length < 16) {\n  throw new Error('miniapp login code missing or malformed')\n}\nif (!config.douyinMiniAppAppId || !config.douyinMiniAppSecret) {\n  throw new Error('Douyin mini-app appid/secret not configured')\n}","typeGuard":"import { ChannelPlatformException } from '../channels/platforms/platforms.exception'\n\nfunction isDouyinPlatformError(e: unknown): e is ChannelPlatformException {\n  return e instanceof ChannelPlatformException\n    && e.platform === 'douyin'\n    && (e.cause?.platformCode !== undefined || typeof e.cause?.httpStatus === 'number')\n}","tryCatchPattern":"try {\n  session = await douyinMiniAppService.code2session(code)\n} catch (e) {\n  if (isDouyinPlatformError(e) && e.retryable) {\n    return retryWithBackoff(e)\n  }\n  throw e // invalid js_code/appid errors are not retryable\n}","preventionTips":["Remember js_code is single-use and short-lived: never retry code2session with the same code; get a fresh one from the client.","Keep mini-app appid/secret in validated config; fail at boot, not at first login.","Cache access_token with expiry tracking and refresh ahead of calls instead of hitting token endpoints repeatedly.","Verify outbound connectivity to Douyin open-platform domains in deploy health checks.","Branch on cause.platformCode to distinguish config errors (appid/secret) from user-flow errors (expired code)."],"tags":["douyin","miniapp","api","http","third-party","axios"],"backgroundTag":"platform-api-request-failed","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}