> For complete lifecycle-event payload schemas and examples, use the canonical overview: Session /api/sessions; Room lifecycle, members, and room playback /api/rooms; Session playback /api/playback; Recording /api/recording; DTMF /api/keypad-input; Messages /api/messages; WebSocket Session /api/web-socket. Use /api/event-delivery for transport behavior. Endpoint pages name relevant events, but these overviews are the canonical references.

# Recording

> Complete Recording resource and lifecycle-event reference, including speech-activity events and payload examples.

A `Recording` belongs to exactly one [Session](/api/sessions) or [Room](/api/rooms).
Each Recording has a `recording_uuid` that identifies its lifecycle events,
inspection endpoint, and control commands.

A `202 Accepted` response confirms command admission. The lifecycle events on
this page report whether audio became available, speech activity when enabled,
and the later completion or failure outcome.

## When to use

Use Recording to transcribe a conversation in real time, capture voicemail,
download a completed recording, or keep the full conversation for analysis and
quality review.

## Session recording collection

Create a Session Recording with `POST /v1/sessions/{uuid}/recordings`. The
request must include `direction` and `channels`:

```json
{
  "direction": "both",
  "channels": "mono",
  "include_gaps": true
}
```

`direction` accepts `both`, `session_in`, or `session_out`; `channels` accepts
`mono` or `stereo`. The initial release supports only `both` with `mono`.
Other valid combinations return HTTP `400` as unsupported.

`include_gaps` is optional and defaults to `false`. Omit it or set it to `false`
to remove redacted periods and periods without recordable audio from the
artifact. Set it to `true` to include those periods as silence. This includes
intervals controlled by the Recording mask and media interruptions such as a
hold path that stops delivering audio to the recorded Session. Natural silence
that arrives as audio is already part of the Recording and isn't a gap.

The setting applies only to the new Recording, so concurrent Recordings on one
Session can choose independently. The list and inspection endpoints return the
selected boolean.

An accepted create request returns `202 Accepted` with the new
`recording_uuid`. Store that identifier. A Session may own multiple concurrent
recordings, including recordings with the same settings; there is no implicit
current Recording and no command that stops all recordings.

Use `GET /v1/sessions/{uuid}/recordings` to list the Session's active or
stopping recordings and
`GET /v1/sessions/{uuid}/recordings/{recording_uuid}` to inspect one. These
endpoints expose the live collection, not recording history. Address the exact
Recording when controlling it:

* `POST /v1/sessions/{uuid}/recordings/{recording_uuid}/stop`
* `POST /v1/sessions/{uuid}/recordings/{recording_uuid}/mask`
* `POST /v1/sessions/{uuid}/recordings/{recording_uuid}/unmask`

Session hangup stops every active Recording owned by that Session. Stop, mask,
and unmask affect only the addressed Recording.

Voice-activity events are optional on each Session Recording create request.
`enable_voice_activity_events` defaults to `false`, so omit it when that
Recording does not need speech events. Multiple concurrent Recordings may set
it to `true`, and each Recording may use a different `voice_activity_config`.
Each receives its own events correlated by `recording_uuid` and
`operation_uuid`. Stopping one Recording stops only its events and does not
affect the other active Recordings.

## Methods

* Create, list, and inspect Session recordings under
  `/v1/sessions/{uuid}/recordings`
* Stop, mask, or unmask one Session Recording by its `recording_uuid`
* [Start room recording](api:voice-api:POST/api/v1/rooms/\{room_id}/playback/record)
* [Stop room recording](api:voice-api:POST/api/v1/rooms/\{room_id}/playback/record/stop)

## Resource representation

### The Recording resource

```json
{}
```

## Properties

### Schema (`Recording`)

```yaml
components:
  schemas:
    Recording:
      oneOf:
        - description: Any type
        - description: Any type
      title: Recording
```

## Lifecycle events

Recording events notify your application as a session or room
[Recording](/api/recording) moves through its lifecycle. Session recordings
can fail their liveness check — the platform confirming that audio is
actually reaching durable storage — and can opt into customer speech
activity events.

The per-command correlation field is `operation_uuid`, the correlation ID
echoed back from the command's `202 Accepted` acknowledgement. It is just
that echoed ID, not a separate object you can fetch.

Session and room recordings use the same lifecycle event names and the same
public `recording_uuid`. Session events carry `session_uuid`; room events carry
`room_id`.

Every payload schema below also includes the required `aud` string, which
identifies the application that should receive the signed event.

## Recording admission

A recording create or start command's `202 Accepted` response is its sole
admission acknowledgement; no webhook reports admission. Creating a Session
Recording returns its `recording_uuid`, which identifies that Recording in all
later events and commands. Concurrent recordings on one Session have distinct
identifiers and independent lifecycles. A session recording then emits either
[`recording.became_available`](#recordingbecame_available) or
[`recording.failed`](#recordingfailed), normally within about 10 seconds. A
room recording emits [`recording.became_available`](#recordingbecame_available)
only when liveness is confirmed; otherwise it remains active without a failure
webhook.

## recording.speech.started

Fired when customer speech is detected during a session recording started with
`enable_voice_activity_events: true`. The first event omits
`preceding_silence_duration_ms`; later events include the measured silence since
the preceding `recording.speech.ended`. `sensitivity` controls how quiet a sound
can be and still count as speech; `speech_duration_ms` controls how long speech
must continue before this event. Lowering either detects speech faster but
produces more false starts. See the Session Recording create request schema for ranges
and recommended values.

Each Recording opts into these events independently; the option defaults to
disabled. Multiple active Recordings on the same Session may enable the events
with different `voice_activity_config` values. Each receives its own events,
correlated by `recording_uuid` and the originating `operation_uuid`. Stopping
one Recording stops only its events and does not affect the other active
Recordings.

| Field                           | Type    | Description                                                              |
| ------------------------------- | ------- | ------------------------------------------------------------------------ |
| `event`                         | string  | Always `"recording.speech.started"`. **Required**                        |
| `session_uuid`                  | string  | Session being recorded. **Required**                                     |
| `recording_uuid`                | string  | Recording identifier (`rec_<uuid-v4>`). **Required**                     |
| `operation_uuid`                | string  | Correlation ID of the originating Recording create command. **Required** |
| `preceding_silence_duration_ms` | integer | Silence before speech resumed. Omitted for the first speech interval.    |
| `timestamp`                     | string  | RFC 3339 / ISO 8601 UTC transition timestamp. **Required**               |

```json
{
  "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34",
  "event": "recording.speech.started",
  "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "recording_uuid": "rec_11111111-2222-4333-8444-555555555555",
  "operation_uuid": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
  "timestamp": "2025-06-15T14:50:03.000Z"
}
```

## recording.speech.ended

Fired after customer audio remains below the configured threshold for
`silence_duration_ms`. Lower values report an end faster but can split natural
pauses; higher values tolerate pauses but delay this event. The value can be
configured from 20 milliseconds up to one hour.
It is never emitted before the first `recording.speech.started`, and neither
speech event is emitted after the recording stops or the session hangs up.

| Field            | Type   | Description                                                              |
| ---------------- | ------ | ------------------------------------------------------------------------ |
| `event`          | string | Always `"recording.speech.ended"`. **Required**                          |
| `session_uuid`   | string | Session being recorded. **Required**                                     |
| `recording_uuid` | string | Recording identifier (`rec_<uuid-v4>`). **Required**                     |
| `operation_uuid` | string | Correlation ID of the originating Recording create command. **Required** |
| `timestamp`      | string | RFC 3339 / ISO 8601 UTC transition timestamp. **Required**               |

```json
{
  "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34",
  "event": "recording.speech.ended",
  "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "recording_uuid": "rec_11111111-2222-4333-8444-555555555555",
  "operation_uuid": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
  "timestamp": "2025-06-15T14:50:05.000Z"
}
```

## recording.became\_available

Fired after the recording create or start request returns `202 Accepted`, once the platform confirms that audio is reaching durable storage and is fetchable. Wait for this event when you need that guarantee before acting. It carries two URLs:

* `pull_url` — the **whole recording so far**, WAV-wrapped. It streams a live chunked tail while recording is active, then serves the completed file with HTTP Range support. Download this URL to keep a copy.
* `live_url` — a **non-seekable live tail stream** from the current position. A listener joins at the live edge, hears “now” forward, and does not replay the beginning. Use it to monitor an active recording.

For a session recording, liveness not confirmed within the validation window emits [`recording.failed`](#recordingfailed) instead. Room recordings emit `recording.became_available` only on success; they do not emit a liveness-failure webhook. `recording.became_available` and `recording.failed` are mutually exclusive outcomes for a session recording.

**Payload schema**

| Field            | Type   | Description                                                                          |
| ---------------- | ------ | ------------------------------------------------------------------------------------ |
| `event`          | string | Always `"recording.became_available"`. **Required**                                  |
| `session_uuid`   | string | Session being recorded. *Session recordings*                                         |
| `room_id`        | string | Room being recorded. *Room recordings*                                               |
| `recording_uuid` | string | Recording identifier (`rec_<uuid-v4>`). **Required**                                 |
| `pull_url`       | string | URL for the whole-so-far WAV. **Required**                                           |
| `live_url`       | string | URL for the non-seekable live tail stream. **Required**                              |
| `expires_at`     | string | Exact RFC 3339 UTC expiration time shared by both URLs. **Required**                 |
| `operation_uuid` | string | Correlation ID of the Recording create or room recording start command. **Required** |
| `timestamp`      | string | RFC 3339 / ISO 8601 UTC emission timestamp. **Required**                             |

```json
{
  "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34",
  "event": "recording.became_available",
  "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "recording_uuid": "rec_11111111-2222-4333-8444-555555555555",
  "pull_url": "https://recordings.example.com/c/opaque-signed-url-1",
  "live_url": "https://recordings.example.com/c/opaque-signed-url-2",
  "expires_at": "2025-06-16T14:50:01.000Z",
  "operation_uuid": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
  "timestamp": "2025-06-15T14:50:01.000Z"
}
```

## recording.ended

Fired when an active recording is stopped. Session recordings also emit this event when the session hangs up while recording.

For both session and room recordings, `operation_uuid` identifies the
originating Recording create or room recording start command when available. A
later stop response has its own UUID, which is not copied into this lifetime
event.

**Payload schema**

| Field            | Type    | Description                                                                                                      |
| ---------------- | ------- | ---------------------------------------------------------------------------------------------------------------- |
| `event`          | string  | Always `"recording.ended"`. **Required**                                                                         |
| `session_uuid`   | string  | Session that was recorded. *Session recordings*                                                                  |
| `room_id`        | string  | Room that was recorded. *Room recordings*                                                                        |
| `recording_uuid` | string  | Recording identifier (`rec_<uuid-v4>`). **Required**                                                             |
| `pull_url`       | string  | Fresh URL for the completed Recording. **Required**                                                              |
| `expires_at`     | string  | Exact RFC 3339 UTC expiration time for `pull_url`. **Required**                                                  |
| `operation_uuid` | string  | Correlation ID of the originating Session Recording create or room recording start command. *Present when known* |
| `duration`       | integer | Wall-clock recording duration in whole seconds. **Required**                                                     |
| `timestamp`      | string  | RFC 3339 / ISO 8601 UTC emission timestamp. **Required**                                                         |

```json
{
  "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34",
  "event": "recording.ended",
  "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "recording_uuid": "rec_11111111-2222-4333-8444-555555555555",
  "pull_url": "https://recordings.example.com/c/opaque-signed-url-3",
  "expires_at": "2025-06-16T14:51:02.000Z",
  "operation_uuid": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
  "duration": 62,
  "timestamp": "2025-06-15T14:51:02.000Z"
}
```

For a Session Recording created with `include_gaps: true`, `duration` includes
redacted periods and periods without recordable audio, subject to audio frame
and whole-second rounding. When the field is omitted or `false`, the artifact
can be shorter because those periods are removed.

## recording.failed

`recording.failed` is session-only. It fires when the post-dispatch liveness
check does not confirm that a new session recording is reaching durable storage
within the validation window. This event is emitted instead of
[`recording.became_available`](#recordingbecame_available), with the error
`"recording unavailable"` and no usable URL. A room liveness timeout does not
emit this event or end the room recording.

This failure completes the start-liveness check. A later stop or hangup can
still emit [`recording.ended`](#recordingended). If recording is not configured,
the start request returns `503` and no recording webhook is emitted.

**Payload schema**

| Field            | Type   | Description                                                                |
| ---------------- | ------ | -------------------------------------------------------------------------- |
| `event`          | string | Always `"recording.failed"`. **Required**                                  |
| `session_uuid`   | string | Session whose recording failed the liveness check. **Required**            |
| `recording_uuid` | string | Recording identifier (`rec_<uuid-v4>`). **Required**                       |
| `operation_uuid` | string | Correlation ID of the Recording create command. **Required**               |
| `error`          | string | Currently `"recording unavailable"`. **Required**                          |
| `pull_url`       | string | Empty because no URL was minted; there is no validity window. **Required** |
| `timestamp`      | string | RFC 3339 / ISO 8601 UTC emission timestamp. **Required**                   |

```json
{
  "aud": "app_3f9c0b2a-7d41-4e8b-9f12-2a6c5d0e7b34",
  "event": "recording.failed",
  "session_uuid": "acW68-f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "recording_uuid": "rec_11111111-2222-4333-8444-555555555555",
  "operation_uuid": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
  "error": "recording unavailable",
  "pull_url": "",
  "timestamp": "2025-06-15T14:50:10.000Z"
}
```

## Masking an active recording

Mask and unmask commands emit no webhooks; each request's `202 Accepted`
response is its sole admission acknowledgement. Both commands address one
Session Recording at
`/v1/sessions/{uuid}/recordings/{recording_uuid}/mask` or `/unmask`. The
Recording stays active while audio writing is masked, and unmask requests that
writing resume. Other concurrent recordings are unaffected.

## Recording lifecycle

### Session recordings

1. A create request returns `202 Accepted` with `recording_uuid`; this is the admission acknowledgement.
2. The liveness check emits exactly one of [`recording.became_available`](#recordingbecame_available) or [`recording.failed`](#recordingfailed).
3. A stop command or session hangup emits [`recording.ended`](#recordingended).

In short: `202 Accepted` → (`recording.became_available` | `recording.failed`) → `recording.ended`.

`recording.became_available` and `recording.failed` are mutually exclusive. `recording.failed` and a later `recording.ended` are not mutually exclusive.

### Room recordings

1. A start command returns `202 Accepted`; this is the admission acknowledgement.
2. A successful liveness check emits [`recording.became_available`](#recordingbecame_available); no event is emitted when the check does not confirm liveness.
3. A room recording stop emits [`recording.ended`](#recordingended).

Room recordings emit `recording.became_available` when liveness succeeds and do not emit `recording.failed`.

## Recording URLs

Fetch recording URLs without an `Authorization` header. Audio responses use `Content-Type: audio/wav`.

* `expires_at` is the exact expiry. The default validity window is 24 hours and may be configured differently.
* Fresh URLs are minted independently for lifecycle events. In particular, `recording.ended` provides a new default 24-hour download window after stop.
* Fetching a URL does not extend its validity.
* Invalid, expired, malformed, or altered URLs return `403 Forbidden`.
* Unknown recordings return `404 Not Found`; recordings removed after retention return `410 Gone`.
* `pull` streams the whole recording so far while active and supports HTTP Range after completion.
* `live` tails from the current offset and is not Range-capable.

Applications that need longer-lived audio must download the WAV before the chosen URL expires and store it themselves.

## Triggered by

* `recording.became_available` — the successful liveness-check outcome after a Session Recording create or room recording start.
* `recording.failed` — the unsuccessful liveness-check outcome after a Session Recording create only.
* `recording.ended` — `POST /v1/sessions/{uuid}/recordings/{recording_uuid}/stop`, Session hangup while recording, and `POST /api/v1/rooms/{room_id}/playback/record/stop`.

Recording create/start, mask, and unmask admission is acknowledged only by each
request's `202 Accepted` response.