calls and individual media streams
bots appear as visible call participants with a blue bot tag. REST negotiates access and SDP; WebRTC carries separate participant media tracks.
call admission
starting, joining, speaking, receiving audio, receiving camera video, receiving screen video and publishing video are distinct grants. start creates or returns a call but does not join. join_with_user requires the installer to be a current participant and disconnects when they leave, unless join_all remains granted. join_all still requires an in-scope call the installer is eligible to join. private call guest access never becomes chat access. one active call session per installation and one participant per application/installer/call are allowed; show “bot · installed by [name]” to make distinct installations visible.
individual tracks
Track is {id,participant_id,source,kind,revision,active}; source is microphone, camera, screen or screen_audio, and kind is audio or video. audio.receive exposes each permitted microphone as a separate track and screen_audio only when screen.receive is also granted. camera and screen subscriptions require their own receive grants. track listings and events omit unauthorized types. a bot receives no mixed call recording, transcription or hidden channel. participants see the bot’s receiving/publishing indicators before tracks flow; joining a call with a bot displays the same notice. user mute and source-stop state remain effective.
WebRTC negotiation
POST a session to join the call and receive {id,participant_id,ice_servers,capabilities,expires_at,revision}. capabilities advertises available audio/video codecs and transceiver limits; baseline audio is Opus and video negotiates VP8 or H.264 from the current service. the developer supplies a standards-compliant WebRTC implementation, not an assumed Node built-in media stack. the existing Sometimes SFU is the transport target, but provider app secrets and other participants’ provider session IDs stay behind the API. use a single serialized SDP operation queue per session.
publishing and subscribing
for publish, create local transceivers and an SDP offer, wait for ICE gathering to complete, then submit {description:{type:offer,sdp},tracks:[{mid,source}]}; source may be microphone or camera only. the service returns {description:{type:answer,sdp},tracks,revision}; apply that answer. for subscriptions, submit remote track IDs; the service returns an SDP offer plus mappings of mid to track_id. apply it, create an answer, wait for ICE gathering and PUT that answer to /answer. received ontrack media is associated through mid, never by guessing stream order. no trickle-ICE endpoint is offered in v1; offers/answers carry gathered candidates. SDP is limited to 64 KiB. answer within 15 seconds or recreate the session.
call routes
all routes carry the installation header. session IDs and track IDs are installation-bound. discovery includes only eligible calls. call invites use the existing participant proposal/vote/acceptance rules and never force someone into a call. session DELETE always permits cleanup by its creating installation even if a media grant was revoked; credentials must remain valid.
scroll the table horizontally to see all columns.
| method and path | request | result |
|---|---|---|
| GET /calls | cursor, limit; start or join grant | eligible Call list, including authorized guest calls |
| GET /chats/{chat}/call | none; start or join grant | Call{id,chat_id,status,started_at,participants} or null |
| POST /chats/{chat}/calls | video?:boolean; permission 42 | 201 Call, or 200 for an existing call; no implicit join |
| POST /calls/{call}/sessions | {}; permission 44 or 45 | 201 Session + ICE/capabilities |
| POST /calls/{call}/sessions/{session}/heartbeat | none | {expires_at}; renew 75-second lease |
| GET /calls/{call}/sessions/{session}/tracks | none | authorized Track list |
| POST /calls/{call}/sessions/{session}/publish | description + tracks:[{mid,source}]; If-Match; permission 43/46 | SDP answer + Track list |
| POST /calls/{call}/sessions/{session}/subscriptions | track_ids[]; If-Match; permissions 8/9/10 | SDP offer + {mid,track_id} mappings |
| PUT /calls/{call}/sessions/{session}/answer | description:{type:answer,sdp}; If-Match | 204; completes pending negotiation |
| DELETE /calls/{call}/sessions/{session}/tracks/{track} | If-Match | 200 Negotiation with revision; apply any returned offer and answer it |
| DELETE /calls/{call}/sessions/{session} | none | 204; leave and release SFU resources |
| POST /calls/{call}/invitations | user_ids[]; permission 47 | Invitation{id,status,expires_at} |
| GET /calls/{call}/invitations/{id} | none; permission 47; own proposal | Invitation |
leases, revocation and recovery
send call heartbeat every 20 seconds; a stale session expires after 75 seconds independently of gateway health. call end, membership loss, block, group exclusion, uninstall or private invite removal force-close applicable tracks and participant sessions. permission loss closes only now-forbidden tracks unless join authority is also lost. revalidate at negotiation commit and never accept client-supplied foreign SFU IDs. ICE failure recreates the session with a new ID and authorized subscriptions; duplicate starts with the same idempotency key return the original receipt rather than new participants. a bot cannot end a human call, mute/kick another participant or publish screen tracks.