Skip to content

Errors

Every 4xx and 5xx response from the Lisoloo API uses the same JSON shape. Branch on error_code, never on message — messages are localised and may change for clarity; codes are part of the public API contract.

{
"success": false,
"status_code": 400,
"error_code": "1101",
"message": "Invalid amount or recipient list",
"details": [
{
"field": "to[0]",
"message": "Must match E.164 format",
"code": "INVALID_PHONE_NUMBER"
}
],
"timestamp": "2026-05-27T10:15:00Z",
"request_id": "req_8f3a4c2e9b1d7a6f",
"retryable": false,
"retry_after": null
}
FieldTypeNotes
successboolAlways false on error. Easy guard: if (!body.success) { … }.
status_codeintMirrors the HTTP status.
error_codestringFour-digit stable code. Branch on this.
messagestringHuman-readable. Localised by Accept-Language. Show to ops, not end users.
detailsarray | nullField-level breakdown for validation errors (1110).
timestampISO 8601Server time the error was generated.
request_idstringQuote when contacting support.
retryablebooltrue for transient failures. Never auto-retry on false.
retry_afterint | nullSeconds to wait when retryable: true (and 429s).

The leading digit groups the cause:

RangeGroupExample codes
10011005Authentication1001 INVALID_APP_KEY, 1004 APP_SUSPENDED
11011110Request validation1101 INVALID_RECIPIENTS, 1104 INVALID_PHONE_NUMBER, 1109 MISSING_REQUIRED_FIELD
12011205Business logic1202 INSUFFICIENT_BALANCE, 1203 RECIPIENT_LIMIT_EXCEEDED
13011305Security & rate-limit1301 RATE_LIMIT_EXCEEDED
14011405Server / system1401 INTERNAL_SERVER_ERROR, 1402 SERVICE_UNAVAILABLE
15011505Carrier / SMS processor1502 SMS_REJECTED_BY_CARRIER, 1503 SMS_TIMEOUT

The full catalogue with remediation is at Errors → catalogue.

function handleLisolooError(body: {
error_code: string;
message: string;
retryable: boolean;
retry_after: number | null;
}) {
switch (body.error_code) {
case "1001": // INVALID_APP_KEY
case "1004": // APP_SUSPENDED
// Stop. Don't retry. Surface to operations.
throw new AuthenticationFailed(body.message);
case "1101": // INVALID_RECIPIENTS
case "1104": // INVALID_PHONE_NUMBER
case "1109": // MISSING_REQUIRED_FIELD
// Bad input on our side. Fix and don't retry the same body.
throw new ValidationError(body.message);
case "1202": // INSUFFICIENT_BALANCE
// Surface to dashboard, alert ops, do not retry.
throw new BalanceExhausted(body.message);
case "1301": // RATE_LIMIT_EXCEEDED
// Honour retry_after.
return scheduleRetry(body.retry_after ?? 60);
case "1402": // SERVICE_UNAVAILABLE
case "1503": // SMS_TIMEOUT
if (body.retryable) {
return scheduleRetry(body.retry_after ?? 30);
}
throw new ProcessorFailure(body.message);
default:
throw new UnknownError(body.error_code, body.message);
}
}

Some codes map onto multiple HTTP statuses depending on context (e.g. 1004 APP_SUSPENDED is 403, while 1001 INVALID_APP_KEY is 401). The HTTP status is the broad category; error_code is the specific cause. Use the HTTP status for routing through middleware (auth failures to one handler, validation failures to another); use error_code for the actual logic.

HTTPMeaning
400Validation failure. Don’t retry.
401Auth header missing or invalid. Don’t retry.
402Payment required (balance). Don’t retry.
403Authenticated but forbidden (suspended key). Don’t retry.
404Resource not found (e.g. unknown message_id).
429Rate-limited. Honour Retry-After.
500Server error. Safe to retry once with exponential backoff.
502 / 503Upstream / carrier issue. Safe to retry.

When the gateway returns 1110 INVALID_FIELD_FORMAT (or 1109 MISSING_REQUIRED_FIELD), the details array carries one entry per failed field:

{
"error_code": "1110",
"message": "Field validation failed",
"details": [
{ "field": "to[2]", "message": "Must match E.164 format", "code": "INVALID_PHONE_NUMBER" },
{ "field": "message", "message": "Must be 1-1600 characters", "code": "INVALID_LENGTH" },
{ "field": "scheduled_dates[0].time", "message": "Must be HH:mm", "code": "INVALID_TIME_FORMAT" }
]
}

The field path uses JSONPointer-style dot notation with [n] for array indices. Iterate details to render per-field errors in your UI.