---
title: "错误代码"
description: "Apinoa API 返回的错误代码完整参考。"
---

## 错误响应格式

所有错误响应遵循一致的封装格式：

```json
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "可读的错误描述"
  }
}
```

网关端点还包含用于追踪的 `requestId`：

```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"
}
```

## 平台 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_*` 代码属于自定义字体，列在
[自定义字体](/docs/api-reference/image-translate/custom-fonts) 一页中。还有两个可能直接由翻译调用返回：
用不属于该私有字体的密钥指定字体时的 `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` 等待，而不要自行猜测：

```javascript
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");
}
```

```bash
# 使用 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
```
