Skip to main content
A conversation lets an end user revise a message they already sent. The revision does not overwrite the original: it is stored as a second version of that message, the reply streams on the new branch, and the conversation serves that branch from then on. The user can switch back later. The server owns the lineage. It stores the parent of every message, assembles the branch a fetch returns, and records which branch the conversation currently serves. A client does not need to hold a message tree or compute an ordering. You adopt the capability through three touchpoints. A client that never sends 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 add edit_of_message_id naming the user message the new text replaces. message is required with it.
What the server does 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.
The 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 carries parent_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.
The 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.
Switching back to an earlier version serves that branch’s newest reply.

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_id is optional on the turn request.
  • branch is 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.
Last modified on September 25, 2026