confirm_card
Server-built approval for a write or destructive action.
The safety mechanism, drawn. Before a write or destructive action runs, the server synthesizes a confirm_card from your action manifest: the summary comes from your confirmTemplate, the parameters come from the invocation the server already validated, and the model never touches either.
That is the whole point. A hallucination cannot misstate what the user is approving, because the model did not write the card.
Re-rendered in the browser from the SDK’s own scales. Not a screenshot.
Rendered natively by the Flutter SDK and the Web SDK, both since 0.1.0.
When not to use it
You cannot use it. confirm_card is the one block type the model may not emit. It is absent from the render_ui tool by construction, and no host code creates one either.
If you want the user to choose between options, that is quick_replies or plan_comparison. If you want them to supply values, that is form. Confirmation is not a choice; it is an approval of something already fully specified.
Props
Every block also carries type and a required fallback_text of 1 to 300 characters.
| Prop | Type | Required | Constraints |
|---|---|---|---|
invocation_id | string | Yes | Server-assigned. |
action_name | string | Yes | The registered action this card approves. |
summary | string | Yes | 1 to 300 characters. Rendered from your confirmTemplate. |
params | object | Yes | Machine-readable. This is what the handler runs on. |
params_display | array | Yes | Up to 12 entries. Human-readable only. |
params_display[].label | string | Yes | Up to 60 characters. |
params_display[].value | string | Yes | Up to 200 characters. |
risk | write | destructive | Yes | read actions never produce a card. |
confirm | action | Yes | { kind: "confirm", invocation_id } or { kind: "cancel", invocation_id }. See Actions. The server sends confirm here. |
cancel | action | Yes | { kind: "confirm", invocation_id } or { kind: "cancel", invocation_id }. See Actions. The server sends cancel here. |
expires_at | string | No | ISO 8601 in UTC (...Z); a numeric offset fails validation. The server stamps every card, write and destructive alike, ten minutes out. Past it the SDK refuses to confirm and the card shows as expired. The server enforces it too, with 150 seconds of grace on top, because the handler runs on the device before the result is posted and the manifest lets a handler take up to 120. It measures from when the card was created, not from when the user pressed, and a decline is accepted however late. |
executes | server | No | server for a console action the API runs itself, as the signed-in user, once the confirm is recorded. The device has no handler for it: on confirm it records the decision and sends back a success result with no data, and the server answers with what happened. Absent for an action the device runs. |
On the wire
{
"id": "blk_09",
"type": "confirm_card",
"fallback_text": "Confirm cancelling order 1042.",
"props": {
"invocation_id": "inv_8f21",
"action_name": "cancel_order",
"summary": "Cancel order 1042 and refund $129 to the original card?",
"params": {
"order_id": "1042"
},
"params_display": [
{
"label": "order_id",
"value": "1042"
}
],
"risk": "destructive",
"confirm": {
"kind": "confirm",
"invocation_id": "inv_8f21"
},
"cancel": {
"kind": "cancel",
"invocation_id": "inv_8f21"
},
"expires_at": "2026-08-27T10:10:00Z"
}
}Interaction
Confirm runs your registered handler with params and posts the result as a new request. Go back posts a declined tool result, so the turn resolves either way and the thread never sits waiting on a card nobody answered.
params_display is a rendering. Every value in it has been through String() and truncated at 200 characters, so nothing may ever be reconstructed from it. The SDK runs params or it runs nothing. A card that somehow arrives without machine-readable params is reported as an error rather than executed on a guess.
Only confirm and cancel are legal here. The general UIAction union does not apply.
States
| State | What the SDK draws |
|---|---|
| Destructive | The plate's edge goes to full ink at 1.5pt and an eyebrow reads "This cannot be undone". The accent stays on the confirm button: weight carries the warning, so a host never has to own a red it did not choose. |
| Resolved | The buttons are replaced by "Confirmed" or "Not confirmed", which stays on the card. Scrolling back through the thread still shows exactly what was agreed to. |
| Not answered | The user asked something else while the card was open. Both ends treat that as an answer: the SDK marks the card "Not answered" before the new message goes out, and the server closes the invocation so the turn's history is valid. A card left behind is never confirmable again, so tapping it later cannot run a handler whose result nothing will accept. |
| Expired | The buttons are replaced by an "Expired" mark and the line "Nothing was done. Ask again to start it over." A destructive card also releases its raised edge, since there is nothing left to warn about. The card watches its own clock, so it changes the moment the deadline passes rather than at the next unrelated redraw. |
| Handler not registered on this device | The card resolves as declined and an error result is posted, rather than leaving the thread parked on a card that does nothing when tapped. |
| Loading | A plate with a short eyebrow line, two body lines and two 44pt button shapes, so the card does not double in height when it hydrates. |
Accessibility
The card is the most important thing a screen-reader user will hear in the thread. fallback_text is written by the server and states the action and its object. Both buttons are 44pt, and the resolution is announced as a live region so the outcome is spoken without the user hunting for it.