API Reference
Build video & chat into your app
A single REST API for rooms, tokens, and participants, plus a realtime channel for chat and video. Everything below is a working example — copy a snippet, drop in your key, and go.
The container for a call
Rooms
A room is the unit of a conversation — a container that participants join to exchange video, chat, and screen share. Create a room, hand out scoped join tokens, and Switchboard provisions the underlying media (a LiveKit SFU) on demand. Rooms are ephemeral: they idle until the first participant joins and close when the last one leaves.
The room object
idstring- Unique identifier, prefixed rm_.
namestringrequired- Human-readable label, e.g. "Patient intake".
statusenum- One of idle, active, or closed.
capabilitiesarrayrequired- Any of video, chat, recording. Gates what tokens may do.
max_participantsinteger- Hard cap on concurrent participants. Defaults to the plan limit.
regionstring- Media region the room is pinned to. US-hosted, single region.
metadataobject- Up to 4KB of your own key/value pairs. Never logged in the clear.
created_attimestamp- RFC 3339 creation time.
/api/v1/roomsCreate a room
Provisions a new room. Pass an Idempotency-Key header to make retries safe — a repeated key returns the original room instead of creating a duplicate.
curl -X POST https://api.switchboard.dev/api/v1/rooms \
-H "Authorization: Bearer sk_live_3wire…" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"room": {
"name": "Patient intake",
"capabilities": ["video", "chat", "recording"],
"max_participants": 4
}
}'{
"data": {
"id": "rm_8Xk2p9QwZ",
"name": "Patient intake",
"status": "idle",
"capabilities": ["video", "chat", "recording"],
"max_participants": 4,
"region": "us-east",
"created_at": "2026-07-02T15:04:05Z"
}
}/api/v1/roomsList rooms
Returns a cursor-paginated list of rooms, most recent first. Filter by status with ?status=active.
curl https://api.switchboard.dev/api/v1/rooms?limit=2&status=active \
-H "Authorization: Bearer sk_live_3wire…"{
"data": [
{ "id": "rm_8Xk2p9QwZ", "name": "Patient intake", "status": "active" },
{ "id": "rm_4Rf7t1LmN", "name": "Follow-up", "status": "active" }
],
"has_more": false,
"next_cursor": null
}/api/v1/rooms/:idRetrieve a room
Fetches a single room by id, including its live participant count.
curl https://api.switchboard.dev/api/v1/rooms/rm_8Xk2p9QwZ \
-H "Authorization: Bearer sk_live_3wire…"{
"data": {
"id": "rm_8Xk2p9QwZ",
"status": "active",
"participant_count": 2
}
}/api/v1/rooms/:idClose a room
Ends the room immediately, disconnecting every participant. Recordings already in flight are finalized to your bucket.
curl -X DELETE https://api.switchboard.dev/api/v1/rooms/rm_8Xk2p9QwZ \
-H "Authorization: Bearer sk_live_3wire…"{
"data": { "id": "rm_8Xk2p9QwZ", "status": "closed" }
}Scoped, short-lived join credentials
Tokens
A token is a short-lived, single-participant credential minted from your secret key. Your server mints one per participant and hands it to the client — the raw API key never touches the browser. Tokens carry an identity, a role, and a TTL, and inherit the room's capabilities.
/api/v1/rooms/:room_id/tokensMint a join token
Issues a JWT scoped to one room and one identity. The role governs permissions (e.g. a patient can't remove others). Tokens default to a 60-minute TTL.
curl -X POST https://api.switchboard.dev/api/v1/rooms/rm_8Xk2p9QwZ/tokens \
-H "Authorization: Bearer sk_live_3wire…" \
-d '{
"identity": "patient-9182",
"role": "patient",
"ttl_seconds": 3600
}'{
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…",
"url": "wss://media.switchboard.dev",
"expires_at": "2026-07-02T16:04:05Z"
}
}Who is in the room
Participants
Participants are the live members of a room. List them to build a roster, or remove one to force-disconnect. Identities are HMAC-hashed before they're persisted, so the audit log never stores who joined in the clear.
/api/v1/rooms/:room_id/participantsList participants
Returns everyone currently connected to the room, with their role and join time.
curl https://api.switchboard.dev/api/v1/rooms/rm_8Xk2p9QwZ/participants \
-H "Authorization: Bearer sk_live_3wire…"{
"data": [
{ "identity": "clinician-22", "role": "host", "joined_at": "2026-07-02T15:05:01Z" },
{ "identity": "patient-9182", "role": "patient", "joined_at": "2026-07-02T15:05:44Z" }
]
}/api/v1/rooms/:room_id/participants/:identityRemove a participant
Disconnects a single participant and revokes their token so they can't rejoin with it.
curl -X DELETE \
https://api.switchboard.dev/api/v1/rooms/rm_8Xk2p9QwZ/participants/patient-9182 \
-H "Authorization: Bearer sk_live_3wire…"{
"data": { "identity": "patient-9182", "status": "removed" }
}Realtime messaging in every room
Chat
Chat rides the same room as video over a WebSocket channel, room:{room_id}. Connect with the join token, then push and receive messages, typing indicators, and presence. Messages are delivered in order and mirrored to signed webhooks so your backend can persist or moderate them.
The message object
idstring- Unique identifier, prefixed msg_.
room_idstring- The room this message belongs to.
fromstring- The sender's participant identity.
bodystringrequired- Message text, up to 4,000 characters.
sent_attimestamp- Server receive time, used for ordering.
channel: room:{room_id}Connect & send a message
Open the channel with the participant token, then push a chat.message event. The server echoes a message.created to every member. This is a client-side WebSocket flow — no secret key in the browser.
import { connect } from "@switchboard/client";
// token + url came from POST /rooms/:id/tokens
const room = await connect(url, token);
// send a chat message
room.chat.send({ body: "Hi — ready when you are." });
// receive messages
room.chat.on("message.created", (msg) => {
console.log(msg.from, msg.body); // "clinician-22" "Hi — ready when you are."
});
// typing + presence
room.chat.on("typing", (p) => showTyping(p.identity));// broadcast to every participant in the room
{
"event": "message.created",
"data": {
"id": "msg_1a2B3c",
"room_id": "rm_8Xk2p9QwZ",
"from": "clinician-22",
"body": "Hi — ready when you are.",
"sent_at": "2026-07-02T15:06:10Z"
}
}1:1 to large rooms, with screen share
Video
Video is provisioned automatically for any room with the video capability, backed by a LiveKit SFU. The client joins with a token, publishes camera and microphone tracks, and subscribes to everyone else. Screen share is a second published track. Nothing about the media path touches your secret key.
join with @switchboard/clientJoin and publish tracks
Connect with the join token, then publish the local camera and mic. Remote tracks arrive on the trackSubscribed event — attach them to a <video> element to render.
import { connect } from "@switchboard/client";
const room = await connect(url, token);
// publish camera + microphone
await room.localParticipant.enableCameraAndMicrophone();
// render remote video as it arrives
room.on("trackSubscribed", (track, participant) => {
if (track.kind === "video") {
track.attach(document.getElementById("stage"));
}
});
// share your screen
document.getElementById("share")
.onclick = () => room.localParticipant.setScreenShareEnabled(true);// connection lifecycle events you can observe
"participantConnected" { identity: "patient-9182" }
"trackSubscribed" { kind: "video", source: "camera" }
"trackSubscribed" { kind: "video", source: "screen_share" }
"participantDisconnected" { identity: "patient-9182" }Customer-owned egress to your bucket
Recordings
Start a recording and Switchboard egresses the composited call straight to your S3 bucket, encrypted with your KMS key. Switchboard stores only a metadata pointer — never the media bytes. Recordings require the recording capability on the room.
/api/v1/rooms/:room_id/recordingsStart a recording
Begins composite egress to the destination you configured. Returns a recording handle you can poll or receive webhooks for.
curl -X POST https://api.switchboard.dev/api/v1/rooms/rm_8Xk2p9QwZ/recordings \
-H "Authorization: Bearer sk_live_3wire…" \
-d '{ "layout": "grid", "format": "mp4" }'{
"data": {
"id": "rec_Zt9wQ",
"status": "recording",
"destination": "s3://acme-phi/recordings/rec_Zt9wQ.mp4"
}
}Signed events, pushed to your backend
Webhooks
Switchboard posts events to your endpoint as they happen — room and participant lifecycle, chat messages, recording completion. Every delivery is HMAC-signed; verify the Switchboard-Signature header before trusting the payload. Deliveries retry with exponential backoff.
POST https://you.example/hooksReceive & verify an event
Compute the expected signature over the raw body with your endpoint secret and compare in constant time. Reject on mismatch.
import { verify } from "@switchboard/node";
app.post("/hooks", (req, res) => {
const event = verify(
req.rawBody,
req.headers["switchboard-signature"],
process.env.SWITCHBOARD_WEBHOOK_SECRET
); // throws on bad signature
if (event.type === "recording.completed") {
archive(event.data.destination);
}
res.sendStatus(200);
});// the delivered payload
Switchboard-Signature: t=1719936370,v1=9f86d0818…
{
"type": "participant.joined",
"data": {
"room_id": "rm_8Xk2p9QwZ",
"identity": "patient-9182",
"at": "2026-07-02T15:05:44Z"
}
}