edit_of_message_id and never calls the branch route sees one linear transcript in the order messages were stored.
All three accept an organization API key or a scoped key with
conversations:use.
1. Revise a message
Take a normal turn, and addedit_of_message_id naming the user message the new text replaces. message is required with it.
- Stores the new text as a sibling version of the target message, not as a new message at the end of the transcript.
- Leaves the superseded exchange out of the history the model sees, so the reply answers the revised message alone.
- Streams the reply exactly like any other turn, over the same frames. See Turns and streaming.
- Serves the new branch from then on.
conversation frame carries user_message_id, the id of the message this turn stored. It is the first frame of the stream unless the message carries attachments, in which case an attachment frame comes first. Keep user_message_id if you want to offer a second revision without refetching the conversation.
Only a user message can be revised. An assistant message target returns 400 invalid_edit_target.
2. Render the version switcher
Fetch the conversation as usual. Every message carriesparent_message_id, and a message that has sibling versions also carries branch.
Draw
branch as a version switcher under the message: the pair index and count reads as 2/2, and the previous and next controls address siblings[index - 2] and siblings[index]. Absence of the field is the whole “no fork here” signal, so branch on presence rather than counting anything yourself.
The transcript a fetch returns is the branch the conversation currently serves, not every version: its ancestors, then the newest reply at each step below. Superseded versions are reachable through branch.siblings and are kept in full by the conversation export, which keeps every version.
3. Switch versions
Post the id of any message on the branch you want served. The server resolves that message’s ancestry, follows the newest reply from there, records the selection, and returns the whole conversation detail payload for that branch.200 body is the same shape the conversation GET returns (conversation, messages, truncated), already assembled for the selected branch, so one request both switches and returns what to render.
What persists
The selection is stored on the conversation, not in your client:- A later fetch returns the selected branch.
- The next turn continues the selected branch, and its messages are stored there.
- A reload, a second device, or a different client of the same conversation all see the branch the conversation currently serves.
Errors
pending_turn fires for both kinds of pause: an open confirmation and an open client tool handoff. Its details.pause_kind is confirm or handoff, and details.pending_call_ids lists the calls to resolve. See Client tools and confirmations.
Two client-side consequences follow from the last two rows. A turn cannot be cancelled, so disable the revise action while a turn streams rather than sending into a turn_in_progress refusal. And a paused turn must be resolved before a revision is accepted, so let the user answer the confirmation or let your application post the tool results first.
The branch route does not take the turn lock, so a switch and a running turn are not serialized against each other. Disable the switcher while a turn streams.
Compatibility
Both fields and the branch route are additive:edit_of_message_idis optional on the turn request.branchis present only at a fork.- Not calling the branch route means the conversation keeps serving the branch its last edit created, which for a conversation with no edits is the original single thread.