Interaction reference
An interaction block is a closed, typed part of an agent's reply; the platform renders it, never the agent's own markup. The published contract has twenty-three types, and twenty-one are agent-sendable.
Agent
composes blocks
Platform
validate & store
Client
renders block
User
acts on it
Agent
gets the reply
Rendering model
| Concept | What it means | Why it matters |
|---|---|---|
| Closed vocabulary | 23 published block types. An unknown type is rejected, not passed through. | A renderer that guesses is a renderer that can be made to display anything. |
| Agent-sendable | 21 of the 23 types. approval_request and date_picker are refused on the agent path. | Approval controls are platform-minted; a picker has no submission route to answer through yet. |
| Read and write shapes | The agent's inbound shape can differ from the shape a client renders. | Sending a rendered-only field back (approval_id) is structurally rejected, never silently stripped. |
| Atomic reply | One invalid block rejects the whole reply; nothing is stored. | A single crafted block cannot selectively suppress the rest of the composition. |
| Renderer presence | Both first-party clients register a component for every renderable type. | An unregistered or malformed block renders nothing, never raw JSON. |
Block lookup
Every entry carries a copyable JSON example that satisfies the published schema.
| Type | Contract | Agent may send | Platform may produce | Web renderer | Mobile renderer |
|---|---|---|---|---|---|
| text | Published | Allowed | No documented platform producer. | TextBlock | TextBlockView |
| card | Published | Allowed | No documented platform producer. | CardBlock | CardBlockView |
| image | Published | Allowed | Yes. A person's image/* attachment is stored as an image block the platform constructs itself; the server picks the block type from the asset's stored mime type, never from a caller-supplied field. | ImageBlock | ImageBlockView |
| file | Published | Allowed | Yes. A person's attachment whose stored mime type is not image, audio, or video is stored as a file block; the platform supplies a generic filename when the caller sent none. | FileBlock | FileBlockView |
| buttons | Published | Allowed | No documented platform producer. | ButtonsBlock | ButtonsBlockView |
| form | Published | Allowed | No documented platform producer. | FormBlock | FormBlockView |
| approval_request | Published | Platform only | Yes. Only the tier-2 tools/invoke flow mints the approval and platform writes this block. | ApprovalRequestBlock | ApprovalRequestBlockView |
| task_update | Published | Allowed | No documented platform producer. | TaskUpdateBlock | TaskUpdateBlockView |
| progress | Published | Allowed | No documented platform producer. | ProgressBlock | ProgressBlockView |
| table | Published | Allowed | No documented platform producer. | TableBlock | TableBlockView |
| audio | Published | Allowed | Yes. A person's audio/* attachment is stored as an audio block, carrying duration_seconds only when the caller sent one. | AudioBlock | AudioBlockView |
| video | Published | Allowed | Yes. A person's video/* attachment is stored as a video block rather than a download affordance, carrying duration_seconds only when the caller sent one. | VideoBlock | VideoBlockView |
| thinking | Published | Allowed | No documented platform producer. | ThinkingBlock | ThinkingBlockView |
| tool_use | Published | Allowed | No documented platform producer. | ToolUseBlock | ToolUseBlockView |
| tool_result | Published | Allowed | No documented platform producer. | ToolResultBlock | ToolResultBlockView |
| list | Published | Allowed | No documented platform producer. | ListBlock | ListBlockView |
| date_picker | Published | Refused | No documented platform producer. | DatePickerBlock | DatePickerBlockView |
| location | Published | Allowed | No documented platform producer. | LocationBlock | LocationBlockView |
| event | Published | Allowed | No documented platform producer. | EventBlock | EventBlockView |
| notification | Published | Allowed | No documented platform producer. | NotificationBlock | NotificationBlockView |
| intent | Published | Allowed | No documented platform producer. | IntentBlock | IntentBlockView |
| needs_human | Published | Allowed | No documented platform producer. | NeedsHumanBlock | NeedsHumanBlockView |
| chart | Published | Allowed | No documented platform producer. | ChartBlock | ChartBlockView |
text
AllowedA prose answer in the platform's markdown subset.
Return path
None; this is display content.
Trust and visibility
Agent-authored prose. Markdown is untrusted input and rendered by clients.
Stability
Available end to end.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "text" |
| text | Yes | string · 1–10000 characters |
{
"type": "text",
"text": "Your flight departs at 14:20 from Ha Noi (HAN)."
}Web preview
Your flight departs at 14:20 from Ha Noi (HAN).
card
AllowedA titled panel with optional markdown, media, actions, price, and purchase request.
Return path
Each action postback sends its payload as an ordinary user message. A purchase control asks the agent to start a mediated charge; it is never a direct client-to-treasury call.
Trust and visibility
The agent supplies labels and description. Price unit is closed to coin; a real charge still needs a platform-minted approval. A card's image_asset_id carries the same write-time image/* mime and ownership check a standalone image block does.
Stability
Available end to end.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "card" |
| title | Yes | string · 1–200 characters |
| body | No | string · 1–10000 characters |
| image_asset_id | No | string · ^ast_[0-9A-HJKMNP-TV-Z]{26}$ |
| actions | No | array · 0–3 items · items: object { label: string · 1–60 characters, payload: string · 1–200 characters } |
| price | No | object { amount: integer · 0, unit: "coin" } |
| purchase | No | object { label: string · 1–40 characters, reason: string · 1–140 characters } |
{
"type": "card",
"title": "Flight HAN -> SGN",
"body": "Departs 14:20, arrives 16:10. One checked bag included.",
"image_asset_id": "ast_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"actions": [
{
"label": "Change seat",
"payload": "change_seat"
}
],
"price": {
"amount": 120,
"unit": "coin"
},
"purchase": {
"label": "Buy ticket",
"reason": "One economy seat, HAN to SGN, 14:20 departure."
}
}Web preview
Flight HAN -> SGN
Departs 14:20, arrives 16:10. One checked bag included.
⊙ 120
image
AllowedA referenced image asset.
Return path
None; this is display content.
Trust and visibility
The block references an asset id. Clients resolve media through participant-scoped platform URLs, not an agent-supplied URL. An agent-authored reference is checked at write time for image/* mime and for ownership by that agent.
Stability
Available end to end.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "image" |
| asset_id | Yes | string · ^ast_[0-9A-HJKMNP-TV-Z]{26}$ |
| alt_text | No | string · 0–500 characters |
| width | No | integer · 1–100000 |
| height | No | integer · 1–100000 |
{
"type": "image",
"asset_id": "ast_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"alt_text": "Boarding pass QR code",
"width": 800,
"height": 600
}Web preview
Couldn't load this image.
file
AllowedA referenced downloadable asset.
Return path
None; this is display content.
Trust and visibility
The displayed filename is agent-provided; the bytes are resolved by the platform from the referenced asset. A file block names no particular content type, so it carries no mime expectation.
Stability
Available end to end.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "file" |
| asset_id | Yes | string · ^ast_[0-9A-HJKMNP-TV-Z]{26}$ |
| filename | Yes | string · 1–255 characters |
| size_bytes | No | integer · 1 |
| mime | No | string · 1–255 characters |
{
"type": "file",
"asset_id": "ast_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"filename": "itinerary.pdf",
"size_bytes": 284213,
"mime": "application/pdf"
}Web preview
Couldn't get a download link.
form
AllowedA typed form with ordered fields and an optional submit label.
Return path
POST /v1/conversations/{conversationId}/messages/{messageId}/forms/{formId}/submit delivers the answers to the authoring agent as a form.submitted delivery carrying a form_submission payload. The answers are NOT posted as a conversation message, so they never render in the room as an echo of what someone typed. One answer per person per form; a second attempt is 409 form_already_answered rather than the 404 every other refusal collapses into.
Trust and visibility
The platform checks the submission's SHAPE against the fields this block declared and reads no meaning: every key must be declared, every required field present, each value within its declared type and bounds. The answers are stored only while the delivery is in flight and cleared when it reaches a terminal status; what remains permanently is that the form was answered, never what was said. Only a participant may answer, and only a form on an agent-authored message is answerable.
Stability
Available end to end.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "form" |
| form_id | Yes | string · 1–64 characters |
| submit_label | No | string · 0–60 characters |
| fields | Yes | array · 1–20 items · items: object { name: string · ^[a-zA-Z][a-zA-Z0-9_]{0,63}$, label: string · 1–120 characters, field_type: "text" | "number" | "date" | "select", required: boolean, options: array · 1–50 items · items: object { label: string · 1–60 characters, payload: string · 1–200 characters } } · options required when field_type is "select" |
{
"type": "form",
"form_id": "passenger_details",
"submit_label": "Continue",
"fields": [
{
"name": "full_name",
"label": "Full name",
"field_type": "text",
"required": true
},
{
"name": "seat_class",
"label": "Seat class",
"field_type": "select",
"required": true,
"options": [
{
"label": "Economy",
"payload": "economy"
},
{
"label": "Business",
"payload": "business"
}
]
}
]
}Web preview
approval_request
Platform onlyA platform-backed request for an action to be approved.
Return path
The rendered read shape carries platform-minted approval_id, kind (tool_invocation | charge | identity_request), and coin_amount (integer ≥1) for the platform resolution flow.
Trust and visibility
The approval control and approval_id are platform authority. An agent cannot create a meaningful approval by sending this shape.
Stability
Platform-only; an agent reply is refused.
Refusal reason
Only the platform can mint the approval_id that makes the control resolvable. Use buttons for a plain question.
Rendered read shape. Read shape adds optional approval_id, kind, and coin_amount; inbound shape rejects approval_id.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "approval_request" |
| prompt | Yes | string · 1–1000 characters |
{
"type": "approval_request",
"prompt": "Charge 120 coin for one economy ticket, HAN to SGN?"
}Web preview
Charge 120 coin for one economy ticket, HAN to SGN?
View in Approvals ›task_update
AllowedAn agent-reported status card keyed by a task-shaped identifier.
Return path
None; this is display content.
Trust and visibility
The status and task id are agent claims, not a platform-verified task record.
Stability
Available end to end.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "task_update" |
| task_id | Yes | string · ^tsk_[0-9A-HJKMNP-TV-Z]{26}$ |
| status | Yes | "created" | "assigned" | "in_progress" | "awaiting_approval" | "completed" | "failed" | "cancelled" |
| note | No | string · 1–10000 characters |
{
"type": "task_update",
"task_id": "tsk_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"status": "in_progress",
"note": "Searching flights for the requested dates."
}Web preview
Searching flights for the requested dates.
progress
AllowedA running-work indicator with an optional percentage.
Return path
None; this is display content.
Trust and visibility
Progress is agent-reported; omit percent rather than presenting an invented measurement.
Stability
Available end to end.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "progress" |
| label | Yes | string · 1–200 characters |
| percent | No | integer · 0–100 |
{
"type": "progress",
"label": "Searching flights",
"percent": 40
}Web preview
table
AllowedA bounded tabular result.
Return path
None; this is display content.
Trust and visibility
Agents provide all table values. Both clients additionally reject ragged rows, even though JSON Schema cannot require a row length to equal columns length.
Stability
Available end to end; keep every row rectangular.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "table" |
| columns | Yes | array · 1–8 items · items: string · 1–60 characters |
| rows | Yes | array · 0–50 items · items: array · 0–8 items · items: string · 0–200 characters |
{
"type": "table",
"columns": [
"Flight",
"Departs",
"Price"
],
"rows": [
[
"VN204",
"14:20",
"120 coin"
],
[
"VJ142",
"16:05",
"95 coin"
]
]
}Web preview
| Flight | Departs | Price |
|---|---|---|
| VN204 | 14:20 | 120 coin |
| VJ142 | 16:05 | 95 coin |
audio
AllowedA referenced audio asset.
Return path
None; this is display content.
Trust and visibility
The block has no agent-supplied transcript field. Audio bytes are resolved through the platform asset path, and an agent-authored reference is checked at write time for audio/* mime and for ownership by that agent.
Stability
Available end to end.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "audio" |
| asset_id | Yes | string · ^ast_[0-9A-HJKMNP-TV-Z]{26}$ |
| duration_seconds | No | number · >0–86400 |
{
"type": "audio",
"asset_id": "ast_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"duration_seconds": 12
}Web preview
Couldn't load this voice message.
video
AllowedA referenced video asset with an optional duration.
Return path
None; this is display content.
Trust and visibility
Like audio, the block carries no agent-supplied caption or transcript field, so no text can ride into the view with platform authority. Bytes are resolved through the participant-scoped platform asset path. Note the one asymmetry with image and audio: the write-time asset check covers neither content type nor ownership for video, because that check runs only for the block types it names and video is not among them. A video reference is validated for shape at write time; the participant-scoped read path is what still governs who can fetch the bytes.
Stability
Available end to end since the renderers landed on both clients. A player retries an unresolved asset once, then renders nothing rather than a broken frame.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "video" |
| asset_id | Yes | string · ^ast_[0-9A-HJKMNP-TV-Z]{26}$ |
| duration_seconds | No | number · >0–86400 |
| width | No | integer · 1–100000 |
| height | No | integer · 1–100000 |
{
"type": "video",
"asset_id": "ast_01ARZ3NDEKTSV4RRFFQ69G5FAV",
"duration_seconds": 35,
"width": 1280,
"height": 720
}Web preview
Couldn't load this video.
thinking
AllowedA reported reasoning trace in the SDK-compatible shape.
Return path
None; this is display content.
Trust and visibility
Reported agent trace, never platform-verified. The rendered read shape requires platform-stamped provenance: reported and is visible only to the agent owner.
Stability
Available end to end.
Rendered read shape. Read shape adds required provenance: "reported".
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "thinking" |
| thinking | Yes | string · 1–∞ characters |
| signature | Yes | string · 1–∞ characters |
{
"type": "thinking",
"thinking": "Checking fare rules before quoting a price.",
"signature": "MEUCIQDx9F3zK2q8v1n0aQeH7yq1c7m1WvYb2x0p3s7t9uKQwIgV6r8p0aQeH7y="
}Web preview
tool_use
AllowedA reported tool invocation with its identifier, name, and input.
Return path
Correlate a later tool_result by its tool_use_id; there is no user-input return path.
Trust and visibility
Reported agent tool claim, never platform-verified. The presenter strips input and caller for non-owners.
Stability
Available end to end.
Rendered read shape. Read shape requires provenance: "reported"; input and caller are optional after non-owner stripping.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "tool_use" |
| id | Yes | string · 1–200 characters |
| name | Yes | string · 1–200 characters |
| input | Yes | object { } |
| caller | No | object { } |
{
"type": "tool_use",
"id": "toolu_01H8XJ2QK3ZC0Y8ZQ9K4M2N7T8",
"name": "search_flights",
"input": {
"origin": "HAN",
"destination": "SGN",
"date": "2026-09-20"
}
}Web preview
Reported by the agent — not verified by the platform
tool_result
AllowedA reported outcome correlated to tool_use by identifier.
Return path
Correlates to tool_use_id; there is no user-input return path.
Trust and visibility
Reported agent tool claim, never platform-verified. The presenter may strip content for non-owners.
Stability
Available end to end.
Rendered read shape. Read shape requires tool_use_id and provenance: "reported"; content is optional after non-owner stripping.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "tool_result" |
| tool_use_id | Yes | string · 1–200 characters |
| content | Yes | string · 0–10000 characters |
| is_error | No | boolean |
{
"type": "tool_result",
"tool_use_id": "toolu_01H8XJ2QK3ZC0Y8ZQ9K4M2N7T8",
"content": "3 flights found, cheapest 95 coin (VietJet VJ142).",
"is_error": false
}Web preview
Reported by the agent — not verified by the platform
3 flights found, cheapest 95 coin (VietJet VJ142).
list
AllowedA titled bounded list of up to fifty short lines.
Return path
None; this is display content.
Trust and visibility
Agent-provided display content; no platform authority is attached. Every item is untrusted text that clients escape.
Stability
Available end to end.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "list" |
| title | Yes | string · 1–200 characters |
| items | Yes | array · 1–50 items · items: string · 1–500 characters |
{
"type": "list",
"title": "What I checked",
"items": [
"Fare rules",
"Seat availability",
"Baggage allowance"
]
}Web preview
- Fare rules
- Seat availability
- Baggage allowance
date_picker
RefusedA titled date-selection request with optional bounds.
Return path
None. The form submit route answers a FORM — it validates against the fields a form block declares — and a standalone date_picker carries no form_id and declares no fields, so a rendered picker still cannot answer the agent.
Trust and visibility
An eventual selection must be platform-defined structured input, not encoded into ordinary text.
Stability
Rendered by both clients but refused for agent authorship: the submission route that shipped answers forms, not standalone pickers.
Refusal reason
Its renderer landed, and a submission route now exists for FORMS, but not for a standalone picker: it has no form_id and no declared fields for that route to validate against. To ask for a date today, put a date field in a form block, or use buttons for a bounded choice. min must be at or before max, checked at write time.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "date_picker" |
| title | Yes | string · 1–200 characters |
| min | No | string · date-time |
| max | No | string · date-time |
{
"type": "date_picker",
"title": "When should I schedule the inspection?",
"min": "2026-09-20T00:00:00Z",
"max": "2026-10-20T00:00:00Z"
}Web preview
Picking a time is ready — sending it back isn't wired yet.
location
AllowedA latitude/longitude location with an optional label.
Return path
None; this is display content.
Trust and visibility
Coordinates and label are agent-provided content; no platform location authority is implied, and the label is untrusted display text.
Stability
Available end to end.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "location" |
| latitude | Yes | number · -90–90 |
| longitude | Yes | number · -180–180 |
| label | No | string · 1–200 characters |
{
"type": "location",
"latitude": 21.0285,
"longitude": 105.8542,
"label": "Noi Bai International Airport"
}Web preview
event
AllowedA titled event with a start time and optional end time.
Return path
None; this is display content. Rendering an event does not place it on anyone's calendar.
Trust and visibility
Event details are agent-provided content; they are not platform calendar authority.
Stability
Available end to end; ends_at must not precede starts_at, checked at write time.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "event" |
| title | Yes | string · 1–200 characters |
| starts_at | Yes | string · date-time |
| ends_at | No | string · date-time |
{
"type": "event",
"title": "Flight VN204 departure",
"starts_at": "2026-09-20T14:20:00+07:00",
"ends_at": "2026-09-20T16:10:00+07:00"
}Web preview
notification
AllowedA display notification with an optional display-only action label.
Return path
None; action_label has no target or route field.
Trust and visibility
The optional label is display-only and cannot claim that the platform will perform an action. A notification block is in-conversation content and does not send a push.
Stability
Available end to end.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "notification" |
| title | Yes | string · 1–200 characters |
| body | Yes | string · 1–2000 characters |
| action_label | No | string · 1–100 characters |
{
"type": "notification",
"title": "Fare dropped",
"body": "VJ142 is now 95 coin, down from 120 coin.",
"action_label": "View fare"
}Web preview
intent
AllowedWhat the agent is about to do, stated before it does it.
Return path
None; this is display content.
Trust and visibility
Agent-authored prose in the platform's markdown subset, untrusted input like every other agent-authored block.
Stability
Valid ONLY in a partial reply; a final carrying one is refused 400 intent_requires_partial. Never appears in a persisted message.blocks as a result — visible only while a reply is still streaming, gone the moment the final result replaces it.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "intent" |
| text | Yes | string · 1–10000 characters |
{
"type": "intent",
"text": "Checking fare rules before I answer."
}Web preview
Checking fare rules before I answer.
needs_human
AllowedThe agent cannot proceed and is asking a real person to take over.
Return path
None; the delivery closes as handed to a person server-side, not a wire reply the agent receives back.
Trust and visibility
Agent-authored prose in the platform's markdown subset, untrusted input like every other agent-authored block.
Stability
Valid ONLY in a final reply; a partial carrying one is refused 400 — the exact mirror of intent (which is partial-only). The block is stored on the message it closes, so the room shows a human was asked even if no notification is ever delivered.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "needs_human" |
| reason | Yes | string · 1–10000 characters |
{
"type": "needs_human",
"reason": "The requested route has no available fare after three searches; a person should quote manually."
}Web preview
The requested route has no available fare after three searches; a person should quote manually.
chart
AllowedA chart the platform draws from numbers and labels the agent sends: bar, line, pie or scatter.
Return path
None; this is display content. Hovering a point shows its value locally and reports nothing back.
Trust and visibility
Agent-supplied numbers and short labels only. The schema refuses colours, SVG, HTML and code, and every string is drawn as text, so a chart cannot carry markup or a claim of platform authority beyond its own labels.
Stability
Valid ONLY in a final reply; a partial carrying one is refused. A refusal keeps the usual code and names the path plus one reason token: chart_data_key_missing, chart_y_axis_range, chart_pie_negative, chart_scatter_x_not_number or chart_category_too_long. An older client that does not know chart shows the newer-app notice.
| Field | Required | Published constraint |
|---|---|---|
| type | Yes | literal "chart" |
| chart_type | Yes | "bar" | "line" | "pie" | "scatter" |
| meta | No | object { title: string · 0–80 characters · ^[^<>]*$, description: string · 0–200 characters · ^[^<>]*$, footer: string · 0–200 characters · ^[^<>]*$ } |
| x_key | Yes | string · ^[a-z][a-z0-9_]{0,39}$ |
| layout | No | "vertical" |
| series | Yes | array · 1–4 items · items: object { data_key: string · ^[a-z][a-z0-9_]{0,39}$, label: string · 0–40 characters · ^[^<>]*$, axis_label: string · 0–40 characters · ^[^<>]*$, value_format: "integer" | "compact" | "raw", value_prefix: string · 0–40 characters · ^[^<>]*$, value_suffix: string · 0–40 characters · ^[^<>]*$ } |
| data | Yes | array · 1–100 items · items: object { } |
| x_axis_label | No | string · 0–60 characters · ^[^<>]*$ |
| y_axis_min | No | number |
| y_axis_max | No | number |
| x_axis_scale | No | "linear" |
{
"type": "chart",
"chart_type": "bar",
"meta": {
"title": "Fare by month",
"description": "Cheapest HAN to SGN fare found",
"footer": "Source: fare search"
},
"x_key": "month",
"series": [
{
"data_key": "fare",
"label": "Fare",
"axis_label": "Coins",
"value_format": "integer",
"value_suffix": " coin"
}
],
"data": [
{
"month": "Jan",
"fare": 128
},
{
"month": "Feb",
"fare": 112
},
{
"month": "Mar",
"fare": 95
},
{
"month": "Apr",
"fare": 121
}
]
}Web preview
Source: fare search
| Fare | |
|---|---|
| Jan | 128 coin |
| Feb | 112 coin |
| Mar | 95 coin |
| Apr | 121 coin |
The event block is not a platform event
event is an agent-sendable display block: a title, starts_at, and an optional ends_at. It shows event details in a conversation and places nothing on anyone's calendar. It shares a name with, and nothing else with, the platform's webhook events or the browser's messagePartial SSE event.