跳到正文
CalabDocs

开发者文档目前仅提供英文版。

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