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

> Every apinoa docs page as markdown: https://apinoa.com/llms.txt (all in one file: https://apinoa.com/llms-full.txt)

## 错误格式

所有错误都使用相同的信封：`success: false`、包含稳定的 `code` 和供人阅读的 `message` 的 `error`，以及联系支持时
需提供的 `requestId`（也会通过 `x-request-id` 响应头发送）。

```json
{
  "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](/docs/api-reference/image-translate/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`：它会以同样的方式失败。

```js tab="JavaScript"
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));
  }
}
```

```python tab="Python"
import os
import time

import requests

RETRYABLE = {"CAPACITY_SATURATED", "MARKETPLACE_SATURATED", "RATE_LIMITED", "SERVICE_UNAVAILABLE",
             "INTERNAL_ERROR", "TOKEN_EXPIRED", "UPSTREAM_ERROR", "UPSTREAM_READ_FAILED", "UPSTREAM_TIMEOUT",
             "UPSTREAM_UNAVAILABLE"}

def call(url, params=None, attempts=4):
    for i in range(attempts):
        res = requests.get(url, params=params, headers={"x-api-key": os.environ["APINOA_API_KEY"]})
        body = res.json()
        if body["success"]:
            return body["data"]
        if body["error"]["code"] not in RETRYABLE or i == attempts - 1:
            raise RuntimeError(f'{body["error"]["code"]}: {body["error"]["message"]}')
        time.sleep(float(res.headers.get("retry-after") or 2 ** i))
```

```bash tab="cURL"
for i in 1 2 3 4; do
  status=$(curl -s -o response.json -w "%{http_code}" \
    "https://gateway.apinoa.com/v1/walmart/search?query=wool+socks" \
    -H "x-api-key: $APINOA_API_KEY")
  case "$status" in
    200) cat response.json; break ;;
    429|5??) sleep $((2 ** i)) ;;
    *) cat response.json; break ;;
  esac
done
```
