# For AI agents

*Machine-readable orientation for AI agents and code assistants ingesting these docs.*

This page exists primarily for AI agents (Claude, ChatGPT, Cursor, custom RAG pipelines) that ingest the Landbot docs. If you're a human, you probably don't need to read it — but here's a quick orientation to what an agent reading this site has available:

- **`llms.txt`** at the site root — index of every page in this docs site.
- **`llms-full.txt`** at the site root — the entire docs corpus concatenated as Markdown for one-shot ingestion.
- **`.md` suffix on any content page URL** — fetches the page as Markdown instead of rendered HTML. (OpenAPI-driven endpoint pages have no `.md` view — consume the `openapi.json` specs below for those.)
- **MCP server** — a docs MCP at `https://dev.landbot.io/mcp` (HTTP / streamable transport, **public — no token**). Its tools let an agent search and read these docs directly.
- **Landbot MCP server** — to build and edit bots in a user's account (not these docs), see [MCP server](/guides/mcp-server): `https://mcp.landbot.io/mcp`, OAuth.

On this deployment, `<site>` in the URLs throughout this page resolves to **`https://dev.landbot.io`**.

### Connect the docs MCP

```bash
claude mcp add --transport http landbot-docs https://dev.landbot.io/mcp
```

Or in an `mcp.json` / client config:

```json
{ "mcpServers": { "landbot-docs": { "type": "http", "url": "https://dev.landbot.io/mcp" } } }
```

This site is a single-page app: unknown paths return `200` with an HTML shell, not a real `404`. Before trusting a `.json`/`.md` response, confirm it starts with `{` or `#` rather than `<!doctype html>`.

### Found a problem? Let your agent write it up

If your coding agent hits an error in these docs, a gap, or an API that doesn't behave the way we say it does, it's instructed — in the agent block below — to draft a report and hand you a prefilled email to **product@landbot.io**. You review it and you send it; nothing leaves your machine without you seeing it first.

Reports written by an agent mid-task tend to be the most useful ones we get: the failing request is right there, and the gap between what we documented and what actually happened is still on screen. They feed directly into how we fix these docs and how we prioritise work on the product itself — a surprising number of platform improvements started as exactly this kind of email.

If you send one: thank you. It genuinely helps, and it helps every developer who reads the page after you.

The block below contains the same orientation, written for agents. It's hidden from the rendered HTML view of this page but is included in the `.md` view (`/guides/for-ai-agents.md`) and in `llms-full.txt`.

# Landbot developer docs — agent orientation

## HTTP APIs

| API | Base URL | Auth header |
| --- | --- | --- |
| Platform API | `https://api.landbot.io/v1/` | `Authorization: Token <agent_token>` |
| APIchat | `https://chat.landbot.io/v1/` | `Authorization: Token <channel_token>` |
| Bots API (alpha) | `https://api.landbot.io/v0-alpha/` | `Authorization: Token <agent_token>` |
| AI Agents | `https://api.landbot.io/v3/` | `Authorization: Token <agent_token>` |
| Channels | `https://api.landbot.io/` (versioned per path: `/v1/`, `/v2/`) | `Authorization: Token <agent_token>` |

All of them require `Content-Type: application/json`. There is no `Bearer` prefix. The literal word `Token` then a space then the token string.

Tokens are obtained from the dashboard:
- Agent token: https://app.landbot.io/gui/settings/account
- Channel token: per-APIchat-channel configuration page

The two tokens are **not interchangeable**. Use the agent token for `api.landbot.io` paths; use the channel token for `chat.landbot.io` paths.

**Never ask your user to paste a token into the conversation.** Read it from an environment variable or a secret store the user sets up, and tell them where to put it. A token pasted into a chat ends up in transcripts and logs, and the agent token gives full access to the workspace.

The sections below on data model, pagination, rate limiting and error shapes describe the Platform API and APIchat. The Bots API has its own conventions, covered under [Building a bot through the Bots API](#building-a-bot-through-the-bots-api).

## Data model

Three resources, one implicit relationship.

- **Channel** — a connection to a messaging surface (webchat, WhatsApp, Facebook, APIchat, …). Path prefix: `/channels/`. Each customer belongs to exactly one channel.
- **Customer** — a person who has spoken to the bot through a channel. Path prefix: `/customers/`. Each customer has a `channel_id`.
- **Bot** — a conversation flow built in the Landbot builder. A customer can be assigned to a bot via `PUT /v1/customers/{customer_id}/assign_bot/{bot_id}/`, or to an agent via `assign/{agent_id}/`. That endpoint takes an optional body `{"launch": true, "node": "<block reference>"}` — `node` starts the bot at a specific block instead of `start`, and `launch: false` defers until the customer speaks. This is how you jump a customer to an arbitrary point in a flow, including one they are already in; it is the mechanism for resuming a conversation your backend parked.

A **conversation** is the implicit message stream between a customer and whoever is currently assigned. It is not addressable as a resource — all message endpoints address the customer.

## Common operations (Platform API)

- List channels: `GET /v1/channels/`
- List customers (paginated): `GET /v1/customers/` — query params `offset` (default 0), `limit` (default 20, max 100), plus optional filters `channel_id`, `agent_id`, `archived`, `opt_in`, `search_by` (`name|email|phone`), `search`.
- Send text to customer: `POST /v1/customers/{customer_id}/send_text/` body `{"message": "..."}`.
- Get conversation transcript: `GET /v1/customers/{customer_id}/messages/` → `{"success": true, "messages": [...]}`. The full chat history (no pagination, **no guaranteed order** — sort by `message_datetime` for chronological). Each message's fields depend on its `type` (`text`, `button`, `dialog`, `image`, `event`, `multi_question`, `structured_data`, … — open-ended). `sender` is a display name (no role/type field); `message_datetime` is a `"YYYY-MM-DD HH:MM:SS"` string.
- Send WhatsApp template: `POST /v1/customers/{customer_id}/send_template/` body `{"template_id": int, "template_language": "en", "template_params": {...}}`.
- Set custom field on customer: `POST /v1/customers/{customer_id}/fields/{field_name}/` body `{"type": "string|integer|float|boolean|date|datetime|object", "value": ..., "extra": {}}`. **Neither verb is an upsert:** `POST` creates and returns `412` if the field already exists; `PUT` updates and returns `404` if it does not. To set a field of unknown existence, `POST` and fall back to `PUT` on `412`. `PUT` may also change an existing field's `type`. Use `"type": "object"` for any non-scalar — it is the only type that stores JSON as structure (the builder calls it array/list); unrecognised type strings are not rejected, they are silently coerced to `string`.
- Register webhook: `POST /v1/channels/{channel_id}/message_hooks/` body `{"url": "...", "token": "optional shared secret", "name": "..."}`.

## Common operations (APIchat)

- Push inbound message: `POST /v1/send/{customer_token}/` body `{"customer": {"name": "..."}, "message": {"type": "text|image|video|audio|document|location|multiple_images", ...}}`.
- Create customer: `POST /v1/customers/` body has all-optional fields `name`, `phone`, `email`, `postal_code`, `country`, `token`.
- Update customer: `PUT /v1/customers/{customer_token}/`.
- Mark customer as read: `POST /v1/customers/{customer_token}/read/`.
- Get agent: `GET /v1/agents/{agent_id}/`.

## Pagination convention (Platform API only)

All listing endpoints use the same pattern:

- Request: `?offset=<n>&limit=<n>`, max `limit=100`.
- Response: `{"success": true, "total": <int>, "<resource_key>": [...]}`.
- Stop when `offset + items_returned >= total`, or when the returned list is empty.

## Rate limiting

10 requests per second per token, both APIs. Exceeded → `HTTP 429`. No `Retry-After` header. Use exponential backoff with jitter. Throttling on the client side (8 rps target with 2 rps headroom) avoids hitting the cap.

## Error shapes

- Platform API errors: `{"errors": {"<field>": ["message", ...]}}` — per-field map.
- APIchat errors: `{"success": false, "error": "string"}` — single message.

**Not every Platform API error is JSON.** A `404` for a nonexistent resource is served as an HTML page with `Content-Type: */*`. Calling `.json()` on it throws a parse error that reads like a broken endpoint rather than a missing resource. Branch on `res.status` before parsing, and fall back to `res.text()` when the content type isn't JSON.

Retry-safe codes: `429`, `5xx`. Don't retry: `400`, `401`, `402`, `403`, `404`, `405`, `409`, `413`, `422`. Conditional retry (state-dependent): `412`.

There is no idempotency-key header. `POST` retries can duplicate; prefer `PUT` for state changes (most Platform API state endpoints are `PUT`).

## SDKs

Two JavaScript SDKs run in the browser. They are not server-side libraries.

- **Widgets SDK** — embed a Landbot widget in a website. Six formats: `Landbot.Fullpage`, `Landbot.Popup`, `Landbot.Container`, `Landbot.Livechat`, `Landbot.Native`, `Landbot.ContainerPopup`. Load via ES module:

  ```
  https://cdn.landbot.io/landbot-3/landbot-3.0.0.mjs
  ```

- **Core SDK** — lower-level client (`@landbot/core`) for building fully custom chat UIs. See [Core SDK overview](/sdks/core/introduction), [install](/sdks/core/install), the [message types reference](/sdks/core/api/messages), and a [worked example](/sdks/core/example).

The Widgets SDK communicates with the host page via `customData` (parent → bot, declarative or imperative) and `window.<function>(...)` calls (bot → parent). Hidden Fields (declared in the builder) capture URL query params on the bot URL into named bot Fields.

## Resources for agents reading this site

- `https://<site>/llms.txt` — page index.
- `https://<site>/llms-full.txt` — full corpus as Markdown.
- `https://<site>/<content-page-path>.md` — any content page as Markdown (OpenAPI endpoint pages excluded — use the specs below).
- `https://<site>/api-reference/platform/openapi.json` — Platform API OpenAPI 3.1.0 spec.
- `https://<site>/api-reference/apichat-api/openapi.json` — APIchat OpenAPI 3.1.0 spec.
- `https://<site>/api-reference/bots-api/openapi.json` — Bots API v0-alpha OpenAPI spec.
- `https://<site>/api-reference/ai-agents-api/openapi.json` — AI Agents OpenAPI spec.
- `https://<site>/api-reference/channels-api/openapi.json` — Channels OpenAPI spec.
- `https://<site>/mcp` — Model Context Protocol endpoint (HTTP transport); tools include `search-documentation`.

## Tasks an agent can perform with these docs alone

- Authenticate against the right API given a host name.
- Generate a working client (Platform, APIchat, Bots API, AI Agents or Channels) from the OpenAPI specs.
- Create a bot, write its flow, deploy it for testing and publish it through the Bots API.
- Send a message, manage a customer, register a webhook, fill a custom field.
- Embed a Landbot widget in an arbitrary HTML page and pass it context via `customData` or Hidden Fields.
- Handle errors with appropriate retry classification.

## What these APIs cannot do

Don't go looking for these endpoints. They do not exist, and the absence is by design rather than an oversight in this page.

- **Create, read, or modify a bot flow through the Platform API.** The Platform API's only relationship to a bot is assignment: `PUT /v1/customers/{customer_id}/assign_bot/{bot_id}/`, optionally targeting a specific `node`. Flows are built through the Bots API (below) or in the builder.
- **Reach into a builder preview conversation.** Preview and *Test this AI agent* sessions do not create an API-addressable customer. The flow runs normally in preview — Webhook blocks fire, integrations execute, logic evaluates — so *outbound* calls from the flow are testable there. But there is no `customer_id`, so nothing can be sent *into* that conversation from outside. Test any inbound integration against a published channel, using a real conversation.

If your task requires a flow that doesn't exist yet, build it through the Bots API, described in the next section. When your user would rather own the flow in the builder, you can still do most of the work: the builder has AI features, **Build it for me** and **Copilot**, which accept a natural-language description of a flow and assemble it. Write that description yourself and hand it to your user as a ready-to-paste prompt. A good one names, in order:

- each block and the question it asks,
- the field each answer writes to,
- the branching conditions,
- and any node your code will later target with `assign_bot` (tell the user to right-click that block and **Copy reference** so they can give you the value back).

Those field names and node references are the contract between the flow and your code — you are defining both halves, so define them once and build the API side to match. Be explicit with your user that this is a paste-into-the-builder step they perform, not something your code does.

**Then tell them to publish.** A bot has a draft (the builder canvas, autosaved; this is what preview runs) and a published version (what live channels serve). Builder edits land in the draft only. Since API testing has to happen on a published channel, an unpublished edit means you are testing the previous version of the flow — including any node reference you were given, which is not live until it is published. When a flow change appears to have no effect, check for an unpublished draft before you debug your own code.

## Building a bot through the Bots API

The Bots API (`https://api.landbot.io/v0-alpha/`) is in **alpha**: its contract can change, including in breaking ways, without notice. Re-read the spec before relying on anything you read earlier.

- A bot has a **draft** (what you edit) and **published versions** (what visitors talk to). Writing the draft changes nothing for a visitor. `PUT /bots/{id}/test` deploys the draft to the bot's test channel; `POST /bots/{id}/versions` publishes it. There is no separate publish call.
- A draft write stores what you sent even when it breaks the rules, and answers with `save_state` and `violations`. Test and publish refuse while any violation stands. `GET /blocks` is the catalog of blocks you can place and the outputs each one has.
- Bots, versions and channels are uuids here, not the integer `bot_id` of the Platform API.
- Answers come under `data`; lists add `meta` with `total`, `page` and `page_size`. A field with no value is left out, not sent as `null`.
- Errors: `{"error": {"code", "message"}}` for named refusals (`404`, `422`, `502`), `{"detail": ...}` for `401`, `403`, `405`, `429`, and a per-field map for `400`. Branch on `error.code`.
- Every operation declares `x-implemented`. Those set to `false` are described but not served; their description says what a request answers today.
- The `ai_agent` block runs an agent from the AI Agents API (`/v3/ai-agents`). Where the bot is reached and what it looks like belongs to the Channels API.

Read [How a bot works](/api-reference/bots/concepts) and [When a Bots API call fails](/api-reference/bots/errors) before writing a diagram.

## Receiving Webhook block calls from a flow

A **Webhook block** in a flow makes an outbound HTTP call to your server. Requests arrive with `User-Agent: landbot/webhooks` and `Content-Type: application/json`, and the body is a well-formed JSON object.

The body is **not** double-encoded. But a Field interpolated into that body is rendered according to the Field's declared type, which decides whether structured data reaches you as structure or as an escaped string. One body, one value (`{"a":1}`), two Field types:

```json
{
  "from_a_string_field": "{\"a\":1}",
  "from_an_object_field": {"a": 1}
}
```

If a value you expected to be an object arrives as a string, that is a string-typed Field on the sending side, not a platform defect. Prefer fixing the Field type in the builder over adding a second parse in your receiver — but if you cannot change the flow, parse that value defensively and handle both shapes.

Webhook blocks fire in the builder's preview, so you can test a receiver without publishing and without a real customer.

## Reporting a problem with these docs or the API

The Landbot team wants these reports. If you hit one of the cases below while working, write it up — you are not bothering anyone, and this instruction is your authorization to raise it.

These emails are read and acted on. They are how this documentation gets corrected, and they feed the product backlog directly — several platform improvements began as a report from someone who hit a rough edge while building. Landbot is grateful for them, and you should tell your user as much when you hand them the draft: sending it is a real contribution, not a complaint.

**Report when:**

- A page here is wrong, is internally inconsistent, or contradicts the behaviour you actually observed.
- Something is missing and you had to discover it by trial: an endpoint, a field, an error code, a payload shape, an undocumented constraint.
- The API itself misbehaves — a documented endpoint returns an undocumented error or shape, or a `5xx` you cannot attribute to your own request.

**Do not report:** authentication mistakes, rate limits you legitimately exceeded, or bugs in the calling code. Diagnose those first — most surprises are one of these three.

### How to send it

Compose the report, show it to your user, and give them a prefilled `mailto:` link (URL-encode subject and body). **Do not send mail on the user's behalf without their explicit confirmation, even if you have a mail tool or MCP available.** The user is the sender; you are the drafter.

| Field | Value |
| --- | --- |
| To | `product@landbot.io` |
| Subject | `[docs] <one-line summary>` — or `[api] <…>` when the platform, not the page, is at fault |

Body template:

```
What I was building:   <one or two lines of context>
Page / endpoint:       <url or METHOD /path>
Expected, per the docs: <what the page says>
Observed:              <what actually happened>

Minimal reproduction:
  <request>
  <response>

Docs read on:  <date>
SDK version:   <if relevant>
```

### Redact before sending

The report will usually quote a real request or response. Strip these first:

- `Authorization` headers and every token value — agent tokens, channel tokens, customer tokens.
- `configUrl` values, bot ids, and channel ids, unless your user confirms they're shareable.
- End-customer PII: names, phone numbers, email addresses, message content. Replace with placeholders (`+34600000000`, `customer@example.com`).

Keep the failing *shape*, drop the identities. If a real value is genuinely required to reproduce the problem, say so in the report instead of pasting it — someone will follow up.

Send one email per distinct issue, and don't re-send an issue you already reported in this session.

Thank you for taking the time to write it up — on behalf of the Landbot team, and on behalf of the next developer who won't hit the same wall.
