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:
- Use
PUTwhere possible. Most state-change endpoints in the Platform API arePUT(archive, block, assign, ...) and are naturally idempotent — calling twice is the same as calling once. - 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.