The turn endpoint
A turn is one exchange with your GenUI experience, and the turn endpoint is the one runtime endpoint your frontend application calls to make it happen.
Your frontend application sends the newest user message, and the GenUI Portal returns the LLM’s interface choice for the frontend to render, described in A2UI.
The turn endpoint is project specific: POST /organization/{organizationId}/project/{projectId}/turn. It requires a minted session token from your backend. Connecting your application covers session tokens and the project allowlist, and Authentication covers how session tokens are verified.
What you send
Section titled “What you send”x-genui-versionheader pins the project version this turn serves, for examplev2. Your frontend explicitly states which version of the project it is built against, so an unsupported version never reaches your client. You can run against a draft version of your project, but keep in mind that unpublished project versions can change at any time. A version value that does not match any version of the project comes back asunsupported_version; a missing or malformed header will also return an error.messageis what the user just said. Send only the newest message, then the GenUI Portal holds the conversation history and replays it to the model for you.actionis what the user just pressed, sent instead ofmessagewhen a person taps a component that carries an action. Send exactly one of the two. See Sending an action below.conversationId(UUID) identifies the conversation. When a conversation starts and ends is your decision. If you send a UUID that the GenUI Portal has not seen before it will treat it as a new conversation. To keep the conversation going send that sameconversationIdon the following turns. The GenUI Portal binds it to thesubjectthe first time it sees it and echoes it back unchanged. Any UUID works. The GenUI Portal only checks that it is indeed a valid UUID value. Conversation IDs outlive session tokens, so refreshing authentication never drops the thread. If you want to start a new conversation then send a new UUID.output(optional) tells the GenUI Portal which format your frontend can render, so one project can serve different frontends. It carries aformatand aversion, for example{ "format": "a2ui", "version": "0.9.1" }. Omit it and the turn defaults to A2UI v0.9.1, the current stable A2UI release. If your frontend is built on the official flutter/genui SDK (a2ui_core), request{ "format": "a2ui", "version": "0.9" }instead: that SDK checks the A2UI version string and rejectsv0.9.1, so it needs the turn served asv0.9. The two are the same interface with a different version label, so you get identical output either way, tagged the way your client expects. A format or version the GenUI Portal does not serve comes back asunsupported_version.
What you get back
Section titled “What you get back”A successful turn returns the intended interface for your frontend to render, plus the context you need to render and to debug it:
outputis the interface to render, tagged with its format.output.formatandoutput.versionecho the format you were served,a2uiand0.9.1by default.output.payloadis the UI itself: for A2UI v0.9.1, an ordered list of A2UI messages, acreateSurfacethat opens a surface followed by anupdateComponentsthat fills it with a flat list of components. Please note that your frontend should provide actual visual implementations for all of the components available for the LLM through the project’s catalog, including the default components (Column,Row, andList, see below).servedVersionis the version the turn was served from, echoing yourx-genui-versionpin verbatim. You can assert it against your pin to confirm you got the version you built against.conversationIdis the conversation this turn belongs to, the same UUID you sent, echoed back so you can confirm the turn landed on the thread you meant.
Every response also carries an x-trace-id header. You do not need it for normal use, but it will be very useful for debugging.
Note that a message or an action only enters the history stack after it passes the inbound checks and gets a reply. A turn that fails or is refused is never added, so it can never become context for a later turn.
Sending an action
Section titled “Sending an action”A catalog item with an Action property lets the model put something pressable on screen. When a person presses it, your frontend sends that press as the next turn on the same conversation, in action instead of message. The Flutter SDK does this for you. If you call the endpoint yourself, send the action exactly as your A2UI renderer produced it:
{ "conversationId": "5f0c7a52-6f1e-4d8e-9a57-0d1d6f2c9e11", "action": { "name": "show_similar", "surfaceId": "s1", "sourceComponentId": "similar-button", "timestamp": "2026-09-24T10:15:00.000Z", "context": { "productId": "p-114" } }}nameandcontextcome from the action the model served on that component. A name is always an identifier: it starts with a letter and uses only letters, digits,_,.and-, up to 64 characters. A request whose name is anything else is refused with a 400 before the turn runs.surfaceIdandsourceComponentIdsay which surface and which component the press came from.timestampis when the press happened, by the device’s clock. It is kept with the conversation for the model’s benefit and is never used to order turns.
An action is checked against what the GenUI Portal actually served on that conversation. The surface and the component must exist, and the component must carry an action with that name. Every value in context that the model wrote into the served action must come back unchanged, and no value the model did not declare may be added. That check is what lets the model and your Logs tell a real press from text that merely looks like one.
Values the model wrote are not screened as if the person had said them, which keeps a legitimate press from being refused by a content check meant for free text. A context value the served action takes from the interface’s own data, rather than a value the model wrote, may carry something the person entered, so that value goes through the same inbound check a message does.
The model then sees the press in one fixed form and replies with the next interface. In your project’s Logs the turn is marked as an action, with its name.
Layout components you get for free
Section titled “Layout components you get for free”Every project’s catalog automatically includes three layout catalog items, Column, Row, and List, for the LLM to arrange the components you define. You don’t need to add them to your GenUI Portal project; they are always available, and the model reaches for them to lay out a turn. IMPORTANT: your frontend must provide actual implementations for them (as for all other catalog items) so they get properly rendered.
When a turn fails
Section titled “When a turn fails”unauthorizedmeans the session token was missing, expired, or refused, the request came from an origin or bundle ID your allowlist does not include, or the conversation ID sent belongs to a differentsubject, project or organization. Re-minting the token fixes an expiry and never fixes a disallowed origin, so retry once with a fresh token, then stop rather than minting in a loop.blockedmeans a guardrail stopped the turn, on the message you sent, on a value a person entered into an action, or on the model’s output. Which guardrail, and why, stays on the platform on purpose.invalid_actionmeans the action you sent does not match anything the GenUI Portal served on this conversation: an unknown surface or component, a different name, or context values that were changed or added. Which part did not match stays on the platform. Sending the same action again fails the same way.no_resultmeans the turn produced nothing to render.invalid_outputmeans the model produced output that was not valid A2UI for your catalog. It is a fault on our side, not yours, and often clears on a retry; fall back in the meantime, and if it persists it is worth reporting with thex-trace-id.unavailablemeans GenUI Portal could not serve the turn, usually because the model provider was unreachable. It is normally transient, so retry later and fall back in the meantime.unsupported_versionmeans the number you pinned inx-genui-versionnames no version of the project, or you asked for an output format we do not serve. Pin a version the project has, or drop back to a supported format.