Developer Guide · Getting started
Rate limits & errors
How many requests you can make, how errors are shaped, and how to handle each status code so your integration retries the right things and fixes the rest.
The Inboxi API is designed to be predictable: every response is JSON with a success flag, limits are announced in headers, and rate limits come with a Retry-After value. This page tells you how to build a client that behaves well under load.
Rate limits
Each API key may make 60 requests per minute by default. The window starts with your first request and resets 60 seconds later. Every authenticated response tells you where you stand:
| Header | Type | Required | Description |
|---|---|---|---|
X-RateLimit-Limit | number | optional | Requests allowed per minute for this key. |
X-RateLimit-Remaining | number | optional | Requests left in the current window. |
Retry-After | seconds | optional | Only on 429: how long to wait before the window resets. |
HTTP/1.1 429 Too Many Requests
Retry-After: 23
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 0
{ "success": false, "error": "Rate limit exceeded. Max 60 requests per minute." }/api/v1/external-data/bulk (up to 500 records per call) instead of looping single pushes, queue outbound events in your backend, and read X-RateLimit-Remaining to slow down before you hit zero.Per-recipient event cooldown
Separately from the key limit, an event can have a cooldown per phone number so a customer hammering a “resend” button does not receive a burst of identical WhatsApp messages. A blocked trigger also returns429, with the earlier send's logId so you can poll its status instead of re-triggering:
HTTP/1.1 429 Too Many Requests
Retry-After: 42
{
"success": false,
"error": "COOLDOWN_ACTIVE",
"message": "A 'custom.otp' message was already sent to this number 18s ago. Try again in 42s.",
"retryAfter": 42,
"logId": 9182
}Error format
Errors always have "success": false and an error field. Some endpoints use a machine-readable code in error plus a human message; plan errors add code.
// Validation error from a resource endpoint
{ "success": false, "error": "Required fields: source, dataType, data" }
// Coded error from event messaging
{ "success": false, "error": "MISSING_FIELDS", "message": "Missing required fields: student_name, course_name" }Status codes
| Status | Meaning | What to do |
|---|---|---|
| 200 / 201 | Success / resource created | Read data from the body. |
| 202 | Accepted: an event was queued or scheduled | Keep the logId and check status later. |
| 400 | Bad request: missing or invalid fields, unknown event, no WhatsApp connection | Fix the request. Do not retry unchanged. |
| 401 | Missing, invalid, revoked or expired API key | Check the X-API-Key header and the key status. |
| 403 | Key lacks a permission, or plan lacks the feature (FEATURE_LOCKED) | Use a key with the permission or upgrade the plan. |
| 404 | Resource not found in your organization | Check the ID. IDs from other workspaces are never visible. |
| 429 | Rate limit or event cooldown | Wait Retry-After seconds, then retry. |
| 500 | Unexpected server error | Retry with backoff; contact support if it persists. |
Event messaging error codes
| error | Status | Cause |
|---|---|---|
VALIDATION_ERROR | 400 | event or phone missing, or data is not an object. |
EVENT_NOT_ACTIVE | 400 | The event does not exist or is switched off in Settings › Automated Messages. |
MISSING_FIELDS | 400 | A variable marked required in the event is missing from data. |
SCHEDULING_NOT_ALLOWED | 400 | The event is marked sensitive (e.g. a one-time code) and cannot be sent with sendAt. |
COOLDOWN_ACTIVE | 429 | The same event was sent to this number within its cooldown window. |
INTERNAL_ERROR | 500 | Unexpected failure while queueing. |
Retrying safely
- Retry only
429,500and network timeouts. Never retry a400,401,403or404without changing something. - Use exponential backoff with jitter (for example 1s, 2s, 4s, 8s) and honour
Retry-Afterwhen present. - Make external-data pushes idempotent by always sending a stable
externalId: a retried push updates the same record instead of creating a duplicate. - For events, a timeout does not mean the message was not queued. Before re-triggering, check recent logs or rely on the event cooldown.
async function inboxi(path, options = {}, attempt = 0) {
const res = await fetch('https://inboxi.app/api/v1' + path, {
...options,
headers: { 'X-API-Key': process.env.INBOXI_API_KEY, 'Content-Type': 'application/json', ...options.headers }
});
if ((res.status === 429 || res.status >= 500) && attempt < 4) {
const wait = Number(res.headers.get('Retry-After')) * 1000 || (2 ** attempt) * 1000 + Math.random() * 250;
await new Promise(r => setTimeout(r, wait));
return inboxi(path, options, attempt + 1);
}
const body = await res.json();
if (!body.success) throw Object.assign(new Error(body.message || body.error), { status: res.status, body });
return body;
}The full per-endpoint error list is in the API reference.