<!-- Source: https://docs.infraio.xyz/zh-TW/api-reference/errors -->
<!-- Last updated: 2026-10-04 -->

# 錯誤

所有 4xx/5xx 回應都帶有相同的 JSON 信封:

```json
{
  "code": 400,
  "message": "invalid_input",
  "details": [
    { "field": "items[0].unit_price", "message": "must be a positive decimal string" }
  ]
}
```

- `code` — **數值型的 HTTP status**(`400`、`401`、`404` …)。用於通用的 HTTP 層處理很方便，但需要分支邏輯時請改用 `message` — `code` 無法區分如 `invalid_input` 與 `payment_method_not_supported`(兩者都是 400)這類情境。
- `message` — **lower-snake-case** 的 sentinel 名稱，即 sentinel 名稱的 lower snake case 形式(例如 `INVALID_INPUT` 變成 `"invalid_input"`)。跨版本穩定 — 以此分支。
- `details` — 僅在驗證錯誤時出現。`{ field, message }` 物件陣列，讓 client 能將錯誤對應到輸入欄位。其他情境下會省略。

> **Warning:**
>
> 錯誤 body 不包含 trace ID 或 timestamp。回報問題時，請向 support 提供回應的 `Date` 標頭、`X-RateLimit-*` 標頭、你的 merchant ID、endpoint，以及大約的請求時間。

> **Note:**
>
> **驗證失敗使用不同的形態。** 上面的信封是 API 對大多數錯誤所回傳的格式。在處理之前就被拒絕的請求 —— `/b2b/v1/*` 呼叫上缺失 / 非法的 `X-Signature`、未知的 `X-Client-ID`,或過期的 `X-Timestamp` —— 會以 `{ "error": "...", "message": "..." }` 的形態回傳，其中 `error` 是粗粒度 slug(`unauthorized` / `bad_request` / `service_unavailable`),`message` 攜帶具體資訊。沒有數值 `code`,也沒有 `details`。請先依 HTTP 狀態分支，再讀 `message`。範例(401):
>
> ```json
> { "error": "unauthorized", "message": "invalid signature" }
> ```

## HTTP 狀態 → 常見 code

| HTTP | 常見 `code` 值 | 意義 |
| --- | --- | --- |
| **400** | `INVALID_INPUT`、`MISSING_REQUIRED`、`INVALID_FORMAT`、`INVALID_LENGTH`、`INVALID_VALUE`、`PAYMENT_METHOD_NOT_SUPPORTED`、`AMOUNT_BELOW_MINIMUM` | 請求有誤 — 請看 `details` |
| **401** | `INVALID_CREDENTIALS`、`INVALID_TOKEN`、`TOKEN_EXPIRED`、`INVALID_SIGNATURE`(B2B);`SESSION_EXPIRED`(僅儀表板) | 驗證失敗 — 金鑰錯誤、timestamp 過期、簽章不符。儀表板登入相關的 code 與 B2B 整合無關。 |
| **402** | `INSUFFICIENT_CREDIT` | 商家預付餘額用盡 — 請先儲值再重試 |
| **403** | `FORBIDDEN`、`IP_BLOCKED` | 金鑰有效但缺少呼叫此 API 所需的 scope / IP 許可 |
| **404** | `NOT_FOUND`、`RECORD_NOT_FOUND` | 資源不存在(或對此商家不存在) |
| **409** | `ALREADY_EXISTS` | 不同 body 的冪等 replay,或狀態機拒絕該轉移 |
| **429** | `TOO_MANY_REQUESTS`、`TOO_MANY_ATTEMPTS` | 觸發速率限制;請退避後重試 |
| **500** | `INTERNAL_SERVER_ERROR`、`EXTERNAL_SERVICE_ERROR` | 我們的問題;可帶退避安全重試。 |
| **503** | `SERVICE_UNAVAILABLE` | 下游依賴出問題。請退避後重試 |

## 完整 code 參考

你可能看到的 `code` 值:

### 身分驗證(401)
- `INVALID_CREDENTIALS` — API 金鑰組合被拒絕
- `INVALID_TOKEN` — token 無法解析或被竄改
- `TOKEN_EXPIRED` — token 已過期
- `SESSION_EXPIRED` — 儀表板 session 已過期
- `INVALID_SIGNATURE` — B2B / webhook 呼叫的 HMAC 簽章不符

### 授權(403)
- `FORBIDDEN` — 已驗證，但你無權執行此動作
- `IP_BLOCKED` — 來自此 IP 位址的請求已被封鎖

### 找不到(404)
- `NOT_FOUND` — 通用
- `RECORD_NOT_FOUND` — 找不到該 ID 對應的列

### 衝突(409)
- `ALREADY_EXISTS` — 通用

### 驗證(400)
- `INVALID_INPUT` — 通用;請查 `details`
- `MISSING_REQUIRED` — 缺少必要欄位
- `INVALID_FORMAT` — 值不符合預期格式(例如 UUID、URL、email)
- `INVALID_LENGTH` — 值過短或過長
- `INVALID_VALUE` — 值超出允許的 enum / 範圍
- `PAYMENT_METHOD_NOT_SUPPORTED` — 該 provider / 資產組合未對此商家啟用
- `AMOUNT_BELOW_MINIMUM` — 訂單金額低於該網路或環境的下限。回應的 `details.floor_usd` 攜帶目前設定的下限(USD),可直接取用顯示;`message` 文字也會說明它。
- `INSUFFICIENT_BALANCE` — 買家的錢包持有的支付資產不足以支付此次轉帳。
- `INSUFFICIENT_GAS` — 買家的錢包缺少用於支付網路手續費(gas)的原生代幣，無法廣播此次轉帳。

### 付款 / 計費(402)
- `INSUFFICIENT_CREDIT` — 商家預付餘額不足以支付網路手續費(gas)/ 平台手續費。請從儀表板儲值後再重試

### 速率限制(429)
- `TOO_MANY_REQUESTS` — 超過 IP 速率限制
- `TOO_MANY_ATTEMPTS` — 對同一資源(例如 OTP)反覆失敗導致觸發 throttle

### 伺服器錯誤(500)
- `EXTERNAL_SERVICE_ERROR` — 第三方 provider 失敗
- `INTERNAL_SERVER_ERROR` — 非預期錯誤;請聯繫 support，並提供回應的 `Date` 標頭與 `X-RateLimit-*` 標頭

### 可用性(503)
- `SERVICE_UNAVAILABLE` — 服務暫時無法使用;請退避後重試

## 驗證錯誤(400)

當問題是請求 body 不正確時，`details` 是陣列，讓你可以把錯誤對應
回欄位:

```json
{
  "code": 400,
  "message": "invalid_input",
  "details": [
    { "field": "items[0].unit_price", "message": "must be a positive decimal string" },
    { "field": "success_url", "message": "must be a valid https URL" }
  ]
}
```

## 身分驗證錯誤(401)

`INVALID_SIGNATURE` 有三個常見原因:

- Secret key 錯誤(再次確認環境變數)
- Timestamp 漂移 > 5 分鐘(同步 NTP)
- Canonical string 錯誤(最常見:忘記 `\n` 分隔符，或簽了一份
  經過 parse-then-re-stringify 與實際送出不同的 body)

確切的簽章演算法請見[身分驗證](https://docs.infraio.xyz/zh-TW/api-reference/authentication)。

## 速率限制(429)

| 範圍 | 限制 |
| --- | --- |
| 所有 API 路由(含 `/b2b/v1/*`) | 以 IP 位址計算 — **500 req/min**,共享 bucket。並非以商家為單位。 |
| 公開結帳(`/checkout/*`) | 更嚴格的子 bucket — **每 IP 20 req/min** |

`X-RateLimit-Limit` 與 `X-RateLimit-Remaining` 會包含在每一個
受速率限制的回應中，不只是成功的回應。請把它們視為
你 IP 的即時預算。

被速率限制的回應會包含 `Retry-After` 標頭(距離視窗重置的秒數)—
請遵循它。作為備援，可以 jitter 退避 — 1s 基底 + 指數成長到 30s。

> **Note:**
>
> 速率限制可能會調整。如果你的合法流量達到上限(例如對帳大量歷史
> 資料),請聯繫 support。

## 信用額度不足(402)

`INSUFFICIENT_CREDIT`(HTTP **402 Payment Required**)代表你的預付
餘額無法支付你嘗試的操作所需的網路手續費(gas)與平台手續費，典型情境是結算
加密支付，或網路手續費由平台代付的鏈上動作。請從商家儀表板
(**Billing → Add credit**)儲值後再重試該操作;
已在處理中的操作會在儲值後自動接續。

## 伺服器錯誤(5xx)

500 代表我們無法處理該請求。請以退避方式重試 — 你的
`idempotency_key` 確保即使原請求部分成功，也不會被重複收費。

若 1 分鐘內重試仍未恢復，請對買家顯示通用的「付款暫時無法使用」
訊息，並向 support 回報失敗的 endpoint、你的 merchant ID、回應的
`Date` 標頭與 `X-RateLimit-*` 的值，以及大約的請求時間 — 這些資訊
足以讓我們找到該請求。

## Webhook 投遞錯誤

Webhook 投遞是獨立的失敗通道 — 它們不會以 API 錯誤的形式出現，
因為呼叫的不是你的伺服器。當投遞回非 2xx(或逾時)時，會以指數
退避重試:0s、1min、5min、15min、1h、6h(共 6 次嘗試，與
[Webhooks → 總覽](https://docs.infraio.xyz/zh-TW/webhooks/overview)相同排程)。完整的每次
投遞歷史在儀表板的 **Developers → Webhooks → [endpoint] → Delivery
log** 中。第 6 次嘗試失敗後，儀表板會將該投遞標示為
「Failed」，你的伺服器恢復後可手動 replay。
