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:

HeaderTypeRequiredDescription
X-RateLimit-LimitnumberoptionalRequests allowed per minute for this key.
X-RateLimit-RemainingnumberoptionalRequests left in the current window.
Retry-AftersecondsoptionalOnly on 429: how long to wait before the window resets.
json
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." }
Stay under the limit
Use /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:

json
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.

json
// 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

StatusMeaningWhat to do
200 / 201Success / resource createdRead data from the body.
202Accepted: an event was queued or scheduledKeep the logId and check status later.
400Bad request: missing or invalid fields, unknown event, no WhatsApp connectionFix the request. Do not retry unchanged.
401Missing, invalid, revoked or expired API keyCheck the X-API-Key header and the key status.
403Key lacks a permission, or plan lacks the feature (FEATURE_LOCKED)Use a key with the permission or upgrade the plan.
404Resource not found in your organizationCheck the ID. IDs from other workspaces are never visible.
429Rate limit or event cooldownWait Retry-After seconds, then retry.
500Unexpected server errorRetry with backoff; contact support if it persists.

Event messaging error codes

errorStatusCause
VALIDATION_ERROR400event or phone missing, or data is not an object.
EVENT_NOT_ACTIVE400The event does not exist or is switched off in Settings › Automated Messages.
MISSING_FIELDS400A variable marked required in the event is missing from data.
SCHEDULING_NOT_ALLOWED400The event is marked sensitive (e.g. a one-time code) and cannot be sent with sendAt.
COOLDOWN_ACTIVE429The same event was sent to this number within its cooldown window.
INTERNAL_ERROR500Unexpected failure while queueing.

Retrying safely

  • Retry only 429, 500 and network timeouts. Never retry a 400, 401, 403 or 404 without changing something.
  • Use exponential backoff with jitter (for example 1s, 2s, 4s, 8s) and honour Retry-After when 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.
A minimal retrying client
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.