> For the complete documentation index, see [llms.txt](https://docs.maiagent.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.maiagent.ai/tech/en/serve/line-flex-message.md).

# LINE Flex Message Specifications

How MaiAgent sends LINE Flex Message cards: the FlexMessage tool's template and AI field format, the validation endpoint, flex items in streamed replies, and the limits and failure handling when deliv

This page is for engineers who want an Agent to reply with LINE Flex Message cards: how card templates are stored, how the Agent fills in fields, how the platform delivers cards to LINE, and what a card looks like in an API response.

For admin console steps (where to add a card, how to apply a built-in template, how to bind it to an Agent), see [Agent UI](https://docs.maiagent.ai/build/tools/agent-ui) in the user manual. For LINE channel integration steps, see [Connect a Messaging Platform: LINE](https://docs.maiagent.ai/build/choose-channel/line). This page does not repeat those steps.

## How It Works <a href="#how-it-works" id="how-it-works"></a>

MaiAgent sends Flex cards through the **FlexMessage tool** (tool type `flex`, called FlexMessage under Agent UI in the admin console). One card = one tool:

1. You first save a native LINE Flex JSON template (`flexTemplateJson`) and specify which fields in it the AI should fill in (`flexSchema`).
2. Once the tool is bound to an Agent, each field in `flexSchema` becomes a string parameter of the tool, and the Agent decides what to fill in based on the field hints you wrote.
3. When the Agent calls the tool, the platform substitutes the parameter values into a copy of the template to produce the complete Flex JSON, and places it in the reply as a `flex` content item.
4. When the reply is sent to LINE, the platform converts the `flex` item into a LINE Flex Message; Web Chat renders the card directly; API callers receive the same JSON in `contentPayload.items`.

```mermaid
sequenceDiagram
    participant U as User
    participant A as Agent
    participant P as MaiAgent Platform
    participant L as LINE

    U->>A: "Where is my order?"
    A->>A: Write the reply text first, then call the card tool and fill in the fields
    A->>P: tool call (one string parameter per AI field)
    P->>P: Substitute into template → complete Flex JSON (flex content item)
    P->>L: Text message + Flex Message (same reply)
    L-->>U: Text and card
```

A card tool call ends the turn: the tool's output is not passed back to the model for rewriting, so write the reply text **before** calling the card.

{% hint style="info" %}
Cards are for presentation only. To show live data such as orders or inventory, configure a separate tool that can retrieve the data (for example, an [API tool](/tech/en/api-integration/api-tool-engineering-guide.md)), so the Agent first looks up the data and then fills the values into the card fields.
{% endhint %}

## Template and AI Fields <a href="#template-and-fields" id="template-and-fields"></a>

### Template `flexTemplateJson` <a href="#template" id="template"></a>

* It stores the `contents` of a LINE Flex Message: a `bubble` or `carousel` container (not the outer message object that contains `type: "flex"` and `altText`). The platform parses it with the official LINE SDK's `FlexContainer`.
* It must be a JSON object; a FlexMessage tool cannot exist without a template.
* Nested keys are stored as-is. API field names are always camelCase, but keys inside `flexTemplateJson` and `flexSchema` (for example `backgroundColor`, `paddingAll`) are not converted, so write them exactly as the LINE specification defines.

### AI Fields `flexSchema` <a href="#ai-fields" id="ai-fields"></a>

`flexSchema` is a JSON object. Each key is the path of a field in the template, and each value is a field hint for the AI:

| Format                                                                | Description                                                                                                  |
| --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `"body.contents[0].text": "Order number"`                             | The value is a string: used directly as the field hint                                                       |
| `"body.contents[0].text": {"prompt": "Order number", "tag": "title"}` | The value is an object: `prompt` is the field hint, and `tag` is prepended to the hint as a `[title]` prefix |

Rules:

* **Path syntax**: use `.` to separate levels and `[index]` to pick an array element, for example `body.contents[0].text` and `footer.contents[0].action.uri`.
* **Parameter names**: characters in the path other than letters, digits, and underscores are replaced with `_` and collapsed. For example, `body.contents[0].text` becomes the parameter `body_contents_0_text`. Duplicate names get `_2`, `_3`, and so on, in order.
* **Type**: every AI field is a string parameter that defaults to an empty string. Do not expose numeric or boolean fields to the AI.
* **Empty values keep the default**: if the Agent leaves a field unfilled or fills it with an empty string, the original value in the template is kept.
* **Paths that do not resolve are skipped**: if the template was changed and a path no longer exists, that field is not written and no error is raised. After changing a template, check `flexSchema` as well.

Example: an order status card where the title and the tracking link are filled by the AI and everything else is fixed.

```json
{
  "flexTemplateJson": {
    "type": "bubble",
    "body": {
      "type": "box",
      "layout": "vertical",
      "contents": [
        { "type": "text", "text": "Order A000", "weight": "bold", "size": "lg" },
        { "type": "text", "text": "Tap the button below to view delivery progress", "size": "sm", "wrap": true }
      ]
    },
    "footer": {
      "type": "box",
      "layout": "vertical",
      "contents": [
        {
          "type": "button",
          "style": "primary",
          "action": { "type": "uri", "label": "View progress", "uri": "https://example.com/orders" }
        }
      ]
    }
  },
  "flexSchema": {
    "body.contents[0].text": { "prompt": "\"Order \" followed by the order number", "tag": "title" },
    "footer.contents[0].action.uri": "Full URL of the lookup page for this order (starting with https)"
  }
}
```

The model sees two parameters: `body_contents_0_text` and `footer_contents_0_action_uri`.

## Creating and Validating Cards via the API <a href="#api" id="api"></a>

### Create and Manage <a href="#create-and-manage" id="create-and-manage"></a>

FlexMessage tools use the dedicated Agent UI endpoint `/api/v1/agent-ui-tools/` (list, create, update, delete). They cannot be created through the general tool endpoint (it returns `403`). The caller needs access permission to the Agent UI page.

| Field                  | Description                                                                                                                                  |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `toolType`             | `flex`. This endpoint defaults to `flex` when omitted                                                                                        |
| `name`                 | The tool name the model uses to call it; must be unique within an organization; `snake_case` is recommended                                  |
| `displayName`          | Display name in the admin console                                                                                                            |
| `description`／`prompt` | Provide at least one; `prompt` takes priority as the tool description given to the model, so state clearly **when** this card should be sent |
| `flexTemplateJson`     | The template; see [the previous section](#template)                                                                                          |
| `flexSchema`           | AI fields; optional                                                                                                                          |

When saving, the platform runs a structure check on the template with the LINE SDK (without connecting to LINE). If it fails, the response is `400` with a `detail` of "Invalid FlexMessage template: …" followed by the reason (missing fields are listed by name). The check runs again only when the template content differs from the stored one, so changing only the name or group is not blocked by an old template.

Once created, bind the tool to an Agent (the Agent's `tools` field), as with other tools, for it to take effect. The Agent must use Agent mode; see [Bind to an Agent](https://docs.maiagent.ai/build/tools/agent-ui#bind-to-chatbot) in the user manual. For the full field list, see the [API documentation](https://docs.maiagent.ai/api).

### Validate with LINE's Official Endpoint <a href="#validate" id="validate"></a>

The structure check at save time only confirms that the LINE SDK can parse the template (missing fields or fields the SDK cannot parse). It cannot tell whether LINE will reject the card (for example, an invalid URL format or out-of-range field values). Use the validation endpoint before going live:

```http
POST /api/v1/agent-ui-tools/validate-flex/
Authorization: Api-Key <your API key>
Content-Type: application/json

{ "flexTemplateJson": { "type": "bubble", "body": { … } } }
```

Response:

```json
{
  "isValid": false,
  "details": [{ "property": "/footer/contents/0/action/uri", "message": "invalid uri" }],
  "source": "line",
  "tokenSource": "platform"
}
```

| Field         | Description                                                                                                                                                                                                                                                                                                         |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `isValid`     | Whether validation passed                                                                                                                                                                                                                                                                                           |
| `details`     | Per-field errors; `property` is the JSON path of the error, in the same format as LINE's error responses                                                                                                                                                                                                            |
| `source`      | `line`: determined by LINE's official validation endpoint; `local`: not sent to LINE. Either the template failed the structure check (in which case `isValid` is `false` and it is not sent to LINE even if a token is available), or no usable LINE token was available and only the structure check was performed |
| `tokenSource` | Which token was used to call LINE: `platform` (provided by the platform) or `org_channel` (the token of your organization's LINE channel); `null` when `source` is `local`                                                                                                                                          |

Details:

* The call uses LINE's `validate_push`: it only validates, sends no message, and does not consume push quota.
* The template must pass the structure check before it is sent to LINE.
* The platform's validation token is tried first; if there is none or LINE rejects it (`401`), the tokens of your organization's connected LINE channels are tried in order. If there are none (or all are rejected), the response is `source: "local"`.
* The timeout for a single call to LINE is 10 seconds. On a LINE timeout, a `5xx`, or a network error, the response is `503`; the platform does not silently fall back to the weaker local check, so retry later.
* LINE's verdict for identical JSON is cached for 10 minutes.
* Each user can make at most 30 calls per minute; beyond that, the response is `429`. The API key's own per-minute limit also applies.

## The `flex` Content Item in Replies <a href="#content-item" id="content-item"></a>

When you receive replies through the API (streaming or synchronous; see [Conversations and Message Replies](/tech/en/api-integration/dui-hua-yu-xun-xi-hui-fu-chuan-liu-tong-bu.md)), cards appear in `contentPayload.items`:

```json
{
  "type": "flex",
  "flexMessage": { "type": "bubble", "body": { … } },
  "isStreaming": false,
  "startTimestamp": "2026-10-12T10:00:00+08:00"
}
```

* `flexMessage` is the complete container (`bubble` or `carousel`) with the AI field values already substituted. You can pass it directly to the LINE SDK or your own card renderer.
* Only handle items with `isStreaming: false` and a `flexMessage`. Items with `isStreaming: true` carry only a `delta` (a JSON text fragment); wait for the completed item before rendering.
* A reply can contain multiple cards; present them in `items` order.
* In `contentPayload.content` (the plain-text aggregation of all items), cards appear as JSON strings. If you only want to display text, use the items with `type: "text"` instead.

## Delivery Rules for LINE <a href="#line-delivery" id="line-delivery"></a>

The platform converts a reply's `contentPayload.items` into LINE message objects in order: text items become text messages and `flex` items become Flex Messages, all sent in the same reply.

| Item                | Rule                                                                                                                                           |
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| Message count       | At most 5 per send (LINE's limit); anything beyond that is not sent                                                                            |
| Text length         | A single text longer than 5,000 characters is split into multiple messages, which count toward the 5-message limit                             |
| `altText`           | The first 400 characters of all text items in the reply concatenated; if the reply has no text, "View details" is used (translated per locale) |
| Quick reply buttons | Attached to the last message                                                                                                                   |
| Group quotes        | In group conversations, the quote is attached only to text messages; Flex Messages carry no quote                                              |

`altText` is shown in LINE notifications and the chat list preview, so have the Agent write a sentence of explanation before the card so that the preview has content.

### When the Agent Sends Only a Card with No Text <a href="#text-with-cards" id="text-with-cards"></a>

Some models call the card tool directly without writing any text, so the user sees only a card with no explanation. When the Agent setting `requireTextWithFlexCards` is `true` and a turn contains only cards (or cards plus quick reply buttons) with no text at all, the platform appends a prompt and asks the model for another reply, requiring it to write text before sending the card.

* At most one retry per turn; the retry's result is used even if the model still sends only a card.
* A retry is a separate, billed model call.
* It is off by default, because some Agents are designed to reply with just a single menu card.

### Other Channels <a href="#other-channels" id="other-channels"></a>

Only Web Chat and LINE render `flex` items as cards. Other channels (for example Messenger and Instagram) have no corresponding conversion; cards sent by a sub-Agent in Agent Teams are not delivered on these channels, and the card's visible text is passed to the upstream Agent to relay instead. For Agents used across channels, do not make a card the only content of a reply.

## Failures and Error Handling <a href="#errors" id="errors"></a>

| Situation                                                                           | Platform behavior                                                                                                                                                                   | Recommendation                                                                                                                        |
| ----------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| The template structure is invalid on create or update                               | `400`, with the reason in `detail`                                                                                                                                                  | Fix the template and resubmit                                                                                                         |
| LINE is unreachable during validation                                               | `503`                                                                                                                                                                               | Retry later                                                                                                                           |
| The SDK cannot parse a card before sending                                          | That card is skipped (the error is logged) and the remaining messages are sent; if no sendable message remains in the whole reply, the text content of that message is sent instead | Check the template with the [validation endpoint](#validate) before going live                                                        |
| LINE rejects the reply (`400`, for example because the AI filled in an invalid URL) | The same batch of messages is resent as a push; if LINE rejects it again with a `4xx`, it is not retried, and the reply (including the text in the same batch) is not delivered     | Have the AI fill in only fields that are unlikely to go wrong; describe the format of URL fields clearly, or fix them in the template |

{% hint style="warning" %}
The validation endpoint validates only the template itself. Values filled in by the AI are substituted at conversation time; for example, if the AI fills in an invalid URL, LINE may still reject the whole batch of messages. For fields with strict format requirements (`uri`, colors, sizes), fix them in the template and do not expose them to the AI.
{% endhint %}

## Cards in Conversation Memory <a href="#memory" id="memory"></a>

After a card is sent, what is written into that turn's conversation memory is not the whole JSON but the text visible on the card: `text`, `span`, button `label`, and `altText`, arranged in LINE's display order (header → hero → body → footer), with a length limit. In later conversation, the Agent can refer to what is on the card, but cannot read fields that are not displayed, such as button URLs.

## Pre-Launch Checklist <a href="#checklist" id="checklist"></a>

* [ ] The template is a `bubble` or `carousel` container and passes the [validation endpoint](#validate) (`source` is `line`).
* [ ] `flexSchema` exposes only text-type fields to the AI; after changing the template, confirm the paths still resolve.
* [ ] The tool's `prompt` states clearly when to use it and requires writing text before sending the card; enable `requireTextWithFlexCards` if needed.
* [ ] The text plus cards in one reply do not exceed 5 messages.
* [ ] Test on a real LINE device: card display, button actions, and notification preview text.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.maiagent.ai/tech/en/serve/line-flex-message.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
