{"record":{"id":"4f80c87a55c0d5e1","repo":"yikart/AiToEarn","slug":"15070","errorCode":"15070","errorMessage":"{{platform}} platform API request failed","messagePattern":"(.+?)\\} platform API request failed","errorType":"exception","errorClass":"BilibiliPlatformException","httpStatus":null,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/bilibili/bilibili.service.ts","lineNumber":50,"sourceCode":"    private readonly mediaService: MediaService,\n  ) {\n    this.platformHttp = this.createPlatformHttpClient()\n  }\n\n  private readonly authPageUrl = 'https://account.bilibili.com/pc/account-pc/auth/oauth'\n  private readonly openBaseUrl = 'https://member.bilibili.com'\n\n  private createPlatformHttpClient(): AxiosInstance {\n    const http = axios.create()\n    http.interceptors.response.use(\n      (response) => {\n        if (BilibiliPlatformException.hasPlatformError(response)) {\n          throw BilibiliPlatformException.fromPlatformResponse(response)\n        }\n        return response\n      },\n      (error: AxiosError<BilibiliPlatformResponseBody>) => {\n        throw BilibiliPlatformException.fromAxiosError(error)\n      },\n    )\n    return http\n  }\n\n  generateAuthUrl(state: string): string {\n    const params = new URLSearchParams({\n      client_id: this.cfg.clientId,\n      gourl: this.cfg.redirectUri,\n      state,\n    })\n\n    return `${this.authPageUrl}?${params.toString()}`\n  }\n\n  async exchangeCode(code: string): Promise<{\n    accessToken: string\n    refreshToken: string","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-server/src/core/channels/platforms/bilibili/bilibili.service.ts#L32-L68","documentation":"BilibiliService builds its Axios instance in createPlatformHttpClient with response interceptors that normalize every failure into a BilibiliPlatformException (a ChannelPlatformException) whose user-facing template is '{{platform}} platform API request failed' (code 15070). fromAxiosError fires for network-level failures (timeout, DNS, connection refused) and HTTP error statuses on calls to Bilibili open APIs; fromPlatformResponse fires when Bilibili returns HTTP 200 but a body code other than 0. The exception carries httpStatus, platformCode, endpoint context, a retryable flag, and a category so callers can classify it.","triggerScenarios":"Any axios request through this client whose promise rejects (network error, timeout, 4xx/5xx status), or where Bilibili responds with a non-zero body `code` (e.g. -101 invalid access_token, -352 risk control); typical call sites are OAuth token exchange, auth-url flows, and content publishing via the interceptor-wrapped http instance.","commonSituations":"Expired or revoked Bilibili access tokens bound to the connected account; Bilibili API downtime or 5xx during publishes; hitting Bilibili rate limits/risk control (-352, -412); misconfigured Bilibili app credentials causing 4xx on token endpoints; container DNS/proxy issues blocking outbound calls.","solutions":["Read exception.cause.httpStatus and cause.platformCode: 401/-101 style codes mean reconnect the Bilibili account (refresh tokens via the channel service) rather than retrying.","If retryable is true (5xx per isHttpStatusRetryable, or no response = network error), retry the request with backoff.","Check the endpoint in exception.context to identify which Bilibili API failed and verify app credentials/env config for that endpoint.","Confirm outbound network access (proxy, DNS, firewall) from the server to the Bilibili open API; reproduce with curl from the same container."],"exampleFix":"// before: treating every platform failure the same\ntry {\n  await bilibiliService.publish(account, payload)\n} catch (e) {\n  this.logger.error('publish failed')\n}\n\n// after: branch on the classified ChannelPlatformException\ntry {\n  await bilibiliService.publish(account, payload)\n} catch (e) {\n  if (e instanceof ChannelPlatformException) {\n    if (e.retryable) return this.retryWithBackoff(e)\n    if (e.cause?.httpStatus === 401) return this.markAccountReauthRequired(account)\n  }\n  throw e\n}","handlingStrategy":"try-catch","validationCode":"// before calling: fail fast on missing credentials and check account token presence\nif (!config.bilibiliAppKey || !config.bilibiliAppSecret) {\n  throw new Error('BILIBILI app credentials are not configured')\n}\nif (!account.accessToken) {\n  throw new Error(`Bilibili account ${account.id} has no access token; reconnect required`)\n}","typeGuard":"import { ChannelPlatformException } from '../channels/platforms/platforms.exception'\n\nfunction isBilibiliPlatformError(e: unknown): e is ChannelPlatformException {\n  return e instanceof ChannelPlatformException\n    && e.platform === 'bilibili'\n    && (e.cause?.httpStatus !== undefined || e.cause?.platformCode !== undefined)\n}","tryCatchPattern":"try {\n  return await bilibiliService.publish(account, payload)\n} catch (e) {\n  if (isBilibiliPlatformError(e)) {\n    if (e.retryable) return retryWithBackoff(e)\n    if (e.cause.httpStatus === 401) return markAccountNeedsReauth(account.id)\n  }\n  throw e\n}","preventionTips":["Always branch on ChannelPlatformException's retryable flag and cause.httpStatus instead of catching bare Error.","Refresh/validate Bilibili tokens before long publish jobs; proactively reconnect accounts with near-expiry tokens.","Add alerting on PlatformErrorCategory.PlatformUnavailable so Bilibili 5xx incidents trigger backoff, not error storms.","Smoke-test outbound connectivity to the Bilibili open API in deployment health checks.","Regenerate one-time request payloads before each retry instead of reusing them."],"tags":["bilibili","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"}