合作夥伴 API(B2B)
錯誤與速率限制
合作夥伴 API 的錯誤碼、每組金鑰速率限制與 X-RateLimit 標頭,以及重試方法。
錯誤格式#
錯誤回應本文為 { statusCode, code, message }。message 為供人閱讀的英文說明且可能變動,請一律依 code 判斷。
json
// Success
{ "success": true, "data": ..., "total": 128, "page": 1, "limit": 20 }
// Error
{ "statusCode": 401, "code": "INVALID_API_KEY", "message": "The API key is invalid." }注意
查詢值驗證錯誤(400)使用標準格式 { statusCode: 400, message: [...], error: "Bad Request" },不含 code。
錯誤碼#
| HTTP | code | 含義與處理 |
|---|---|---|
| 401 | API_KEY_REQUIRED | 未傳送金鑰 — 請加上 X-API-Key 標頭 |
| 401 | INVALID_API_KEY | 格式錯誤或不存在的金鑰 — 檢查是否複製不完整或誤用了公開 API 金鑰 |
| 401 | KEY_REVOKED | 已撤銷的金鑰 — 請核發新金鑰 |
| 401 | KEY_EXPIRED | 已到期的金鑰(含輪替寬限期結束後的舊金鑰)— 請使用新金鑰 |
| 403 | PARTNER_SUSPENDED | 帳號未啟用 — 請聯絡 esimoa |
| 403 | INSUFFICIENT_SCOPE | 請求超出金鑰權限範圍 |
| 403 | IP_NOT_ALLOWED | 從允許清單以外的 IP 呼叫 — 請加入伺服器對外 IP |
| 404 | PRODUCT_NOT_FOUND | 商品不存在或已停售 |
| 429 | RATE_LIMITED | 超出速率限制 — 在 Retry-After 秒後重試 |
速率限制#
注意
B2B API 預設每組金鑰每小時 20,000 次。
從首次請求起計 3,600 秒,並同時套用每分鐘限制。計算呼叫次數而非傳輸 MB,實際配額可由營運設定調整。
X-RateLimit-Hour-Limit · X-RateLimit-Hour-Remaining · X-RateLimit-Hour-Reset
這些標頭表示每小時上限、剩餘次數及重設時間(Unix 秒)。超限回傳 HTTP 429 和 Retry-After 秒數。每分鐘限制拒絕的請求不計入小時配額。
預設每組金鑰每分鐘 300 次(以 60 秒為一個區間,所有伺服器共用)。esimoa 可依合約按帳號上調。通過驗證的每個回應都會附帶配額標頭。
X-RateLimit-Limit— 每分鐘允許次數X-RateLimit-Remaining— 目前區間剩餘次數X-RateLimit-Reset— 區間重置時間(Unix 秒)Retry-After— 429 時重試前需等待的秒數
http
HTTP/1.1 200 OK
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1790000000
HTTP/1.1 429 Too Many Requests
Retry-After: 18
{ "statusCode": 429, "code": "RATE_LIMITED", "message": "Rate limit exceeded (300 requests per minute)." }重試#
- 收到 429 時,等待 Retry-After 秒後重試。
- 5xx 請以指數退避方式少量重試。
- 400、401、403、404 重試也不會改變結果 — 請修正原因。
- 將目錄快取幾分鐘,幾乎不會觸及限制。
javascript
async function b2bFetch(url, attempt = 0) {
const res = await fetch(url, { headers: { 'X-API-Key': process.env.ESIMOA_PARTNER_KEY } });
if (res.status === 429 && attempt < 3) {
const wait = Number(res.headers.get('Retry-After') ?? 1);
await new Promise((r) => setTimeout(r, wait * 1000));
return b2bFetch(url, attempt + 1);
}
const body = await res.json();
if (!res.ok) throw Object.assign(new Error(body.message), { code: body.code, status: res.status });
return body;
}