错误
API 返回的每个错误代码、对应的 HTTP 状态码以及应对方法。
错误格式
所有错误都使用相同的信封:success: false、包含稳定的 code 和供人阅读的 message 的 error,以及联系支持时
需提供的 requestId(也会通过 x-request-id 响应头发送)。
{
"success": false,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Your balance does not cover this call. Top up at https://apinoa.com/dashboard/balance."
},
"requestId": "550e8400e29b41d4a716446655440000"
}请根据 code 分支处理,绝不要依据 message:消息的措辞可能调整,而代码在 v1 内不会变化。失败的调用
不计费。
Marketplace API 与 SNS API
你的账户
| 状态码 | 代码 | 含义 |
|---|---|---|
| 401 | UNAUTHORIZED | 缺少 x-api-key 请求头,或密钥已吊销、已过期或不存在。 |
| 402 | INSUFFICIENT_BALANCE | 本次调用的费用超过剩余额度。请在控制台 > 余额中充值。 |
| 403 | ACCOUNT_SUSPENDED | 账户已被停用;其下所有密钥都会被拒绝。请联系支持。 |
| 403 | FORBIDDEN | 密钥有效,但不允许该密钥进行此调用。 |
402 是需要处理的那个:对于预付余额,它是额度用尽的正常结果,而非故障。重试无法解决。
你的请求
| 状态码 | 代码 | 含义 |
|---|---|---|
| 400 | BROWSABLE_ONLY_UNAVAILABLE | 在没有该数据的电商平台上传入了 browsableOnly=true。请省略该参数。 |
| 400 | CATEGORY_NOT_BROWSABLE | 该类目是不含商品的导航层级。请从 categories 中选择其子类目浏览。 |
| 400 | CATEGORY_REQUIRED | 缺少该操作必需的类目。 |
| 400 | CURRENCY_NOT_SUPPORTED | 该电商平台不以所请求的币种标价。 |
| 400 | IMAGE_REQUIRED | 缺少该操作必需的图片。 |
| 400 | INVALID_CURSOR | 该 cursor 不是此操作返回的。请去掉它重新开始。 |
| 400 | INVALID_PARAMS | 某个参数缺失、未知或超出范围。消息中会指出其名称。 |
| 400 | INVALID_PRODUCT_ID | 该 id 不是此电商平台使用的格式。 |
| 400 | PAGE_NOT_SUPPORTED | 该操作按 cursor 分页,而不是按 page。 |
| 400 | QUERY_REQUIRED | 缺少该操作必需的关键词。 |
| 400 | VIDEO_TOO_LARGE | 视频文件超过此 API 可返回的大小(64 MB)。 |
| 404 | NOT_FOUND | 不存在该账号、帖子、群组、合集、地点或应用,或其为私密、已删除或受年龄限制。 |
| 404 | NOT_SHOPIFY_STORE | 传给 shopify/catalog 的站点不是 Shopify 店铺。 |
| 404 | PRODUCT_NOT_FOUND | 该电商平台上没有此商品。 |
| 404 | REVIEWS_NOT_FOUND | 商品存在,且确实没有评论。在 Google Shopping 上:单个商店的商品,Google 不保存其评论。 |
| 404 | STORE_CLOSED | 该 Shopify 店铺受密码保护或已关闭,其商品目录不公开。 |
| 404 | UNKNOWN_MARKETPLACE | 没有该平台。GET /v1/marketplaces 会列出所有平台。 |
| 404 | UNKNOWN_OPERATION | 该平台没有此操作。 |
400 一律不计费。
背压
| 状态码 | 代码 | 含义 |
|---|---|---|
| 429 | CAPACITY_SATURATED | 当前没有可用于此调用的容量。平台本身没有问题。 |
| 429 | MARKETPLACE_SATURATED | 同上,但针对单个平台。 |
两者都携带以秒为单位的 Retry-After 响应头。等待相应时间后重试:它是真实的估算值,而非固定常数。两者都不计费。
覆盖范围
| 状态码 | 代码 | 含义 |
|---|---|---|
| 501 | OPERATION_UNSUPPORTED | 该操作存在,但不支持此输入。 |
| 501 | REVIEW_APP_UNSUPPORTED | 该店铺通过本 API 尚不支持读取的应用发布评论。 |
| 501 | REVIEWS_UNREACHABLE | 评论存在(评论数或平均分表明如此),但一条都无法读取。 |
501 刻意不作为 404,也不返回空列表:数据存在,只是无法读取。
上游
| 状态码 | 代码 | 含义 |
|---|---|---|
| 502 | UPSTREAM_ERROR | 平台返回了无法使用的内容。 |
| 502 | UPSTREAM_READ_FAILED | 平台的响应被截断。 |
| 502 | UPSTREAM_TIMEOUT | 平台未能及时响应。 |
| 502 | UPSTREAM_TOO_LARGE | 平台的响应过大,无法返回。 |
| 502 | UPSTREAM_UNAVAILABLE | 平台没有响应。 |
| 503 | SERVICE_UNAVAILABLE | 我方的临时故障。请重试。 |
请以指数退避方式重试这些错误。它们就是全部词汇:平台故障总会以其中之一报告。
平台通用错误
这些错误可能在任何路径上返回。
| 状态码 | 代码 | 含义 |
|---|---|---|
| 400 | BAD_REQUEST | 请求完全无法读取:请求体或查询字符串格式错误。 |
| 404 | NOT_IN_V1 | 该路径在 /v1 之前就存在,但不属于 /v1。successor 字段和 Link: …; rel="successor-version" 响应头给出其替代路径。 |
| 413 | PAYLOAD_TOO_LARGE | 请求体超过该接口接受的大小。 |
| 415 | UNSUPPORTED_MEDIA_TYPE | 向 JSON 接口发送了非 application/json 的内容。 |
| 429 | QUOTA_EXCEEDED | 该账户没有涵盖此 API 的计费安排。 |
| 429 | RATE_LIMITED | 该密钥每秒发送的请求数超过其限额。等待 Retry-After 秒后重试;被拒绝的调用不计费。 |
| 500 | INTERNAL_ERROR | 我方故障。请重试;如果持续出现,请提供 requestId。 |
Image Translate
| 状态码 | 代码 | 含义 |
|---|---|---|
| 4xx | WORKER_USER_FAULT | 请求本身被拒绝;状态码和消息会说明原因。 |
| 413 | IMAGE_TOO_LARGE | 图片的像素尺寸超过限制。消息中会给出两者。 |
| 422 | INVALID_IMAGE | 这些字节不是可读取的图片。 |
| 422 | UNSUPPORTED_IMAGE_FORMAT | 图片可读取,但其格式不支持翻译。 |
| 502 | RENDER_FAILED | 无法将译文绘制回图片上。 |
| 502 | ROUTING_INCIDENT | 无法处理该请求。请重试。 |
| 502 | TRANSLATION_FAILED | 已读取文字,但无法翻译。 |
| 504 | NETWORK_ERROR | 图片 URL 未能及时响应。 |
FONT_* 代码属于自定义字体,列在
Custom Fonts 中。其中两个可能由翻译调用本身返回:
FONT_AUTH_REQUIRED(401),当指定了私有字体但未使用拥有该字体的密钥时;以及
FONT_RESOLVE_ERROR(500),当字体无法加载时。
AliExpress
| 状态码 | 代码 | 含义 |
|---|---|---|
| 403 | PROHIBITED_COUNTRY | 无法配送到所请求的国家。 |
| 404 | ITEM_NOT_FOUND | AliExpress 上没有此商品。 |
| 503 | TOKEN_EXPIRED | AliExpress 暂时不可用。请稍后重试。 |
AliExpress 自身的错误编号(如果有)位于响应体的 upstreamCode 字段和
X-Apinoa-Upstream-Code 响应头中。
重试
只重试第二次有可能成功的错误:UPSTREAM_* 系列、SERVICE_UNAVAILABLE、
INTERNAL_ERROR 和 TOKEN_EXPIRED 以指数退避重试,429 代码在等待其
Retry-After 后重试。绝不要重试 429 以外的 4xx:它会以同样的方式失败。
const RETRYABLE = new Set(["CAPACITY_SATURATED", "MARKETPLACE_SATURATED", "RATE_LIMITED", "SERVICE_UNAVAILABLE",
"INTERNAL_ERROR", "TOKEN_EXPIRED", "UPSTREAM_ERROR", "UPSTREAM_READ_FAILED", "UPSTREAM_TIMEOUT", "UPSTREAM_UNAVAILABLE"]);
async function call(url, init = {}, attempts = 4) {
for (let i = 0; ; i++) {
const res = await fetch(url, { ...init, headers: { ...init.headers, "x-api-key": process.env.APINOA_API_KEY } });
const body = await res.json();
if (body.success) return body.data;
if (!RETRYABLE.has(body.error.code) || i === attempts - 1) throw new Error(`${body.error.code}: ${body.error.message}`);
const wait = Number(res.headers.get("retry-after")) || 2 ** i;
await new Promise((r) => setTimeout(r, wait * 1000));
}
}