{"record":{"id":"e7797d552c338917","repo":"yikart/AiToEarn","slug":"body-code","errorCode":"body.code","errorMessage":"Relay API request failed","messagePattern":"Relay API request failed","errorType":"error_code","errorClass":"AppException","httpStatus":400,"severity":"error","filePath":"project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/libs/relay/relay.service.ts","lineNumber":58,"sourceCode":"    const data = error.response?.data as Record<string, unknown> | undefined\n    const message = (data?.['message'] as string) || (data?.['error'] as string) || error.message\n    error.message = message\n    error.name = status ? `RelayApiError(${status})` : 'RelayApiError'\n    return error\n  }\n\n  private normalizeResponse<T>(response: AxiosResponse<RelayCommonResponse<T> | T>): AxiosResponse<T> {\n    response.data = this.unwrapResponse(response.data)\n    return response as AxiosResponse<T>\n  }\n\n  private unwrapResponse<T>(body: RelayCommonResponse<T> | T): T {\n    if (!this.isCommonResponse(body)) {\n      return body as T\n    }\n\n    if (body.code != null && body.code !== ResponseCode.Success) {\n      throw new AppException(body.code, body.message ?? 'Relay API request failed')\n    }\n\n    return body.data as T\n  }\n\n  private isCommonResponse<T>(body: RelayCommonResponse<T> | T): body is RelayCommonResponse<T> {\n    return typeof body === 'object'\n      && body !== null\n      && 'data' in body\n  }\n\n  private stringifyForError(value: unknown): string {\n    try {\n      return JSON.stringify(value)\n    }\n    catch {\n      return String(value)\n    }","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/yikart/AiToEarn/blob/d3aa8bea5b146a8675607cf0144d891aad3e9683/project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/libs/relay/relay.service.ts#L40-L76","documentation":"unwrapResponse normalizes Relay API responses: if the body is a RelayCommonResponse envelope and carries a non-success code, it throws AppException(body.code, body.message ?? 'Relay API request failed'). The message shown ('Relay API request failed') is the fallback used when the upstream body has a failure code but no message — the real information is in the exception's code (body.code).","triggerScenarios":"The relay upstream (Relay server /api/ai/...) responds with its common error envelope: invalid RELAY_API_KEY, insufficient quota/balance, upstream provider error proxied through, or bad request params rejected by the relay. Any call flowing through normalizeResponse can surface it.","commonSituations":"Key/env mismatch (China aitoearn.cn key used against aitoearn.ai relay or vice versa → 401-style codes); exhausted credit; relay forwarding a provider-side failure; deprecated endpoint params.","solutions":["Read the AppException code (body.code) to identify the specific relay failure; handle by code, not the generic message","Verify RELAY_API_KEY matches the RELAY_SERVER_URL environment (aitoearn.cn key ↔ https://aitoearn.cn/api, aitoearn.ai key ↔ https://aitoearn.ai/api)","Check relay account balance/quota in the corresponding environment","Log the full relay response body to capture body.message when present"],"exampleFix":"// before\nconst result = await relayService.normalizeResponse(...)\n// after\ntry {\n  const result = await relayService.normalizeResponse(...)\n} catch (e) {\n  if (e instanceof AppException && e.code === ResponseCode.Unauthorized) {\n    throw new UnauthorizedException('Relay API key invalid or env mismatch (cn vs ai)')\n  }\n  throw e\n}","handlingStrategy":"try-catch","validationCode":"function relayEnvMatches(apiKey: string, serverUrl: string): boolean {\n  const isCn = serverUrl.includes('aitoearn.cn')\n  const keyLooksCn = /* per deployment convention */ true\n  return isCn === keyLooksCn // cn keys must pair with aitoearn.cn, intl keys with aitoearn.ai\n}","typeGuard":"function isRelayError(e: unknown): e is AppException {\n  return e instanceof AppException && e.code !== ResponseCode.Success\n}","tryCatchPattern":"try {\n  const data = await relayService.normalizeResponse(body)\n} catch (e) {\n  if (e instanceof AppException) {\n    logger.error({ relayCode: e.code, relayMessage: e.message }, 'relay failure')\n    throw new BadGatewayException(`Relay error ${e.code}: ${e.message}`)\n  }\n  throw e\n}","preventionTips":["Always match RELAY_API_KEY environment with RELAY_SERVER_URL (cn ↔ aitoearn.cn, ai ↔ aitoearn.ai)","Switch on the relay code, not the fallback message, to decide retries","Monitor relay balance/quota proactively","Log full relay bodies to capture body.message when present"],"tags":["relay","api","error-handling"],"backgroundTag":"relay-api-error","analyzedSha":"d3aa8bea5b146a8675607cf0144d891aad3e9683","analyzedAt":"2026-08-31T14:19:24.185Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}