Room Lifecycle
lead_underwriter with full permissions.
Participant Roles
Every participant in a deal room has a role that controls their capabilities within the room.| Role | Description | Default Permissions |
|---|---|---|
lead_underwriter | Primary underwriter driving the deal | Edit, annotate, chat |
underwriter | Supporting underwriter | Edit, annotate, chat |
broker | External broker with controlled access | Annotate, chat |
actuary | Actuarial reviewer | Annotate, chat |
manager | Supervisory oversight | Annotate, chat |
viewer | Read-only observer | View only |
canEdit, canAnnotate, and canChat can be toggled independently of role. External participants (brokers) can optionally be associated with an externalOrgId and externalOrgName.
API Endpoints
All endpoints are prefixed with/v1/deal-rooms and require authentication. Read access requires one of: underwriter, org_admin, superadmin, admin, producer, auditor. Write access requires: underwriter, org_admin, superadmin, admin.
Create a Deal Room
superadmin or system role.
List Deal Rooms
insuredName and lob for display. The status query parameter defaults to active and accepts active, archived, or closed.
Get Deal Room Details
left) are excluded. Each participant entry includes their name, email, role, permissions, notification preferences, and last-seen timestamp.
Add a Participant
Update a Deal Room
closed, the system records closedAt and closedBy automatically.
WebSocket Collaboration
Real-time features are powered by a Durable Object (DealRoomDO) that each room’s participants connect to via WebSocket.
Obtaining a WebSocket URL
Connection Flow
- Client requests a signed WebSocket URL via
GET /v1/deal-rooms/:id/ws. - Client opens a WebSocket connection to the returned
wsUrl. - The Durable Object verifies the JWT token (checks signature, expiration, and DO ID match).
- On successful auth, the server sends an
initmessage with room state, participants, last 50 messages, and all annotations. - If a Yjs CRDT state exists (shared notepad), it is sent as a binary frame immediately after init.
Message Types
Clients send JSON messages over the WebSocket. Each message has atype discriminator.
| Type | Direction | Description |
|---|---|---|
init | Server to client | Room state, participants, recent messages, annotations |
chat | Bidirectional | Text message, file share, or annotation-linked comment |
typing | Client to server | Typing indicator (broadcast to others) |
annotation | Client to server | Create a document or field annotation |
awareness | Client to server | Cursor position and selection state |
reaction | Client to server | Emoji reaction on a message |
Sending a Chat Message
parentMessageId, file attachments via richContent.attachments, and annotation targeting via annotationTarget (to link a message to a specific document field or coordinate).
Creating an Annotation
field, document, image, section. Annotations have a status lifecycle: open -> resolved or dismissed.
Typing Indicator
Server-Sent Events
The Durable Object broadcasts events to all connected clients (excluding the sender where appropriate):participant_joined/participant_left— presence updates with full participant listchat— new message with sender info and timestamptyping— typing indicator from another userannotation_created/annotation_updated— annotation lifecycle eventsawareness— cursor and selection state from other usersreaction— emoji reaction added to a message
Shared Notepad (Yjs CRDT)
Binary WebSocket frames are treated as Yjs CRDT updates, enabling a collaborative notepad that supports concurrent editing without conflicts. The Durable Object relays binary frames to all other connected clients and persists the CRDT state to storage every 10 updates.Message Persistence
The Durable Object stores the last 1,000 messages in memory and persists every message to PlanetScale asynchronously viactx.waitUntil. Messages older than the in-memory buffer are available through the REST API.
Retrieve Chat History
limit (max 200) and offset.
Retrieve Annotations
REST API on the Durable Object
The Durable Object also exposes REST endpoints for server-to-server integration (used by the AI agents and background workers):| Method | Path | Description |
|---|---|---|
| GET | /state | Room state summary |
| GET | /messages | All in-memory messages |
| POST | /messages | Post a message (no WebSocket) |
| GET | /annotations | All annotations |
| POST | /annotations | Create an annotation |
| PATCH | /annotations/:id | Update annotation status/content |
Organization Scoping
All deal room queries are scoped to the caller’s organization. TheorgId is enforced at the database query level, so a user in one organization cannot access rooms belonging to another. Cross-org roles (superadmin, system, service) bypass this scoping.