yikart/AiToEarn · error · AppException
body.code
body.code
Error message
Relay API request failed
What it means
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).
Source
Thrown at project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/libs/relay/relay.service.ts:58
const data = error.response?.data as Record<string, unknown> | undefined
const message = (data?.['message'] as string) || (data?.['error'] as string) || error.message
error.message = message
error.name = status ? `RelayApiError(${status})` : 'RelayApiError'
return error
}
private normalizeResponse<T>(response: AxiosResponse<RelayCommonResponse<T> | T>): AxiosResponse<T> {
response.data = this.unwrapResponse(response.data)
return response as AxiosResponse<T>
}
private unwrapResponse<T>(body: RelayCommonResponse<T> | T): T {
if (!this.isCommonResponse(body)) {
return body as T
}
if (body.code != null && body.code !== ResponseCode.Success) {
throw new AppException(body.code, body.message ?? 'Relay API request failed')
}
return body.data as T
}
private isCommonResponse<T>(body: RelayCommonResponse<T> | T): body is RelayCommonResponse<T> {
return typeof body === 'object'
&& body !== null
&& 'data' in body
}
private stringifyForError(value: unknown): string {
try {
return JSON.stringify(value)
}
catch {
return String(value)
}View on GitHub (pinned to d3aa8bea5b)
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
Example fix
// before
const result = await relayService.normalizeResponse(...)
// after
try {
const result = await relayService.normalizeResponse(...)
} catch (e) {
if (e instanceof AppException && e.code === ResponseCode.Unauthorized) {
throw new UnauthorizedException('Relay API key invalid or env mismatch (cn vs ai)')
}
throw e
} Defensive patterns
Strategy: try-catch
Validate before calling
function relayEnvMatches(apiKey: string, serverUrl: string): boolean {
const isCn = serverUrl.includes('aitoearn.cn')
const keyLooksCn = /* per deployment convention */ true
return isCn === keyLooksCn // cn keys must pair with aitoearn.cn, intl keys with aitoearn.ai
} Type guard
function isRelayError(e: unknown): e is AppException {
return e instanceof AppException && e.code !== ResponseCode.Success
} Try / catch
try {
const data = await relayService.normalizeResponse(body)
} catch (e) {
if (e instanceof AppException) {
logger.error({ relayCode: e.code, relayMessage: e.message }, 'relay failure')
throw new BadGatewayException(`Relay error ${e.code}: ${e.message}`)
}
throw e
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- AiCallFailed
- AiCallFailed
- Relay API error [${body.code}]: ${body.message}
- Relay uploadSign returned no uploadUrl: ${JSON.stringify(sig
- InvalidModel
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/e7797d552c338917.
Report an issue: GitHub.