yikart/AiToEarn · error
Relay API error [${body.code}]: ${body.message}
Error message
Relay API error [${body.code}]: ${body.message} What it means
RelayMediaResolverService.post 是 Relay 媒体服务(uploadSign、confirm 等)的 HTTP 封装:当响应体是 RelayCommonResponse 且 code 非 0(非 null)时,以普通 Error 抛出 `Relay API error [code]: message`。表示 Relay 后端拒绝了请求(业务错误码)。
Source
Thrown at project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/relay-media/relay-media-resolver.service.ts:128
const cdnPrefix = this.assetsConfig?.cdnEndpoint?.replace(/\/+$/, '')
const endpointPrefix = this.assetsConfig?.endpoint?.replace(/\/+$/, '')
if (cdnPrefix && endpointPrefix && localUrl.startsWith(cdnPrefix)) {
const bucket = this.assetsConfig && 'bucketName' in this.assetsConfig
? String(this.assetsConfig.bucketName || '')
: ''
const internalBase = `${endpointPrefix}${bucket ? `/${bucket}` : ''}`
return localUrl.replace(cdnPrefix, internalBase)
}
return localUrl
}
private async post<T>(url: string, data: unknown): Promise<T> {
const response = await this.httpClient!.post<RelayCommonResponse<T> | T>(url, data)
const body = response.data
if (this.isCommonResponse(body)) {
if (body.code != null && body.code !== 0) {
throw new Error(`Relay API error [${body.code}]: ${body.message}`)
}
return body.data
}
return body
}
private isCommonResponse<T>(body: RelayCommonResponse<T> | T): body is RelayCommonResponse<T> {
return typeof body === 'object' && body !== null && 'data' in body
}
private async uploadFileFromLocalUrl(localUrl: string): Promise<string> {
const filename = basename(new URL(localUrl).pathname)
const fileResponse = await axios.get(localUrl, { responseType: 'arraybuffer' })
const contentType = fileResponse.headers['content-type'] || 'application/octet-stream'
const size = (fileResponse.data as ArrayBuffer).byteLength
View on GitHub (pinned to d3aa8bea5b)
Solutions
- 读取错误消息中的 [code],对照 Relay API 文档确定具体业务错误含义。
- 核对 RELAY_SERVER_URL 与 RELAY_API_KEY 是否来自同一环境(cn 域名 Key 不能配 ai 域名 URL)。
- 检查 uploadSign 请求的 filename/type/size 是否合法,size 是否与实际文件一致。
- 确认资产只 confirm 一次,且 uploadUrl 的 PUT 已成功。
Example fix
// before
const sign = await resolver.signResult({ filename, type, size }) // 内部 code!=0 直接抛 Error
// after
try {
const sign = await resolver.signResult({ filename, type, size })
} catch (e) {
const m = /^Relay API error \[(\d+)\]/.exec((e as Error).message)
if (m) {
logger.error({ relayCode: m[1] }, 'Relay rejected request; check RELAY_SERVER_URL/RELAY_API_KEY env match')
}
throw e
} Defensive patterns
Strategy: try-catch
Validate before calling
if (!process.env.RELAY_SERVER_URL || !process.env.RELAY_API_KEY) {
throw new Error('RELAY_SERVER_URL and RELAY_API_KEY are required')
}
// env must match key origin: *.aitoearn.cn keys only with cn URLs, *.aitoearn.ai keys only with ai URLs
const isCnKey = process.env.RELAY_API_KEY_PREFIX === 'cn'
const urlIsCn = process.env.RELAY_SERVER_URL.includes('aitoearn.cn')
if (isCnKey !== urlIsCn) throw new Error('Relay key/env mismatch') Type guard
function isRelayCommonResponse<T>(b: unknown): b is { code: number, message: string, data: T } {
return b != null && typeof b === 'object' && 'data' in b && typeof (b as any).code === 'number'
} Try / catch
try {
return await post(url, data)
} catch (e) {
const m = /^Relay API error \[(\d+)\]: (.*)$/.exec((e as Error).message)
if (m) {
const [, code, message] = m
if (code === '401' || code === '403') throw new Error('Check RELAY_API_KEY matches RELAY_SERVER_URL environment')
if (RETRYABLE_RELAY_CODES.includes(code)) return retryWithBackoff(() => post(url, data))
}
throw e
} Prevention
- 确保 RELAY_SERVER_URL 与 RELAY_API_KEY 属于同一环境(cn vs ai 域名)
- 上传前核对文件大小不超过 Relay 临时资产限制
- 避免对同一资产重复调用 confirm
- 解析错误消息中的 [code] 并按码分类处理,不要笼统重试
When it happens
Trigger: 调用 post 走 /api/assets/uploadSign 或 /api/assets/{id}/confirm 时,Relay 返回业务错误:文件大小超限、签名参数非法、资产 ID 不存在或已确认、鉴权/RELAY_API_KEY 无效导致业务拒绝。
Common situations: Relay 服务器地址或 Key 与环境不匹配(aitoearn.cn vs aitoearn.ai);上传文件超过临时资产大小限制;重复 confirm 同一资产;Relay 服务端版本与客户端约定的错误码结构不一致。
Related errors
- body.code
- AiCallFailed
- Relay uploadSign returned no uploadUrl: ${JSON.stringify(sig
- InvalidModel
- AiCallFailed
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/30c7cb65e3b93f2b.
Report an issue: GitHub.