パートナー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から呼び出し — サーバーのegress IPを追加してください |
| 404 | PRODUCT_NOT_FOUND | 存在しない、または販売終了の商品 |
| 429 | RATE_LIMITED | レート制限超過 — Retry-After 秒後に再試行 |
レート制限#
注記
B2B APIはキーごとに既定で1時間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;
}