Hay dos capas, al estilo Stream/Sendbird: la app declara el techo; cada conversación activa un subconjunto. Bloquear y silenciar son de la persona, no de la sala.
Techo de la Chat App
El producto declara lo que puede existir:
PATCH /v1/chat/apps/{id} (alcance chat:apps:manage) cambia el techo. No existe “la conversación activó vídeo si la app no tiene vídeo”.
Interruptor en la conversación
Serializado en GET/list:
Crear una conversación aplica el default de la app; el body puede pasar un subconjunto. PATCH /v1/chat/conversations/{id} cambia el interruptor y emite conversation.updated (WebSocket) y chat.conversation.updated (webhook).
Quién cambia la conversación
- API Key: siempre
AGENT: siempre
GROUP + OWNER (y ADMIN): sí
USER en DIRECT: solo si allowUserManageCapabilities está activo
Envío rechazado
El composer de la app del cliente solo oculta botones. Quien manda es el servidor:
- Tipo fuera de la sala o del techo →
CHAT_CAPABILITY_DISABLED (403)
- DIRECT con bloqueo entre los
USER → CHAT_USER_BLOCKED (403)
El agente también respeta la sala. Lo mismo en REST y WebSocket (message.send con type + mediaUrl). recording.start exige audio activo.
Ejemplo de soporte: la app permite texto, imagen, audio y archivo. La conversación del pedido nace solo texto. El agente (o la API Key) activa media en esa sala. Apagar después vuelve a rechazar IMAGE / AUDIO / FILE.
Silenciar
POST /v1/chat/conversations/{id}/mute
- JWT: silencia a sí mismo
- API Key: envía
memberExternalUserId
mutedUntil nulo o muted: false lo apaga. El miembro silenciado sigue enviando y leyendo; el campo va en members[].mutedUntil. Evento WebSocket: member.updated.
Bloquear
Por Chat App (PUT / DELETE / GET /v1/chat/blocks/{externalUserId}).
- No crea DIRECT con el bloqueado
- No añade al bloqueado a un grupo (y no entra si
joinPolicy=OPEN)
- DIRECT ya existente: envío rechazado en ambos sentidos
- No borra el historial
USER bloquea USER. No se puede bloquear a un AGENT
- Con API Key, envía
chatAppId y blockerExternalUserId
Eventos: WebSocket user.blocked / user.unblocked en las DIRECT afectadas; webhooks chat.user.blocked / chat.user.unblocked.
Quitar un miembro sigue siendo “sacar de la sala”, no un bloqueo global.
Siguientes pasos