Apinoa Docs

错误

API 返回的每个错误代码、对应的 HTTP 状态码以及应对方法。

查看原始 .mdx

错误格式

所有错误都使用相同的信封: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

你的账户

状态码代码含义
401UNAUTHORIZED缺少 x-api-key 请求头,或密钥已吊销、已过期或不存在。
402INSUFFICIENT_BALANCE本次调用的费用超过剩余额度。请在控制台 > 余额中充值。
403ACCOUNT_SUSPENDED账户已被停用;其下所有密钥都会被拒绝。请联系支持。
403FORBIDDEN密钥有效,但不允许该密钥进行此调用。

402 是需要处理的那个:对于预付余额,它是额度用尽的正常结果,而非故障。重试无法解决。

你的请求

状态码代码含义
400BROWSABLE_ONLY_UNAVAILABLE在没有该数据的电商平台上传入了 browsableOnly=true。请省略该参数。
400CATEGORY_NOT_BROWSABLE该类目是不含商品的导航层级。请从 categories 中选择其子类目浏览。
400CATEGORY_REQUIRED缺少该操作必需的类目。
400CURRENCY_NOT_SUPPORTED该电商平台不以所请求的币种标价。
400IMAGE_REQUIRED缺少该操作必需的图片。
400INVALID_CURSOR该 cursor 不是此操作返回的。请去掉它重新开始。
400INVALID_PARAMS某个参数缺失、未知或超出范围。消息中会指出其名称。
400INVALID_PRODUCT_ID该 id 不是此电商平台使用的格式。
400PAGE_NOT_SUPPORTED该操作按 cursor 分页,而不是按 page。
400QUERY_REQUIRED缺少该操作必需的关键词。
400VIDEO_TOO_LARGE视频文件超过此 API 可返回的大小(64 MB)。
404NOT_FOUND不存在该账号、帖子、群组、合集、地点或应用,或其为私密、已删除或受年龄限制。
404NOT_SHOPIFY_STORE传给 shopify/catalog 的站点不是 Shopify 店铺。
404PRODUCT_NOT_FOUND该电商平台上没有此商品。
404REVIEWS_NOT_FOUND商品存在,且确实没有评论。在 Google Shopping 上:单个商店的商品,Google 不保存其评论。
404STORE_CLOSED该 Shopify 店铺受密码保护或已关闭,其商品目录不公开。
404UNKNOWN_MARKETPLACE没有该平台。GET /v1/marketplaces 会列出所有平台。
404UNKNOWN_OPERATION该平台没有此操作。

400 一律不计费。

背压

状态码代码含义
429CAPACITY_SATURATED当前没有可用于此调用的容量。平台本身没有问题。
429MARKETPLACE_SATURATED同上,但针对单个平台。

两者都携带以秒为单位的 Retry-After 响应头。等待相应时间后重试:它是真实的估算值,而非固定常数。两者都不计费。

覆盖范围

状态码代码含义
501OPERATION_UNSUPPORTED该操作存在,但不支持此输入。
501REVIEW_APP_UNSUPPORTED该店铺通过本 API 尚不支持读取的应用发布评论。
501REVIEWS_UNREACHABLE评论存在(评论数或平均分表明如此),但一条都无法读取。

501 刻意不作为 404,也不返回空列表:数据存在,只是无法读取。

上游

状态码代码含义
502UPSTREAM_ERROR平台返回了无法使用的内容。
502UPSTREAM_READ_FAILED平台的响应被截断。
502UPSTREAM_TIMEOUT平台未能及时响应。
502UPSTREAM_TOO_LARGE平台的响应过大,无法返回。
502UPSTREAM_UNAVAILABLE平台没有响应。
503SERVICE_UNAVAILABLE我方的临时故障。请重试。

请以指数退避方式重试这些错误。它们就是全部词汇:平台故障总会以其中之一报告。

平台通用错误

这些错误可能在任何路径上返回。

状态码代码含义
400BAD_REQUEST请求完全无法读取:请求体或查询字符串格式错误。
404NOT_IN_V1该路径在 /v1 之前就存在,但不属于 /v1。successor 字段和 Link: …; rel="successor-version" 响应头给出其替代路径。
413PAYLOAD_TOO_LARGE请求体超过该接口接受的大小。
415UNSUPPORTED_MEDIA_TYPE向 JSON 接口发送了非 application/json 的内容。
429QUOTA_EXCEEDED该账户没有涵盖此 API 的计费安排。
429RATE_LIMITED该密钥每秒发送的请求数超过其限额。等待 Retry-After 秒后重试;被拒绝的调用不计费。
500INTERNAL_ERROR我方故障。请重试;如果持续出现,请提供 requestId。

Image Translate

状态码代码含义
4xxWORKER_USER_FAULT请求本身被拒绝;状态码和消息会说明原因。
413IMAGE_TOO_LARGE图片的像素尺寸超过限制。消息中会给出两者。
422INVALID_IMAGE这些字节不是可读取的图片。
422UNSUPPORTED_IMAGE_FORMAT图片可读取,但其格式不支持翻译。
502RENDER_FAILED无法将译文绘制回图片上。
502ROUTING_INCIDENT无法处理该请求。请重试。
502TRANSLATION_FAILED已读取文字,但无法翻译。
504NETWORK_ERROR图片 URL 未能及时响应。

FONT_* 代码属于自定义字体,列在 Custom Fonts 中。其中两个可能由翻译调用本身返回: FONT_AUTH_REQUIRED(401),当指定了私有字体但未使用拥有该字体的密钥时;以及 FONT_RESOLVE_ERROR(500),当字体无法加载时。

AliExpress

状态码代码含义
403PROHIBITED_COUNTRY无法配送到所请求的国家。
404ITEM_NOT_FOUNDAliExpress 上没有此商品。
503TOKEN_EXPIREDAliExpress 暂时不可用。请稍后重试。

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));
  }
}

本页内容