错误代码
Apinoa API 返回的错误代码完整参考。
错误响应格式
所有错误响应遵循一致的封装格式:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "可读的错误描述"
}
}网关端点还包含用于追踪的 requestId:
{
"success": false,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Your balance does not cover this call. Top up at https://apinoa.com/dashboard/balance."
},
"requestId": "550e8400e29b41d4a716446655440000"
}平台 API 错误
这些错误来自 gateway.apinoa.com,每一个都带有 requestId —— 联系支持团队时请附上它。
您的账户
| 代码 | 状态 | 含义 |
|---|---|---|
UNAUTHORIZED | 401 | 缺少 x-api-key 头,或密钥已吊销、已过期或无法识别 |
INSUFFICIENT_BALANCE | 402 | 本次调用费用超过剩余余额。请在 控制台 > 余额 中充值 |
FORBIDDEN | 403 | 密钥有效,但不允许发起该调用 |
ACCOUNT_SUSPENDED | 403 | 账号已被停用:解除前该账号下的所有密钥都会被拒绝。请联系客服 |
需要重点处理的是 402。在按量付费模式下,它表示余额正常用尽,而非故障 —— 重试无法解决,
充值之前的每次调用都会返回同样的结果。
背压
| 代码 | 状态 | 含义 |
|---|---|---|
CAPACITY_SATURATED | 429 | 暂无容量处理本次调用,平台本身没有问题 |
MARKETPLACE_SATURATED | 429 | 同上,但仅限某一个平台 |
两者都带有以秒为单位的 Retry-After。它是真实的估算值而非固定常量 —— 请等待相应时间后重试。
两者均不计费。
您的请求
| 代码 | 状态 | 含义 |
|---|---|---|
UNKNOWN_MARKETPLACE | 404 | 平台不存在。GET /v1/marketplaces 可列出所有平台 |
UNKNOWN_OPERATION | 404 | 该平台未实现此操作 |
INVALID_PARAMS | 400 | 参数缺失、无法识别或超出范围;错误信息会指明具体参数 |
QUERY_REQUIRED IMAGE_REQUIRED CATEGORY_REQUIRED | 400 | 缺少该操作唯一的必填输入 |
CATEGORY_NOT_BROWSABLE | 400 | 该类目是没有商品的导航类目。请用同一 id 调用 categories,再浏览其子类目 |
BROWSABLE_ONLY_UNAVAILABLE | 400 | 对尚无测量数据的平台使用了 browsableOnly=true。去掉该参数即可获得完整分类 |
INVALID_CURSOR | 400 | cursor 并非该操作签发的。请不带游标重新开始 |
PAGE_NOT_SUPPORTED | 400 | 该操作按 cursor 翻页,而不是 page |
CURRENCY_NOT_SUPPORTED | 400 | 平台不以所请求的货币标价 |
INVALID_PRODUCT_ID | 400 | 该 id 不是此平台使用的格式 |
PRODUCT_NOT_FOUND | 404 | 平台上没有该商品 |
REVIEWS_NOT_FOUND | 404 | 商品存在,且确实一条评价也没有 |
400 从不计费。
覆盖范围
| 代码 | 状态 | 含义 |
|---|---|---|
OPERATION_UNSUPPORTED | 501 | 该操作存在,但不支持此输入 |
REVIEW_APP_UNSUPPORTED | 501 | 商家通过我们尚未支持的应用发布评价 |
REVIEWS_UNREACHABLE | 501 | 评价确实存在(评价数或平均分可以证明),但一条也没有取回 |
501 有意区别于 404 和空列表:数据存在,只是我们无法读取。
上游
| 代码 | 状态 | 含义 |
|---|---|---|
UPSTREAM_ERROR | 502 | 平台返回了无法使用的内容 |
UPSTREAM_UNAVAILABLE | 502 | 平台没有响应 |
UPSTREAM_READ_FAILED | 502 | 响应被中途截断 |
UPSTREAM_TIMEOUT | 502 | 平台未能及时响应 |
SERVICE_UNAVAILABLE | 503 | 我方的临时故障,请重试 |
没有返回任何内容的调用,无论状态码如何,都不计费。 请使用指数退避重试。
本页列出的代码就是全部。平台错误始终以其中之一返回,不会直接暴露更细的代码。提供 requestId,
支持团队即可查到具体情况。
平台错误
这些与具体平台无关,任何路径、任何平台都可能返回。
| 代码 | 状态 | 含义 |
|---|---|---|
BAD_REQUEST | 400 | 请求根本无法解析:格式错误的请求体、无法解析的查询字符串 |
UNSUPPORTED_MEDIA_TYPE | 415 | 向 JSON 端点发送了非 application/json 的内容 |
PAYLOAD_TOO_LARGE | 413 | 请求体超过该端点接受的大小 |
NOT_FOUND | 404 | 该路径上没有路由 |
NOT_IN_V1 | 404 | 该路径存在于 /v1 之前,并不属于 v1。响应体的 successor 字段和 Link: …; rel="successor-version" 响应头会指明替代路径 |
RATE_LIMITED | 429 | 该 API 密钥每秒请求数超过允许值。请等待 Retry-After 秒后重试;被拒绝的调用不计费 |
QUOTA_EXCEEDED | 429 | 账户没有可覆盖此 API 的计费安排 |
INTERNAL_ERROR | 500 | 我方故障。请重试;若持续出现,请提供 requestId |
图片翻译错误
| 代码 | 状态 | 含义 |
|---|---|---|
INVALID_IMAGE | 422 | 这些字节不是可读的图片 |
UNSUPPORTED_IMAGE_FORMAT | 422 | 图片可读,但格式不在翻译支持范围内 |
IMAGE_TOO_LARGE | 413 | 图片的像素尺寸超过该端点的上限。错误信息会给出实际尺寸与上限 |
NETWORK_ERROR | 504 | 图片 URL 未能及时响应 |
TRANSLATION_FAILED | 502 | 文字已读取,但翻译失败 |
RENDER_FAILED | 502 | 译文无法重新绘制回图片 |
ROUTING_INCIDENT | 502 | 请求未能处理,请重试 |
WORKER_USER_FAULT | 4xx | 请求本身被拒绝——状态码与错误信息会说明原因 |
FONT_* 代码属于自定义字体,列在
自定义字体 一页中。还有两个可能直接由翻译调用返回:
用不属于该私有字体的密钥指定字体时的 FONT_AUTH_REQUIRED(401),以及字体加载失败时的
FONT_RESOLVE_ERROR(500)。
AliExpress 错误
| 代码 | 状态 | 含义 |
|---|---|---|
ITEM_NOT_FOUND | 404 | 商品在平台上不存在 |
PROHIBITED_COUNTRY | 403 | 无法配送到所请求的国家 |
TOKEN_EXPIRED | 503 | AliExpress 暂时不可用,请稍后重试 |
如果 AliExpress 返回了自身的错误编号,它会出现在响应体的 upstreamCode 字段以及 X-Apinoa-Upstream-Code
响应头中。
错误处理
重试策略
对于临时性错误(UPSTREAM_* 系列、SERVICE_UNAVAILABLE、TOKEN_EXPIRED),建议实现指数退避重试。
对于 CAPACITY_SATURATED 和 MARKETPLACE_SATURATED,请按响应给出的 Retry-After 等待,而不要自行猜测:
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.ok) {
return response.json();
}
const error = await response.json();
const code = error.error?.code;
// 客户端错误不进行重试
// INSUFFICIENT_BALANCE 不会自行恢复 —— 重试只会白白耗尽重试次数
if (["INSUFFICIENT_BALANCE", "UNAUTHORIZED", "FORBIDDEN", "ACCOUNT_SUSPENDED", "INVALID_PARAMS",
"ITEM_NOT_FOUND", "PRODUCT_NOT_FOUND"].includes(code)) {
throw new Error(`${code}: ${error.error.message}`);
}
// 临时性错误使用指数退避重试
if (attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
throw new Error("Max retries exceeded");
}# 使用 curl 进行简单重试
for i in 1 2 3; do
response=$(curl -s -w "\n%{http_code}" -X POST \
https://gateway.apinoa.com/v1/aliexpress/search \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{"query": "phone case"}')
http_code=$(echo "$response" | tail -1)
if [ "$http_code" -eq 200 ]; then
echo "$response" | head -1
break
fi
sleep $((2 ** i))
done