Errors

Error response shapes for both APIs, what each status code means in practice, and which are safe to retry.

The Platform API and APIchat return different error shapes but use the same status codes. This page covers both and tells you what to do about each.

Error shapes

Platform API

A per-field map. Each key is the parameter that failed validation; the value is a list of human-readable error messages.

{
  "errors": {
    "customer_id": ["You can't send message to this customer"],
    "channel_id": [
      "The channel is not active",
      "The channel is disabled"
    ]
  }
}

Use the keys to surface form-level errors; use the values for the message.

APIchat

A single string under error, with success: false.

{
  "success": false,
  "error": "Invalid message type"
}

Less granular — there's no field attribution — but easier to log and display.

Status codes

Code Class Meaning Retry?
400 Client Body isn't valid JSON. No — fix the request.
401 Auth Missing or invalid token. No — reauthenticate.
402 Billing Payment required (subscription / quota issue). No — resolve in the dashboard.
403 Auth Token is valid but lacks permission for the resource (Platform) or the operation isn't allowed (APIchat). No — fix scope or stop trying.
404 Client Resource doesn't exist. No — bad ID.
405 Client Wrong HTTP method on a valid path. No — fix the request.
409 Client (APIchat) Conflict — typically "customer already exists" on create. No — read the existing resource instead.
412 State Precondition failed (customer blocked, channel inactive, opt-in missing, ...). Sometimes — see below.
413 Client Request body too large. No — split or shrink.
422 Validation Body parsed but failed validation. No — fix and retry.
429 Throttle Rate-limited. Yes — exponential backoff, see Rate limiting.
5xx Server Landbot-side. Yes — exponential backoff.

412 in detail

412 Precondition failed covers state-dependent rejections — the request is well-formed and authorised, but the system is in a state that blocks it. Examples from the API:

  • "The customer is blocked"
  • "The channel is not active"
  • "The channel is disabled"
  • "The customer has not made the opt in"
  • "This customer is assigned to other agent"

These aren't transient — retrying immediately won't help. They're not permanent either — the state can change (an admin unblocks the customer, the customer opts in, the channel is re-enabled). Treat them as conditional retries: surface the message to the operator who can fix the state, then retry once the cause is resolved.

Retry classification

Putting it together:

  • Safe to retry without changing the request: 429, 5xx. Use exponential backoff with jitter.
  • Retry once after fixing state: 412 (when the underlying condition has plausibly changed).
  • Retry after fixing the request: 400, 404, 405, 409, 413, 422.
  • Don't retry — escalate: 401, 402, 403. These need human intervention (rotate token, resolve billing, change permissions).

Idempotency

Landbot doesn't expose an idempotency-key header. That means POST retries can duplicate: a POST /customers/{customer_id}/send_text/ that times out client-side may or may not have actually sent.

Two ways to handle this safely:

  1. Use PUT where possible. Most state-change endpoints in the Platform API are PUT (archive, block, assign, ...) and are naturally idempotent — calling twice is the same as calling once.
  2. For send endpoints, accept best-effort and dedupe at the bot or downstream. The Landbot Field model can carry an idempotency key your bot checks before processing.

A defensive retry helper

const TRANSIENT = new Set([429, 500, 502, 503, 504]);

async function call(url, options, attempts = 5) {
  for (let i = 0; i < attempts; i++) {
    const res = await fetch(url, options);
    if (!TRANSIENT.has(res.status)) return res;
    if (i === attempts - 1) return res;
    const wait = Math.min(2 ** i * 100, 5000) + Math.random() * 100;
    await new Promise(r => setTimeout(r, wait));
  }
}

This handles 429 and 5xx automatically and surfaces every other status to the caller for explicit handling.