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.

Preview. This reference is an illustrative mock for early feedback — endpoints and payloads are representative, not yet live.

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.
POST/api/v1/rooms

Create 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.

Request
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
    }
  }'
Response
{
  "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"
  }
}
GET/api/v1/rooms

List rooms

Returns a cursor-paginated list of rooms, most recent first. Filter by status with ?status=active.

Request
curl https://api.switchboard.dev/api/v1/rooms?limit=2&status=active \
  -H "Authorization: Bearer sk_live_3wire…"
Response
{
  "data": [
    { "id": "rm_8Xk2p9QwZ", "name": "Patient intake", "status": "active" },
    { "id": "rm_4Rf7t1LmN", "name": "Follow-up",      "status": "active" }
  ],
  "has_more": false,
  "next_cursor": null
}
GET/api/v1/rooms/:id

Retrieve a room

Fetches a single room by id, including its live participant count.

Request
curl https://api.switchboard.dev/api/v1/rooms/rm_8Xk2p9QwZ \
  -H "Authorization: Bearer sk_live_3wire…"
Response
{
  "data": {
    "id": "rm_8Xk2p9QwZ",
    "status": "active",
    "participant_count": 2
  }
}
DELETE/api/v1/rooms/:id

Close a room

Ends the room immediately, disconnecting every participant. Recordings already in flight are finalized to your bucket.

Request
curl -X DELETE https://api.switchboard.dev/api/v1/rooms/rm_8Xk2p9QwZ \
  -H "Authorization: Bearer sk_live_3wire…"
Response
{
  "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.

POST/api/v1/rooms/:room_id/tokens

Mint 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.

Request
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
  }'
Response
{
  "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.

GET/api/v1/rooms/:room_id/participants

List participants

Returns everyone currently connected to the room, with their role and join time.

Request
curl https://api.switchboard.dev/api/v1/rooms/rm_8Xk2p9QwZ/participants \
  -H "Authorization: Bearer sk_live_3wire…"
Response
{
  "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" }
  ]
}
DELETE/api/v1/rooms/:room_id/participants/:identity

Remove a participant

Disconnects a single participant and revokes their token so they can't rejoin with it.

Request
curl -X DELETE \
  https://api.switchboard.dev/api/v1/rooms/rm_8Xk2p9QwZ/participants/patient-9182 \
  -H "Authorization: Bearer sk_live_3wire…"
Response
{
  "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.
POSTchannel: 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.

Request
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));
Response
// 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.

POSTjoin with @switchboard/client

Join 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.

Request
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);
Response
// 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.

POST/api/v1/rooms/:room_id/recordings

Start a recording

Begins composite egress to the destination you configured. Returns a recording handle you can poll or receive webhooks for.

Request
curl -X POST https://api.switchboard.dev/api/v1/rooms/rm_8Xk2p9QwZ/recordings \
  -H "Authorization: Bearer sk_live_3wire…" \
  -d '{ "layout": "grid", "format": "mp4" }'
Response
{
  "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.

POSTPOST https://you.example/hooks

Receive & verify an event

Compute the expected signature over the raw body with your endpoint secret and compare in constant time. Reject on mismatch.

Request
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);
});
Response
// 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"
  }
}