Groups and channels
Create a GROUP such as “Morning squad” or a Channel such as announcements, and keep their members in line with your own data. Your backend decides who belongs; Chat enforces who may read and write.
Prerequisites
Section titled “Prerequisites”conversations.creategroups.create- Groups need
chat:conversations.manageandchat:groups.manage, which carry the permissions above. Channels needchat:conversations.manageandchat:channels.manage(conversations.createandchannels.create).chat:synccovers both. See Grant Chat scopes. - The
ChatClientfrom Create direct conversations.
-
Create the group or channel. A GROUP or CHANNEL needs a
name. Every member must be an eligible tenant member, or Chat answers 422target_user_not_eligible.actor_user_idis recorded as the creator and as the OWNER. -
Add and remove members.
addMembertakes a user id and optional profile hints (display_name,email), used only if Chat has no profile for that user yet. Removing a member works the same way. -
Change it.
updatechangesname,descriptionand, for a channel,visibility(PUBLICorPRIVATE). The default visibility isPUBLIC.
import { chat } from './guide-chat-client';
export async function createMorningSquad(coachId: string): Promise<string> { const group = await chat.conversations.create({ type: 'GROUP', name: 'Morning squad', member_user_ids: [coachId, 'client-john'], // every id must be an eligible tenant member actor_user_id: coachId, // recorded as the creator and OWNER });
// Profile hints are used only when Chat has no profile for the user yet. await chat.conversations.addMember(group.conversation_id, { user_id: 'client-sarah', display_name: 'Sarah Client' }); await chat.conversations.removeMember(group.conversation_id, 'client-john'); return group.conversation_id;}
export async function createAnnouncements(coachId: string): Promise<string> { const channel = await chat.channels.create({ name: 'announcements', visibility: 'PUBLIC', member_user_ids: [coachId], actor_user_id: coachId, }); await chat.conversations.update(channel.conversation_id, { description: 'News from the coaches' });
const channels = await chat.channels.list({ limit: 20 }); return channels.items.map((c) => c.name ?? '').join(', ');}curl -s -X POST "$CHAT_API_URL/tenants/$CCS_TENANT_ID/applications/$CCS_APPLICATION_ID/conversations" \ -H "Authorization: Bearer $CHAT_MACHINE_TOKEN" \ -H "Content-Type: application/json" \ -d '{"type":"GROUP","name":"Morning squad","member_user_ids":["coach-ana","client-john"],"actor_user_id":"coach-ana"}'{ "conversation_id": "0f6b3c2a-5d1e-4b7a-9c3d-2e8f1a4b6c70", "type": "GROUP", "name": "Morning squad", "status": "ACTIVE", "member_count": 2 }Who may manage members
Section titled “Who may manage members”| Conversation | Who may add or remove members |
|---|---|
| DIRECT | Nobody. Its members never change (direct_conversation_immutable). |
| GROUP | Your backend with groups.manage, or a user who is its OWNER or ADMIN and holds groups.create. |
| CHANNEL | Your backend with channels.manage. A user may also join a PUBLIC channel themselves. |
A user can always leave a group or channel. Membership roles (OWNER, ADMIN, MEMBER) are about content ownership, never about authorization on their own.
A PUBLIC channel is readable by every tenant user who holds conversations.read. Sending requires being a member.
Verify
Section titled “Verify”List the channels of your application and check the new one:
const { items } = await chat.channels.list({ limit: 20 }); // items[].name includes "announcements"Page through members with chat.conversations.listMembers(id); each page returns next_cursor to pass as before.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause and fix |
|---|---|
403 missing_capability on create |
The client lacks chat:groups.manage or chat:channels.manage. |
400 invalid_request |
name is missing, or member_user_ids is empty. |
422 direct_conversation_immutable |
You tried to add or remove members of a DIRECT conversation. |
422 target_user_not_eligible |
A user is not an active member with access to your application. |