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

  1. 读取错误消息中的 [code],对照 Relay API 文档确定具体业务错误含义。
  2. 核对 RELAY_SERVER_URL 与 RELAY_API_KEY 是否来自同一环境(cn 域名 Key 不能配 ai 域名 URL)。
  3. 检查 uploadSign 请求的 filename/type/size 是否合法,size 是否与实际文件一致。
  4. 确认资产只 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

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


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/30c7cb65e3b93f2b. Report an issue: GitHub.