合作伙伴 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;
}