> ## Documentation Index
> Fetch the complete documentation index at: https://docs.firetone.com.au/llms.txt
> Use this file to discover all available pages before exploring further.

# Conference rooms

> Create dial-in rooms, put people in them, and run a room while it is up: mute, kick, lock, record, end.

A **conference room** is a dial-in bridge your organisation owns: a short
number, a participant PIN and, optionally, a moderator PIN. Through the API you
create rooms, dial people into them, and control a room while it is up. The
same controls work on a room made by merging two calls from the Desk.

Every request carries `Authorization: Bearer $FIRETONE_KEY`. Creating and
editing rooms needs `conferences:write`; running a live room needs
`conferences:control`; reading needs `conferences:read`.

## Create a room

```bash theme={null}
curl -X POST https://api.your-host/api/v1/conference-rooms \
  -H "authorization: Bearer $FIRETONE_KEY" -H 'content-type: application/json' \
  -d '{
    "name": "Boardroom",
    "number": "800",
    "participant_pin": "246810",
    "moderator_pin": "13579",
    "wait_for_moderator": true,
    "end_when_moderator_leaves": false,
    "record": false,
    "max_members": 0
  }'
```

* `number` is 2 to 8 digits: what an extension dials, and what a caller keys
  when a shared "Conference" phone number asks which room. Unique within the
  organisation; an extension with the same digits wins.
* `participant_pin` is 4 to 10 digits, or empty for a room anyone may dial
  straight into. `moderator_pin` is optional and must differ; empty means the
  room has no moderator role. The PINs are returned on
  every read: they are invitation codes, not credentials.
* `wait_for_moderator` holds participants on music until a moderator joins,
  and needs a moderator PIN. `max_members` `0` means no cap.

**Default members.** `PUT /conference-rooms/{id}/members` with the same shape
replaces the extensions rung when the room opens. A busy or unregistered
member is rung as soon as it is free while the room is up; a session's history
(`GET /conference-rooms/{id}/sessions`) carries each `invites` row with its
`state` and `last_reason`. `minutes_email` on the room is where a virtual
member's minutes are sent.

**Minutes.** `GET /conference-rooms/{id}/sessions/{sid}/meeting` (AI module;
`conferences:read`) is what a virtual member made of one session: `minutes`
(or `minutes_error`, why none were written), the `notes` it took as things
were decided, `turns` with each caller line's `speaker`, `replies_unheard`
(replies the model produced that nobody had asked for, held back), and
`delivered_to` (the addresses the minutes were queued to). Per SESSION, not
per room: a room used three times a day has three sets of minutes. 404 when
no virtual agent sat in that session. `GET /conference-rooms/{id}/meetings`
is the same for every session of the room at once, one summary row per seat
(`session_id`, `agent`, `state`, `has_minutes`, `minutes_error`, `notes`,
`turns`), which is what the room's page reads. The same minutes arrive as the
`conference.minutes` webhook event.

**Extensions that need no PIN.** `PUT /conference-rooms/{id}/exemptions` with
`{"items": [{"extension_id": "…", "role": "moderator"}]}` replaces the set of
the organisation's extensions that dial the room number and are seated without
keying anything, each in its role. The room returns them as `exemptions`. Only
a leg the directory authenticated carries an extension, so an outside caller
can never be exempt.

`GET /conference-rooms`, `GET /conference-rooms/{id}`, `PATCH` and `DELETE`
follow the usual conventions. A room's row carries `numbers`, the phone numbers
pointed at it alone, and `live_members`, how many are in it right now. Deleting
a room a number still points at answers 409 naming them.

To let outside callers in, point a phone number at destination
`conference`: with `destination_id` set to a room the caller is asked only for
the PIN; with none, for the room number first.

## Dial somebody in

```bash theme={null}
curl -X POST https://api.your-host/api/v1/conference-rooms/$ROOM/dial \
  -H "authorization: Bearer $FIRETONE_KEY" -H 'content-type: application/json' \
  -d '{ "kind": "extension", "target": "1001", "role": "moderator" }'
```

The extension rings, showing the room's number and name. Whoever answers is
seated in the room in `role` (`participant` or `moderator`) **without keying a
PIN**. For an outside number send `"kind": "external"` and `target` in E.164;
it is routed and billed as an outbound call, refused without credit, and sees
the organisation's first phone number as caller ID.

The response is `202` with `{"dialling": "…"}`: the call is ringing. Whether
it was answered shows in the room's live list.

## The room while it is up

Live rooms are keyed by the **switch's room name**: `ft-room-<room id>` for a
dial-in room, `ft-conf-<call uuid>` for two calls merged from the Desk. That
is what lets a merged call be run with the same requests.

```bash theme={null}
curl https://api.your-host/api/v1/conferences/ft-room-$ROOM \
  -H "authorization: Bearer $FIRETONE_KEY"
```

```json theme={null}
{
  "room": "ft-room-…", "room_id": "…", "room_name": "Boardroom",
  "session_id": "…", "started_at": "2026-09-22T09:12:04Z",
  "locked": false, "recording": false, "all_muted": false, "run_seconds": 184,
  "members": [
    { "id": 1, "call_uuid": "…", "number": "1001", "name": "Agent One",
      "role": "moderator", "muted": false, "talking": true, "joined_at": "…" },
    { "id": 2, "call_uuid": "…", "number": "+61400111222", "name": "",
      "role": "participant", "muted": false, "talking": false, "joined_at": "…" }
  ],
  "can_control": true
}
```

`id` is the switch's member id, and it is what the member controls take.
`can_control` says whether the caller of this request may use them. `404`
means nobody is in that room right now.

| Request                                                                  | Effect                                                          |
| ------------------------------------------------------------------------ | --------------------------------------------------------------- |
| `POST /conferences/{room}/members/{id}/mute`                             | that person cannot be heard                                     |
| `POST /conferences/{room}/members/{id}/unmute`                           |                                                                 |
| `POST /conferences/{room}/members/{id}/kick`                             | their leg is hung up                                            |
| `POST /conferences/{room}/lock` and `/unlock`                            | nobody new may join; a moderator still may                      |
| `POST /conferences/{room}/mute-all`                                      | toggles everyone but the moderators; answers `{"all_muted": …}` |
| `POST /conferences/{room}/record` with `{"on": true}` or `{"on": false}` | records the whole mix into one file per session                 |
| `POST /conferences/{room}/end`                                           | everybody is hung up and the room is gone                       |

`GET /conferences` lists every room live in your scope, and
`GET /conferences/by-call/{uuid}` names the room a call is in, which is how the
Desk knows to show a merged call's participants.

Inside a room the keypad also works: `*1` mute yourself, `*2` unmute; a
moderator has `*5` lock, `*7` mute everyone else, `*0` end.

## History and recordings

`GET /conference-rooms/{id}/sessions` lists every occupancy of the room,
newest first: when it ran, `peak_members`, who was in it with their roles and
times, the default members' `invites` and how each went (above), and
`has_recording` with `recording_bytes`. A recording is played from
`GET /conference-rooms/{id}/sessions/{sid}/recording/audio` (`recordings:listen`),
a WAV with Range support, and is kept under the organisation's recording
retention like any call recording.

## Events

The live stream (see [Events](/api/events)) carries `conference.updated` with
`room` and an `action` (`joined`, `left`, `locked`, `recording`, `all_muted`,
`ended`, and the switch's mute and talking changes), and `call.conferenced`
when calls are merged from the Desk. Treat them as a nudge to re-read
`GET /conferences/{room}`; the read is the truth.
