Errors

Error bodies and status codes for every Landbot API, what each one means and which ones to retry.

Every Landbot API answers an error with a 4xx or 5xx status and, most of the time, a JSON body. The shape of that body depends on the API and on where the request was stopped.

Check the status code first

Branch on the status code, then read the body. A 404 or a 5xx always arrives as an HTML page, whatever the Accept header says, so parse the body only when Content-Type says it is JSON:

const res = await fetch(url, { method, headers, body });
if (!res.ok) {
  const type = res.headers.get("content-type") || "";
  const detail = type.includes("json") ? await res.json() : await res.text();
  throw new Error(`HTTP ${res.status}: ${JSON.stringify(detail).slice(0, 200)}`);
}

Error bodies

Platform API

The Platform API uses two shapes.

A per-field map, for validation and state errors the endpoint checks itself (400, 402, 403, 412, 422):

{
  "errors": {
    "customer_id": ["The customer is blocked"]
  }
}

A single detail string, for requests stopped before the endpoint runs: a missing or bad token (401), a body that isn't valid JSON (400), a wrong method (405) and rate limiting (429):

{
  "detail": "Invalid token."
}

A customer, channel or hook that belongs to another workspace answers 404, the same as one that doesn't exist.

APIchat

Errors the endpoint checks itself (409 and 422) use success and a single error string:

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

Everything else uses detail, the same as the Platform API. That includes a missing, wrong or inactive channel token, which answers 403:

{
  "detail": "Invalid token."
}

POST /file/{customer_token}/ reports validation errors as a per-field map under errors.

Bots API, AI Agents API and Channels API

The Bots API answers named refusals as {"error": {"code": "...", "message": "..."}}. See When a call fails for every code. The AI Agents and Channels APIs document their error bodies on each operation in the AI Agents API and Channels API references.

On all three, a 404 or 5xx currently arrives as the HTML page described in Check the status code first, not with those JSON bodies. Branch on the status code before you read error.code.

Status codes

Code Meaning Retry
400 The body isn't valid JSON. After fixing the request.
401 Platform API: the token is missing or invalid. See Authentication. After fixing the token.
402 The workspace's subscription has expired, or its plan doesn't include the feature. After the plan changes.
403 Platform API: your agent's role lacks the permission. APIchat: the channel token is missing, invalid or its channel is inactive. After fixing the role or the token.
404 The resource doesn't exist, belongs to another workspace, or the URL matches no route. After fixing the ID or the path.
405 Wrong HTTP method for the path. After fixing the request.
409 APIchat: a customer with that token already exists. No. Read the existing customer instead.
412 The request is valid, but the customer, channel or Field is in a state that blocks it. See 412 Precondition Failed. After the state changes.
413 The body is too large. After shrinking it.
422 The JSON parsed but a field is missing or invalid. After fixing the request.
429 Too many requests. See Rate limiting. Yes, after Retry-After or a backoff.
5xx Landbot failed. Yes, with backoff.

A 402 body names the cause. An expired subscription answers {"errors": {"brand_id": ["Payment required"]}}. A plan without the feature answers <plan> required under brand_id, naming one of the plans listed under plans that include the feature.

On the Platform API, the only 403 from a role is {"errors": {"token": ["You don't have enought permissions"]}}. The agent token acts as its agent, so ask an admin to give that agent's role the permission, or use the token of an agent who has it.

412 Precondition Failed

A 412 body is a per-field map, and each request reports one cause at a time.

Message Returned by Cause Fix
This customer has no chats send_*, assign, unassign, archive, unarchive The customer has no conversation to act on. None through the API. The customer has to start a conversation first.
The channel is deleted send_* The customer's channel was deleted. None. Reach the customer on another channel.
The channel is not active send_* The customer's channel is turned off. Turn the channel back on in the dashboard.
Is not possible to send messages with API to WhatsApp test numbers send_* The customer is on a WhatsApp test number. Use a production WhatsApp number.
The customer is blocked send_* The customer is blocked. PUT /customers/{customer_id}/unblock/.
The customer is assigned to other agent send_* The customer is assigned to an agent other than the token's. Send with the assigned agent's token, or unassign the customer first.
You can't send messages by WhatsApp 24h after the client's last message, try sending a template send_text, send_image, send_location The customer last wrote more than 24 hours ago on WhatsApp. POST /customers/{customer_id}/send_template/.
The customer has not made the opt in send_template, opt_out The customer has no active WhatsApp opt-in. Collect an opt-in first.
This customer is assigned to other agent assign, unassign, archive The token's agent isn't an admin, and the customer is assigned to another agent. assign also refuses a customer who already has any agent, and unassign one who has none. Use an admin agent's token, or PUT /customers/{customer_id}/assign/{agent_id}/.
The field <name> already exists POST /customers/{customer_id}/fields/{field_name}/ The Field exists. PUT the same URL instead.
Inactive channel POST /channels/{channel_id}/message_hooks/ The channel is turned off. Turn the channel back on in the dashboard.
Any other message under channel_id send_* The channel's provider refused the message, for example Temporary failure sending message to Facebook, please retry later. Read the message. Retry the temporary ones with backoff.

send_* covers send_text, send_image, send_location and send_template. Sending to an unassigned customer assigns them to the token's agent, so a second agent's token gets The customer is assigned to other agent from then on.

Trailing slash

Every Platform API and APIchat path ends with /. A request without it is redirected with a 301 to the same path with the slash. Most HTTP clients follow that redirect as a GET and drop the body, so a POST without the slash fails in confusing ways. Write the slash.

Retries

  • Retry 429 and 5xx without changing the request. Wait for Retry-After on a 429 (or back off when it is missing), and back off exponentially with jitter on a 5xx.
  • Retry 412 once the state it names has changed.
  • Fix the request before retrying 400, 404, 405, 409, 413 and 422.
  • Fix the token, the role or the plan before retrying 401, 402 and 403.
async function call(url, options, attempts = 5, maxWaitSeconds = 60) {
  for (let i = 0; i < attempts; i++) {
    const res = await fetch(url, options);
    const retryable = res.status === 429 || res.status >= 500;
    if (!retryable || i === attempts - 1) return res;
    const retryAfter = Number(res.headers.get("retry-after"));
    if (retryAfter > maxWaitSeconds) return res;
    const wait = retryAfter > 0
      ? retryAfter * 1000
      : Math.min(2 ** i * 100, 5000) + Math.random() * 100;
    await new Promise(r => setTimeout(r, wait));
  }
}

Duplicate requests

No Landbot API takes an idempotency key. A POST that times out on your side may or may not have run, and retrying it can send the same message twice.

Most state changes on the Platform API are PUT requests (archive, block, assign), and repeating one leaves the same state. For the send endpoints, decide whether a possible duplicate is worse than a possible gap, and retry only when it isn't.