Skip to content

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.

  • x-genui-version header pins the project version this turn serves, for example v2. 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 as unsupported_version; a missing or malformed header will also return an error.
  • message is 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.
  • action is what the user just pressed, sent instead of message when 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 same conversationId on the following turns. The GenUI Portal binds it to the subject the 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 a format and a version, 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 rejects v0.9.1, so it needs the turn served as v0.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 as unsupported_version.

A successful turn returns the intended interface for your frontend to render, plus the context you need to render and to debug it:

  • output is the interface to render, tagged with its format. output.format and output.version echo the format you were served, a2ui and 0.9.1 by default. output.payload is the UI itself: for A2UI v0.9.1, an ordered list of A2UI messages, a createSurface that opens a surface followed by an updateComponents that 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, and List, see below).
  • servedVersion is the version the turn was served from, echoing your x-genui-version pin verbatim. You can assert it against your pin to confirm you got the version you built against.
  • conversationId is 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.

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" }
}
}
  • name and context come 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.
  • surfaceId and sourceComponentId say which surface and which component the press came from.
  • timestamp is 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.

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.

  • unauthorized means 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 different subject, 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.
  • blocked means 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_action means 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_result means the turn produced nothing to render.
  • invalid_output means 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 the x-trace-id.
  • unavailable means 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_version means the number you pinned in x-genui-version names 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.