Group chat messages
Reference for the messages a moderator client sends and receives to run a group chat room over the Live APIs, including fields, errors, and turn results.
These messages exist only in a room created with group_chat: true. They travel over the LiveKit data channel of that room, alongside the messages documented on the Client-to-server messages and Server-to-client messages pages. Only a human participant of the room may send group-address; this page calls that participant the moderator. A room can hold more than one human when it was created with max_num_participants above its default of 1; the extra humans join as described in Use multi-character sessions. Envelope conventions follow the Message Glossary, with two differences: a group chat command also carries a top-level label and id, and turn-complete arrives without the top-level label field.
Commands you send
group-address
Addresses the room for one turn. Only the moderator may send it, and only over the LiveKit data channel.
{
"label": "rtvi-ai",
"type": "group-address",
"id": "turn-0001",
"data": {
"room_session_id": "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
"mode": "all",
"expected_route_epoch": 0,
"text": "Introduce yourselves in one sentence each."
}
}label
string
No
Conventional RTVI label, "rtvi-ai". The server ignores it; send it for consistency with other client messages.
id
string
Yes
Command id, 1–128 characters. Must be unique across every command you send in this room; group-address, interaction-target, and character-roster-update share one id space.
data.room_session_id
UUID
Yes
The room, from the /connect response.
data.mode
string
Yes
Addressing mode: "all", "tagged", or "open". See the table below this one.
data.target_membership_ids
UUID[]
"tagged" only
At least one live membership_id, with no duplicates. Omit it for "all" and "open" — a list sent with either mode is ignored, and the turn still addresses every instance in the room.
data.expected_route_epoch
integer
Yes
The latest route_epoch you hold, from /connect or from extras.route_epoch of the last group-address, interaction-target, or character-roster-update response. Removing the active instance bumps the epoch.
data.text
string
Yes
The moderator's message.
Addressing modes
mode
Who is addressed
Reply obliged
"all"
Every character instance in the room.
Yes. A decline is reported as a failure.
"tagged"
Only the instances listed in target_membership_ids.
Yes. A decline is reported as a failure.
"open"
Every character instance in the room.
No. An instance may decline and is then reported as passed.
Server response extras
The server answers every group-address with exactly one server-response carrying event_type: "group-address". On a command it accepts, that response arrives after every addressed instance has finished; a rejected command is answered straight away with status: "error". A turn also produces server-response messages you did not send, with event_type "context-update" or "user_text_message", one or more per addressed instance; ignore any server-response whose event_type is not one of your own commands. On status: "success", extras carries:
command_id
string
The id echoed from the request.
room_session_id
UUID
The room.
mode
string
The addressing mode used for this turn.
route_epoch
integer
The room's current epoch. Send this value as expected_route_epoch on the next command.
target_membership_ids
UUID[]
The instances this turn addressed.
answered_membership_ids
UUID[]
The instances that produced a reply.
passed_membership_ids
UUID[]
The instances that declined. Only an "open" turn produces entries here.
failed_membership_ids
UUID[]
The instances that did not produce a reply. A timeout is reported here.
summary.turn_id
string
The turn identifier, equal to command_id.
summary.addressed
integer
Number of instances addressed.
summary.answered
integer
Number of instances that replied.
summary.passed
integer
Number of instances that declined.
summary.failed
integer
Number of instances that did not reply.
summary.cancelled
boolean
Present in every summary. Cancellation is not reachable, so the value is always false.
summary.route_epoch
integer
The route epoch captured when the turn started.
status: "success" means the turn settled, not that anyone answered. A turn in which every instance failed or timed out still returns success. Read failed_membership_ids to find out what happened.
On status: "error", extras carries code. Errors the room itself raises — stale_route_epoch, invalid_target_membership, duplicate_target_membership, empty_target_set on an "all" or "open" turn, turn_in_progress, turn_wait_timeout, and command_id_conflict — also carry command_id, room_session_id, active_membership_id, and route_epoch. Errors rejected before the command reaches the room — not_roster_room, not_group_chat_room, unauthorized_sender, invalid_command, wrong_room, invalid_route_epoch, empty_message, empty_target_set on a "tagged" turn, and coordinator_unavailable — carry code only, so correlate them with the command you have outstanding rather than with extras.command_id. A failed group-address-part frame is answered with code and command_id. An unexpected server-side failure answers with status: "error" and a message but no extras, so read extras.code with optional access and fall back to message.
Error codes
code
When
What to do
not_roster_room
The room was created with character_id, not characters.
Create the room with characters.
not_group_chat_room
The room was created without group_chat: true.
Create a new room with group_chat: true.
unauthorized_sender
The sender is not a human participant of this room.
Send the command from a human participant's connection.
invalid_command
On a group-address, id is missing or over 128 characters, data is not an object, or mode is not one of the three values; or a group-address-part frame is malformed.
Fix the message and send it with a new id.
wrong_room
data.room_session_id is not this room.
Use the room_session_id from /connect.
invalid_route_epoch
expected_route_epoch is not an integer.
Send an integer.
stale_route_epoch
expected_route_epoch does not match the room's epoch.
Read extras.route_epoch from this response and resend with a new id.
empty_message
text is empty.
Send text.
empty_target_set
"tagged" was sent with no targets, or "all" / "open" was sent to a room with no live instances.
List at least one membership_id, or wait for an instance to become ready.
invalid_target_membership
A target is not a live membership_id in this room, or is not a UUID.
Use membership_id values from /connect, bot-ready, or the roster response.
duplicate_target_membership
The same id appears twice in target_membership_ids.
Remove the duplicate.
turn_in_progress
Your command was refused immediately because a turn was running.
Wait for the turn's server-response, then send again.
turn_wait_timeout
Your command waited for the running turn, which did not settle within the turn timeout plus a short margin, about two minutes by default.
Send again.
command_id_conflict
The id was reused with different content.
Use a new id.
coordinator_unavailable
The room is no longer serviceable.
Create a new room.
Idempotency
Resending the same id with the same content is accepted silently and produces no second response. Do not wait for one; rely on the first response. Resending the same id with different content is refused with command_id_conflict.
One turn at a time
A room runs one turn at a time. A second group-address sent while a turn is running is refused with turn_in_progress and is not cached, so retry it after the turn's server-response. interaction-target and character-roster-update are refused with the same code during a turn.
group-address-part
Carries one frame of a group-address that is too large to send whole. The LiveKit data channel refuses a message over 15,360 bytes.
label
string
No
Conventional RTVI label, "rtvi-ai". The server ignores it.
id
string
Yes
The command id of the group-address being sent, 1–128 characters. Identical on every frame.
data.command_id
string
No
When present, it must equal id.
data.index
integer
Yes
Zero-based position of this frame, from 0 to count - 1.
data.count
integer
Yes
Total number of frames, 1 to 64. Identical on every frame.
data.payload
string
Yes
One base64 slice of the complete group-address JSON.
Chunking rules
Base64-encode the complete
group-addressJSON message once, then split that string across the frames; eachpayloadis one slice of it.The server concatenates the slices in
indexorder and decodes them once.Only the last slice may carry
=padding. The server concatenates the slices before it decodes, so padding anywhere but at the end fails the whole command. Split the encoded string at any point; the server rebuilds it exactly. Never base64-encode each frame separately, which puts padding in the middle and fails the whole command.The base64 payloads for one command total at most 1 MiB, which is roughly 768 KiB of
group-addressJSON.A malformed or over-size frame fails that command with
event_type: "group-address",status: "error", andcode: "invalid_command". Two failures are silent instead: a frame whoseidis missing or over 128 characters is dropped because there is nothing to correlate a reply to, and a partly delivered command is abandoned if it is not complete within a minute of its first frame. In both cases the turn never completes, so time out on the client side, and send every frame of one command promptly.The reassembled message is handled exactly like a
group-addresssent with thatid.
Messages you receive
turn-complete
Ends a group chat turn and reports its outcome. turn-complete reports the whole group turn; bot-turn-completed is per character and is unchanged in a group chat room.
room_session_id
UUID
The room.
command_id
string
The id of the group-address that opened the turn.
mode
string
The addressing mode used for this turn.
addressed_membership_ids
UUID[]
The instances this turn addressed. Named target_membership_ids in the server-response extras.
answered_membership_ids
UUID[]
The instances that produced a reply.
passed_membership_ids
UUID[]
The instances that declined. Only an "open" turn produces entries here.
failed_membership_ids
UUID[]
The instances that did not produce a reply. An instance that has not finished within the turn timeout is listed here.
turn_id
string
The turn identifier, equal to command_id.
addressed
integer
Number of instances addressed.
answered
integer
Number of instances that replied.
passed
integer
Number of instances that declined.
failed
integer
Number of instances that did not reply.
cancelled
boolean
Present on every turn-complete. Cancellation is not reachable, so the value is always false.
route_epoch
integer
The route epoch captured when the turn started.
Four properties of this message affect how a client consumes it:
turn-completehas no top-levellabel, unlike most server messages. Do not filter incoming messages onlabel.turn-completeis sent at most once per turn, however many character instances are in the room. It is published through one instance's connection and is not retried, so treat theserver-responseas the reliable end of the turn. Itsdatamay also carry the publishing instance's identity fields; ignore them and read the lists.turn-completecan arrive before an instance's lastbot-llm-textdelta and before theserver-response. Keep accepting reply text for a short grace period after it — one to two seconds is enough in practice.route_epochhere is the turn's snapshot, not the value to send next. Sendextras.route_epochfrom theserver-responseas the nextexpected_route_epoch.
Identity fields on per-character messages
Server messages in a room created with a characters array (which every group chat room is) carry six identity fields that tie them to a character instance: room_session_id, membership_id, character_id, character_session_id, participant_identity, and is_initial. Where those fields sit depends on the shape of the message. A message that already carries a data object receives them inside that object. A flat message receives them inside a data object created for them, which produces a nested data.data path.
data
data
data
bot-ready
data.about, mirrored in data
character-status
data
character-removed
data
Nested data object
Nested data object
Nested data object; the useful fields stay in extras
Each instance streams its reply as ordinary bot-llm-text messages. Key the reply by data.membership_id. bot-llm-stopped ends one instance's reply.
Do not also consume bot-transcription; it repeats the same reply text.
An instance that declines an "open" turn announces it with llm-no-response and reason: "abstain". Because that message is flat, its identity fields sit under a nested data object:
turn-complete is the authority, not this message. If the instance streamed any visible text before it declined, the reply wins and the instance is listed in answered_membership_ids. An instance can also be listed in passed_membership_ids without ever sending llm-no-response. Treat llm-no-response as a hint and settle the outcome from the turn-complete lists.
bot-ready in a group chat room
bot-ready is the message described under Map each character instance. In a group chat room, its data.about object usually carries one field beyond the ones documented there: display_name, the name to show for that instance. The field is best effort. When it is absent, fall back to the display_name on that instance's /connect roster entry, which carries the character's plain name without the room-unique suffix described next, so disambiguate the fallback yourself.
Display names are made unique within the room: a second instance of a character named Ada is given the display name Ada (2). A character-status message with status: "ready" carries the same fields flat in its data object.
Related pages
Build a group chatUse multi-character sessionsClient-to-server messagesServer-to-client messagesLast updated
Was this helpful?