---
summary: Recording calls on request or automatically, the announcement every party
  hears, pausing, transcripts and summaries, who can listen, retention and deletion.
title: Call recording
path: concepts/call-recording
status: published
---

ScaiDial can record calls: when a user starts it, or every call automatically. Before anything is recorded, everyone in the call hears an announcement. Recordings are one audio file per recording (Ogg/Opus, about 11 MB per hour). You can list, play and delete them through the API, `/my/dial` and ScaiWave, and they are deleted automatically when their retention period ends.

Recording is **off** until a tenant admin turns it on in **ScaiDial → Settings**.

## When a call is recorded

The tenant setting `recording_mode`:

| Mode | Behaviour |
|---|---|
| `off` (default) | Nothing is recorded, whatever extensions say |
| `on_demand` | Users can start recording a call they're in |
| `always` | Every call with an outside party is recorded from the moment two parties are connected. Internal calls (extension to extension) only with `recording_internal_calls` |

An extension can override this with its own `recording_mode` (`inherit`, `off`, `on_demand`, `always`). Only admins can set it; users can't. When several extensions take part in a call:

- any extension set to `off` means the call is not recorded (privacy wins);
- otherwise, if any is `always` (its own setting or inherited), the call is recorded automatically;
- otherwise, users can start a recording.

## The announcement

Every party hears *"Dit gesprek wordt opgenomen."* / *"This call is being recorded."* before recording starts. The text follows the tenant's call language. A tenant can set its own text (`recording_announcement_text`, up to 500 characters).

- **Starting a recording:** everyone in the call hears it, then recording begins.
- **Someone joins later** (a transfer target, someone you consult, a merged call): only they hear it, and their voice is recorded once it has finished.
- **The announcement can't be played** (for example, speech synthesis is down): the call is **not** recorded. `start` returns `503 scaidial.recording_announcement_unavailable`; an automatic recording is skipped and logged.

### Asking outside callers (`announce_and_confirm`)

Where every party must agree to be recorded, set `recording_consent` to `announce_and_confirm` (Settings → *Consent* → *Announce and ask outside callers*).

- **Outside callers** (anyone reaching the call through your carrier, inbound or outbound) hear the announcement plus *"Druk op 1 om akkoord te gaan, of blijf aan de lijn zonder dat uw stem wordt vastgelegd."* / *"Press 1 to agree, or stay on the line without your voice being captured."*
- **Pressing 1** within 10 seconds of the end of that sentence starts recording their voice.
- **Anything else, or nothing:** they're never recorded, and the call goes on normally; the other parties are still recorded.
- **Your own users** (apps, desk phones, bots) only hear the announcement.
- **Merged calls** aren't recorded in this mode, because the people behind the bridge can't be asked.
- A party is asked once. After a service restart their earlier answer still stands.

The same applies to captions without recording.

Each party's outcome is in the recording's `participants[].consent`:
- `announced`: heard the announcement;
- `confirmed`: pressed 1;
- `declined`: didn't press 1, not recorded;
- `not_asked`: a merge bridge, or the question couldn't be played; not recorded.

## Controlling a recording

| Action | Effect |
|---|---|
| `start` | Announcement, then recording. A call can be recorded several times; each `start` makes a new recording |
| `pause` | Stops recording, for example while a card number is read out. The paused time is left out of the file and shown as a gap. A pause ends by itself after 5 minutes |
| `resume` | Continues the same recording, without a new announcement |
| `stop` | Ends the recording; the file is ready a few seconds later |

In `/my/dial`, the call card has **Record / Pause / Resume / Stop** and **Captions** buttons. A recording started by the tenant's policy shows there as well, within a few seconds.

Who can control a call's recording:

- whoever placed the call;
- users with `owner`, `manage` or `answer` on an extension that's in the call;
- users with only `observe` on such an extension, if the tenant enables `recording_observers_can_start`;
- admins with `scaidial:recordings:manage`.

With `always`, users can stop a recording only when `recording_user_can_stop` is on (default). Otherwise they can only pause.

## What's in a recording

One mixed audio file of everyone who was recorded:
- a held party is silent;
- music on hold is not recorded;
- a consultation during an attended transfer is recorded;
- after a merge, the call you kept goes on recording everyone, and the merged call's recording stops (`end_reason: merged`).

The recording object lists:
- **participants:** each one's kind (`phone`, `app`, `bot`, `bridge`), name, number or extension, and when they joined and left (milliseconds into the recording);
- **gaps:** pauses, and short breaks when the service restarted during the call (`recorder_restart`; about 20–30 s of a call can be missing then).

## Transcripts and summaries

With `recording_transcript_enabled` (Settings → *Transcripts and summaries*, off by default), each recording gets a transcript and a summary after the call:

- **Transcript:** who said what, and when. Each person is recorded separately, so every turn is labelled with its speaker (name and extension, or number) and its time in the recording, with no guessing who spoke. A stretch that couldn't be transcribed shows as `[inaudible]`.
- **Summary:** at most two sentences in the call's language: who talked about what, and any agreed action, number, date or time.
- Both are made on ScaiLabs-hosted models, usually within seconds of the recording becoming ready. Transcription is billed per recorded minute.
- The setting applies to recordings started after it's turned on. `transcript_state` on the recording shows `pending`, `ready`, `failed` or `off`.
- The transcript and summary are deleted together with the recording.
- Phone audio (8 kHz) makes the occasional word come out wrong; names and numbers are worth checking against the audio.

`GET /recordings/{id}/transcript` returns `transcript_state`, `summary`, `text` (one line per turn: `Speaker: text`) and `turns` (`start_ms`, `end_ms`, `participant_identity`, `speaker_label`, `text`). Reading a transcript is logged like a play.

## Live captions

With `recording_live_captions_enabled` (Settings → *Live captions*, off by default), what each person says shows up as captions in ScaiWave while the call is recorded, a second or two after they pause.

- **Who gets them:** only app participants (ScaiWave) who can hear the speaker. Someone on hold, or the caller waiting during an attended transfer, gets none. Desk phones and outside callers can't show captions.
- **Cost:** with transcripts also on, the captions become the recording's transcript, so the call is transcribed once. That holds unless captions were switched on mid-recording or the service restarted; then the transcript is made from the audio afterwards.
- **Switching:** `POST /calls/{call_id}/captions/start` and `/stop` turn them on and off, with the same permissions as controlling the recording.
- **Without recording:** with `recording_captions_without_recording` (off by default), captions can run on a call that isn't recorded. Everyone hears *"Dit gesprek wordt uitgeschreven."* / *"This call is being transcribed."* first, as with recording, and nothing is stored.
- **Blocked:** captions are refused (`409 scaidial.captions_not_allowed`) when the tenant has them off, or when an extension in the call is set to never be recorded.
- Stopping the recording also stops its captions; turn them on again if needed.

For app developers, captions arrive as LiveKit text streams on topic `lk.transcription`, the standard transcription topic:
- `lk.transcribed_track_id` is the speaker's audio track;
- `lk.transcription_final` is `true`;
- `scaidial.participant_identity`, `scaidial.speaker_label`, `scaidial.start_ms` and `scaidial.end_ms` are also set.

## Who can listen

| Who | Can |
|---|---|
| Whoever placed the call, and users with any role (`owner`, `manage`, `answer`, `observe`) on an extension in the call | List, play |
| `owner` / `manage` on such an extension | Delete |
| `scaidial:recordings:read` | List and play every recording in the tenant |
| `scaidial:recordings:manage` | The same, plus delete and control any call's recording |

Every play and delete is logged (`SCAIGRID-DIAL-RECORDING-ACCESS`: user, recording, action), so you can answer "who listened to this call". Recordings of other tenants are never reachable directly, not even for platform admins.

## Retention and deletion

- `recording_retention_days` (default 90, up to 3650). Each recording gets a `retention_until` date when it's ready. A later change applies to new recordings only.
- Expired recordings are deleted automatically: the audio and anything derived from it. Only an empty record remains (ids, times, `deleted_reason`), so references in logs still resolve.
- `DELETE` removes the audio straight away.
- A recording can't be deleted while it's still running: stop it first.
- **Legal hold:** `PUT /recordings/{id}/legal-hold` (admins with `scaidial:recordings:manage`) keeps a recording past its retention date and blocks every deletion, including erasure. `DELETE /recordings/{id}/legal-hold` lifts it; an expired recording then goes in the next clean-up (within about 5 minutes).
- **Erasing someone's recordings:** `POST /recordings/erase` with `{"e164": "+31612345678"}` deletes every recording in the tenant in which that number took part (as a party, the caller, or the number dialled). It's for requests to delete someone's data. Recordings on legal hold or still running are kept and listed in the reply (`legal_hold_ids`, `live_ids`). The log notes the request without the number. Voicemail from that number is separate: delete it in Voicemail.

## Endpoints

On the module API (`/v1/modules/scaidial`):

| Method + path | Needs |
|---|---|
| `GET /recordings` (`call_id`, `extension_id`, `before`, `limit`) · `GET /recordings/{id}` · `GET /recordings/{id}/audio` · `GET /recordings/{id}/transcript` | `scaidial:recordings:read` or `:manage` |
| `DELETE /recordings/{id}` · `PUT`/`DELETE /recordings/{id}/legal-hold` · `POST /recordings/erase` · `POST /calls/{call_id}/recording/{start\|pause\|resume\|stop}` · `POST /calls/{call_id}/captions/{start\|stop}` | `scaidial:recordings:manage` |
| `GET /me/recordings` · `GET /me/recordings/{id}` · `GET /me/recordings/{id}/audio` · `GET /me/recordings/{id}/transcript` | a role on an extension in the call, or placed it |
| `DELETE /me/recordings/{id}` | `owner` / `manage` |
| `POST /me/calls/{call_id}/recording/{action}` · `POST /me/calls/{call_id}/captions/{start\|stop}` | see *Controlling a recording* |

The audio endpoint returns `audio/ogg` and supports `Range`, so players can seek in long calls.

## Events

`scaidial.recording.started`, `.paused`, `.resumed`, `.stopped` (`reason`: `user`, `call_ended`, `merged`), `.ready` and `.deleted` (`reason`: `user`, `retention`, `admin`, `erasure`), and `scaidial.transcript.ready` (`transcript_state`, `turns`, `has_summary`). Each carries `recording_id`, `call_id` and the `user_ids` of everyone who may see it. ScaiWave receives the same events as `recording.*` webhooks.
