Ir al contenido
CalabDocs

La documentación para desarrolladores está disponible en inglés.

Rooms and voice

Rooms and categories, temporary rooms, voice through LiveKit, call moderation, guest admission, meeting recording, the soundboard and telephony.

A bot sees only the rooms where it has VIEW_ROOM. With the permissions of its roles it can create and configure rooms and categories, and in a voice room it joins the call as an ordinary participant: it listens, speaks, shares a screen, moderates, admits guests, records the meeting, plays sounds and dials phone numbers.

ActionPermission
see a room and categoriesVIEW_ROOM / workspace member
create, change, delete rooms and categories, permission overridesMANAGE_ROOM (in the workspace or in the room)
temporary roomCREATE_TEMP_ROOMS
join voiceCONNECT; speak — SPEAK, screen share — STREAM, camera — VIDEO
mute / unmute, disconnect from voice, stop someone's screen share or cameraMUTE_MEMBERS (and the role hierarchy: only members below you)
move to another roomMOVE_MEMBERS in both rooms
guest admissionINVITE_GUESTS in the room
meeting recordingVIEW_ROOM + CONNECT + MANAGE_RECORDINGS
dial a phone numberPLACE_CALLS, the bot is in the room's call, telephony is on
sound libraryMANAGE_STICKERS

Routes

MethodPathWhat it does
GET/api/workspaces/{id}/roomsWorkspace rooms the bot can see → {rooms}
POST/api/workspaces/{id}/roomsCreate a room → {room} (MANAGE_ROOM)
POST/api/workspaces/{id}/rooms/tempA temporary room without guests or a meeting (CREATE_TEMP_ROOMS)
GET/api/rooms/{id}The room and the bot's permissions in it → {room, permissions}
PATCH/api/rooms/{id}Change a room → {room} (MANAGE_ROOM)
DELETE/api/rooms/{id}Delete a room (MANAGE_ROOM)
PUT/api/rooms/{id}/permissionsReplace the room's permission overrides {overrides} (MANAGE_ROOM)
PUT/api/workspaces/{id}/rooms/orderOrder of rooms and categories {rooms, categories}
GET/api/workspaces/{id}/categoriesRoom categories → {categories}
POST/api/workspaces/{id}/categoriesCreate a category {name, position?}
PATCH/api/categories/{id}Rename / move a category
DELETE/api/categories/{id}Delete a category
POST/api/rooms/{id}/joinJoin voice → {url, token, …} for LiveKit (CONNECT)
POST/api/rooms/{id}/voice/leaveLeave voice → 204
POST/api/rooms/{id}/stream/requestRequest a screen share slot (STREAM)
POST/api/rooms/{id}/camera/requestTurn the camera on before publishing (VIDEO)
POST/api/rooms/{id}/camera/stopTurn your camera off
PATCH/api/voice/selfYour own muted / deafened
PATCH/api/rooms/{id}/voice-statusThe room's call status {status} (≤ 60 characters)
POST/api/rooms/{id}/voice/{userId}/muteMute a member (MUTE_MEMBERS)
POST/api/rooms/{id}/voice/{userId}/unmuteUnmute a member (MUTE_MEMBERS)
POST/api/rooms/{id}/voice/{userId}/disconnectDisconnect from voice (MUTE_MEMBERS)
POST/api/rooms/{id}/voice/{userId}/moveMove to another room {targetRoomId} (MOVE_MEMBERS)
POST/api/rooms/{id}/voice/{userId}/stop-streamStop someone's screen share (MUTE_MEMBERS)
POST/api/rooms/{id}/voice/{userId}/stop-cameraTurn someone's camera off until they leave the call (MUTE_MEMBERS)
POST/api/rooms/{id}/voice/{userId}/allow-cameraAllow a member's camera again (MUTE_MEMBERS)
GET/api/rooms/{id}/admissionsGuests waiting to be admitted → {admissions} (INVITE_GUESTS)
POST/api/rooms/{id}/admissions/{userId}Decide on a guest {status, displayName?, badgeId?}
POST/api/rooms/{id}/recording/startStart recording the meeting → {recording}
POST/api/rooms/{id}/recording/stopStop recording → {recording}
GET/api/workspaces/{id}/soundsSoundboard sounds → {sounds}
POST/api/workspaces/{id}/soundsAdd a sound {name, emoji, fileId} (MANAGE_STICKERS)
PATCH/api/workspaces/{id}/sounds/{soundId}Change a sound (MANAGE_STICKERS)
DELETE/api/workspaces/{id}/sounds/{soundId}Delete a sound (MANAGE_STICKERS)
POST/api/rooms/{id}/sounds/playPlay a sound to everyone in the call {soundId}
POST/api/rooms/{id}/callsDial a number from the room's call {number} (PLACE_CALLS)
DELETE/api/rooms/{id}/calls/{cid}Hang up

Not available to bots

  • Room guest links: GET · POST /api/rooms/{id}/invites, PATCH · DELETE /api/rooms/{id}/invites/{inviteId}, joining by link POST /api/room-invites/{code}/join.
  • Room notification settings: PUT /api/rooms/{id}/notifications.
  • Waiting for admission (DELETE /api/rooms/{id}/admissions/me) — that is a guest's action.
  • Workspace camera backgrounds /api/workspaces/{id}/backgrounds… — a bot has no camera background.
  • Telephony settings and the call journal: /api/workspaces/{id}/sip, /api/workspaces/{id}/sip/test, GET /api/workspaces/{id}/calls.
  • One-to-one calls: POST /api/dms/{id}/call, /api/calls/{id}/… — a bot neither calls nor answers, and nobody can call a bot.
  • Reprocessing, re-uploading and deleting meeting recordings.

Create a room

CreateRoomRequest: type (ROOM_TYPE_TEXT or ROOM_TYPE_VOICE), name (1..100), topic (≤ 1024), isPrivate, categoryId, position, userLimit (voice rooms only, 0..99).

curl -s -X POST $CALAB/api/workspaces/$WS/rooms \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"type": "ROOM_TYPE_VOICE", "name": "Stand-up", "userLimit": 12}'
JSON
{
  "room": {
    "id": "0192a2d0-1111-7000-8000-00000000a001",
    "workspaceId": "0191f0aa-0000-7000-8000-000000000001",
    "type": "ROOM_TYPE_VOICE",
    "name": "Stand-up",
    "topic": "",
    "position": 7,
    "isPrivate": false,
    "categoryId": "",
    "userLimit": 12,
    "allowRecording": false,
    "restricted": false,
    "permissionOverrides": [],
    "createdAt": "2026-10-14T10:00:00Z"
  }
}

The SDK also lists rooms and fetches one: bot.rooms(workspaceId?), bot.room(roomId).

Temporary room

POST /api/workspaces/{id}/rooms/temp takes {name, ttlSeconds, private?, memberIds?, guests?, withEvent?}; the lifetime is 900 s (15 minutes) to 604,800 s (7 days). The answer is {room, inviteUrl, inviteCode}: a link for workspace members only.

Voice through LiveKit

Media flows directly through LiveKit (the SFU); the REST API does not proxy it. A bot in a call is an ordinary participant: a row in the list with a “BOT” badge and a speaking indicator; server mute and disconnect act on it as on a person; it takes a place in the room.

  1. POST /api/rooms/{id}/join (a voice room, CONNECT) → JoinVoiceResponse {url, token, identity, media, canSpeak, canStream, canVideo, pending}. token is a 10-minute LiveKit JWT whose grant follows the bot's permissions: SPEAK — publish a microphone, STREAM — screen share (if a slot is free), VIDEO — camera (call POST /api/rooms/{id}/camera/request before publishing). Connect right away: without a connection within 15 seconds the place is freed.
  2. Connect a LiveKit client with url + token: subscribe to participants' audio tracks and publish your own (Opus 48 kHz, “microphone” source).
  3. To leave, disconnect from LiveKit and call POST /api/rooms/{id}/voice/leave (→ 204); the place is freed at once.

Who is in the room: voiceStates in READY and voiceStateUpdate events (an empty roomId means the member left), see Gateway. In the SDK — bot.voice.participants(roomId).

// npm i @livekit/rtc-node
import { AudioFrame, AudioSource, AudioStream, LocalAudioTrack, Room, RoomEvent, TrackKind,
  TrackPublishOptions, TrackSource } from '@livekit/rtc-node';

const { url, token, canSpeak } = await bot.voice.join(roomId);
const room = new Room();
await room.connect(url, token, { autoSubscribe: true });
room.on(RoomEvent.TrackSubscribed, async (track, _pub, participant) => {
  if (track.kind !== TrackKind.KIND_AUDIO) return;
  for await (const frame of new AudioStream(track, 48000, 1)) {
    // frame.data is an Int16Array: 10 ms of PCM from participant.identity
  }
});
const source = new AudioSource(48000, 1);
if (canSpeak) {
  await room.localParticipant.publishTrack(LocalAudioTrack.createAudioTrack('bot', source),
    new TrackPublishOptions({ source: TrackSource.SOURCE_MICROPHONE }));
  await source.captureFrame(new AudioFrame(pcm480, 48000, 1, 480)); // 10 ms at a time
}
// …
await room.disconnect();
await bot.voice.leave(roomId);

In Go, use livekit/server-sdk-go (lksdk.ConnectToRoomWithToken(url, token, callbacks)). Ready-made examples (a voice echo bot, text to speech, Python) are listed in SDK.

Call moderation

curl -s -X POST $CALAB/api/rooms/$VOICE/voice/$USER/mute -H "Authorization: Bearer $TOKEN"
curl -s -X POST $CALAB/api/rooms/$VOICE/voice/$USER/move -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -d "{\"targetRoomId\": \"$OTHER_VOICE\"}"
curl -s -X PATCH $CALAB/api/voice/self -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -d '{"muted": true}'

Guest admission

When a room requires approval for guests, a guest who follows a link waits for a decision. A bot with INVITE_GUESTS in the room reads the queue and decides: status is ROOM_ADMISSION_STATUS_ADMITTED or ROOM_ADMISSION_STATUS_DECLINED; on admission it may rename the guest (displayName, 1..40) and give a badge (badgeId). If the request is no longer pending — 409 CONFLICT.

curl -s $CALAB/api/rooms/$VOICE/admissions -H "Authorization: Bearer $TOKEN"
curl -s -X POST $CALAB/api/rooms/$VOICE/admissions/$GUEST -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -d '{"status": "ROOM_ADMISSION_STATUS_ADMITTED", "displayName": "Partner"}'

Meeting recording

POST /api/rooms/{id}/recording/start and …/stop work while someone is in the room's call, the room has allowRecording on and the workspace is connected to the recording and transcription service. The answer is {recording: {recordingId, state, byUserId, since, …}}. A bot needs VIEW_ROOM, CONNECT and MANAGE_RECORDINGS. The finished transcript is described in Messages.

const rec = await bot.recording.start(roomId);
// …
await bot.recording.stop(roomId);

Soundboard

GET /api/workspaces/{id}/sounds lists the workspace's sounds ({sounds: [{id, name, emoji, fileId, durationMs, position}]}). POST /api/rooms/{id}/sounds/play {soundId} plays a sound to everyone in the call; soundId is a sound id or a built-in builtin:<name>. The bot must be in that room's call. Limits: 1 sound per 2 seconds per bot and 5 per 10 seconds per room.

A bot with MANAGE_STICKERS edits the library: {name (1..32), emoji, fileId}, where the file is the bot's own upload to this workspace.

curl -s -X POST $CALAB/api/rooms/$VOICE/sounds/play -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' -d '{"soundId": "builtin:air_horn"}'

Telephony

POST /api/rooms/{id}/calls {number} dials a number (E.164, e.g. +79161234567) from the room's call: the callee joins the room as participant sip:<callId>. DELETE /api/rooms/{id}/calls/{cid} hangs up your own line (someone else's with MUTE_MEMBERS). Statuses arrive as the sipCallUpdate event.

  • Requires PLACE_CALLS, the bot in that room's call and telephony enabled for the workspace.
  • At most 20 calls per hour per workspace (429, code SIP_RATE_LIMITED).
  • Business plan only: below it — 409 PLAN_LIMIT (hanging up always works).

Gotchas