Partner API (B2B)
Errors & rate limits
Partner API error codes, per-key rate limits with X-RateLimit headers, and how to retry.
On this page
Error format#
Error bodies are { statusCode, code, message }. message is human-readable English and may change, so always branch on code.
// Success
{ "success": true, "data": ..., "total": 128, "page": 1, "limit": 20 }
// Error
{ "statusCode": 401, "code": "INVALID_API_KEY", "message": "The API key is invalid." }Note
Error codes#
| HTTP | code | Meaning and fix |
|---|---|---|
| 401 | API_KEY_REQUIRED | No key sent — add the X-API-Key header |
| 401 | INVALID_API_KEY | Malformed or unknown key — check the copy was not truncated and that it is not a Public API key |
| 401 | KEY_REVOKED | Revoked key — issue a new one |
| 401 | KEY_EXPIRED | Expired key (including an old key after the rotation grace period) — use the new key |
| 403 | PARTNER_SUSPENDED | Account is not active — contact esimoa |
| 403 | INSUFFICIENT_SCOPE | Request outside the key’s scopes |
| 403 | IP_NOT_ALLOWED | Called from an IP outside the allowlist — add your server’s egress IP |
| 404 | PRODUCT_NOT_FOUND | Unknown or discontinued product |
| 429 | RATE_LIMITED | Rate limit exceeded — retry after Retry-After seconds |
Rate limits#
Note
B2B API requests default to 20,000 per hour per key.
The 3,600-second window starts on the first request; the per-minute limit also applies. Quotas count calls, not transferred MB. Operational settings may change the quota.
X-RateLimit-Hour-Limit · X-RateLimit-Hour-Remaining · X-RateLimit-Hour-Reset
These headers report the hourly limit, remaining calls and reset time (Unix seconds). Hourly exhaustion returns HTTP 429 and Retry-After in seconds. Calls rejected by the minute limit do not count toward the hourly quota.
The default is 300 requests per minute per key (per 60-second window, shared across all servers). esimoa can raise it per account under your contract. Every authenticated response carries quota headers.
X-RateLimit-Limit— requests allowed per minuteX-RateLimit-Remaining— requests left in the current windowX-RateLimit-Reset— when the window resets (Unix seconds)Retry-After— on 429, seconds to wait before retrying
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)." }Retrying#
- On 429, wait for Retry-After seconds, then retry.
- Retry 5xx a few times with exponential backoff.
- 400, 401, 403 and 404 will not change on retry — fix the cause.
- Caching the catalog for a few minutes keeps you well under the limit.
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;
}