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

# Recordings API

> Create recordings, associate them with folders, attach media, and read recording and transcript data through the BuildBetter REST API.

Use the Recordings API to create a recording before its media is available, associate it with folders, attach media by URL or direct upload, and read the resulting recording and transcript. Recording resources use public UUIDs.

Most recording endpoints accept either a user access token or an organization API key. The examples below use an organization API key:

```bash theme={null}
X-BuildBetter-Api-Key: ORGANIZATION_API_KEY
```

The base URL for these endpoints is:

```text theme={null}
https://api.buildbetter.app/v3/rest
```

Except for the meeting-join command called out below, every endpoint on this page accepts either user authentication or an organization API key. Missing or invalid authentication returns `401 Unauthorized`. Authorization failures return the status documented for the operation. Resource paths return `404 Not Found` for unknown, inaccessible, cross-organization, or mismatched public UUIDs.

<Warning>
  The GraphQL API is being deprecated for customer integrations. Use these REST endpoints for new recording and transcript integrations.
</Warning>

## Endpoint overview

| Method   | Path                                                   | Purpose                                                                  |
| -------- | ------------------------------------------------------ | ------------------------------------------------------------------------ |
| `GET`    | `/v3/rest/recordings`                                  | List recordings                                                          |
| `POST`   | `/v3/rest/recordings`                                  | Create a recording                                                       |
| `GET`    | `/v3/rest/recordings/{recordingId}`                    | Get a recording                                                          |
| `GET`    | `/v3/rest/recordings/{recordingId}/transcript`         | Get utterances or monologues                                             |
| `POST`   | `/v3/rest/recordings/{recordingId}/uploads`            | Create a direct upload                                                   |
| `GET`    | `/v3/rest/recordings/{recordingId}/uploads/{uploadId}` | Get upload status                                                        |
| `DELETE` | `/v3/rest/recordings/{recordingId}/uploads/{uploadId}` | Cancel an upload that has not begun processing                           |
| `POST`   | `/v3/rest/recordings/meeting-joins`                    | Ask the BuildBetter recorder to join a meeting; user authentication only |

## Public IDs

`recordingId`, `folderIds`, `uploadId`, participant `personId`, metadata `fieldId`, and other IDs documented on this page are public UUIDs. Do not send GraphQL BigInt IDs to these endpoints.

The `id` returned by recording create, get, and list is the recording's public UUID. Store it and use it in transcript and upload paths.

Feedback IDs and recording IDs identify different resources and are not interchangeable. To associate a recording with folders, send the recording create request with the folders' public UUIDs in `folderIds`.

## List recordings

<Card title="GET /v3/rest/recordings" icon="list">
  Returns recordings visible to the authenticated user or organization.

  ```bash theme={null}
  curl "https://api.buildbetter.app/v3/rest/recordings?page=1&limit=50&recorded_at_gte=2026-07-01T00%3A00%3A00.000Z" \
    -H "X-BuildBetter-Api-Key: ORGANIZATION_API_KEY"
  ```
</Card>

### Query parameters

| Parameter         | Required | Description                                                |
| ----------------- | -------- | ---------------------------------------------------------- |
| `page`            | No       | Page number, starting at `1`. Defaults to `1`.             |
| `limit`           | No       | Results per page, from `1` to `100`. Defaults to `50`.     |
| `recorded_at_gt`  | No       | Return recordings strictly after this RFC 3339 timestamp.  |
| `recorded_at_gte` | No       | Return recordings at or after this RFC 3339 timestamp.     |
| `recorded_at_lt`  | No       | Return recordings strictly before this RFC 3339 timestamp. |
| `recorded_at_lte` | No       | Return recordings at or before this RFC 3339 timestamp.    |

### Success response

`200 OK`

```json theme={null}
{
  "recordings": [
    {
      "id": "0198f3b0-6e2d-7b11-8e12-91b2f91f6001",
      "display_name": "Renewal discussion",
      "source": "upload",
      "recorded_at": "2026-07-28T15:00:00.000Z",
      "created_at": "2026-07-28T15:05:00.000Z",
      "updated_at": "2026-07-28T15:05:00.000Z",
      "metadata": [
        {
          "source": {
            "type": "api",
            "external_entity_name": "recordings"
          },
          "record": {
            "external_id": "crm-call-4815",
            "fields": []
          }
        }
      ]
    }
  ],
  "page": 1,
  "limit": 50,
  "has_more": false
}
```

The list endpoint retains snake\_case field names for its established list projection. Its `metadata` array can contain canonical API metadata and other source-scoped records linked to the recording. Use `has_more` to decide whether to request the next page. Individual recording reads use the camelCase resource shown below and project only the singular canonical API record into `record`.

## Create a recording

<Card title="POST /v3/rest/recordings" icon="plus">
  Creates the recording resource and returns its public UUID immediately. Media processing, when requested, continues asynchronously.

  ```bash theme={null}
  curl -X POST "https://api.buildbetter.app/v3/rest/recordings" \
    -H "X-BuildBetter-Api-Key: ORGANIZATION_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Renewal discussion",
      "recordedAt": "2026-07-28T15:00:00.000Z",
      "folderIds": [
        "0198f3b0-6e2d-7b11-8e12-91b2f91f6501",
        "0198f3b0-6e2d-7b11-8e12-91b2f91f6502"
      ],
      "record": {
        "externalId": "crm-call-4815",
        "values": [
          {
            "fieldId": "0198f3b0-6e2d-7b11-8e12-91b2f91f6101",
            "value": 125000
          }
        ]
      },
      "participants": [
        {
          "speaker": 0,
          "person": {
            "type": "upsert",
            "email": "pat@example.com",
            "firstName": "Pat",
            "lastName": "Lee"
          }
        }
      ],
      "transcript": {
        "utterances": [
          {
            "startSec": 0,
            "endSec": 2.5,
            "text": "Thanks for making time today.",
            "speaker": 0
          }
        ]
      }
    }'
  ```
</Card>

### Request body

| Field               | Required | Description                                                                                                                           |
| ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `record`            | Yes      | The recording's single canonical metadata record.                                                                                     |
| `record.externalId` | Yes      | Your unique identifier for this recording, from `1` to `512` characters after trimming.                                               |
| `record.values`     | No       | Up to 500 typed metadata values. Defaults to `[]`. Each item contains a public `fieldId` and JSON `value`.                            |
| `name`              | No       | Recording name, from `1` to `500` characters. Defaults to `Untitled recording`.                                                       |
| `recordedAt`        | No       | RFC 3339 date-time with an offset.                                                                                                    |
| `folderIds`         | No       | Up to 500 public folder UUIDs to associate with the recording. Duplicate UUIDs have set semantics. Defaults to no folder association. |
| `assetUrl`          | No       | An HTTP or HTTPS media URL for BuildBetter to ingest asynchronously. Maximum length is 4,096 characters.                              |
| `participants`      | No       | Up to 100 speaker-to-person assignments. Defaults to `[]`.                                                                            |
| `transcript`        | No       | An authoritative transcript containing one or more ordered utterances.                                                                |

The server always places `record` in the organization's dedicated metadata source where `type` is `api` and `externalEntityName` is `recordings`. If the source does not exist yet, BuildBetter creates it on demand. The request does not accept a metadata source selector. Create fields for this source through the [Metadata API](/pages/api/metadata), then send their public UUIDs in `record.values`.

### Folder association

`folderIds` works with either a user access token or an organization API key. BuildBetter deduplicates the list and resolves every folder under the authenticated user or organization context before creating the recording. Unknown, inaccessible, and cross-organization folder UUIDs are all returned as `404 Not Found` without revealing whether the folder exists elsewhere.

The recording and every requested folder association are committed in one transaction. If any folder cannot be resolved or any association cannot be written, BuildBetter creates neither the recording nor a partial set of folder associations. This also supports transcript-only integrations: media upload is not required to place the new recording in its destination folders.

An `assetUrl` must use HTTP or HTTPS and cannot contain embedded credentials. The create request rejects localhost names and literal private or reserved IP addresses without performing DNS. BuildBetter resolves and revalidates the hostname in the durable ingestion worker, including every redirect, so transient DNS failures can be retried instead of failing recording creation permanently.

### Participant shapes

Reference an existing person by public UUID:

```json theme={null}
{
  "speaker": 0,
  "person": {
    "type": "reference",
    "personId": "0198f3b0-6e2d-7b11-8e12-91b2f91f6201"
  }
}
```

Or upsert a person by email:

```json theme={null}
{
  "speaker": 1,
  "person": {
    "type": "upsert",
    "email": "alex@example.com",
    "firstName": "Alex",
    "lastName": "Morgan"
  }
}
```

`speaker` must be a non-negative integer. An upsert requires `email`; names are optional. Within one request, a speaker cannot resolve to multiple people and a person cannot resolve to multiple speakers. When `transcript` is present, every participant speaker must appear in at least one supplied utterance; utterance speakers may remain unlabeled.

### Transcript rules

Each utterance requires non-negative `startSec` and `endSec`, non-blank `text`, and a non-negative integer `speaker`. `endSec` must be greater than or equal to `startSec`. Utterances must be ordered by `startSec`, and utterances for the same speaker cannot overlap.

A transcript can contain up to 10,000 utterances and 2,000,000 text characters in total. Each utterance can contain up to 20,000 characters.

When you provide `transcript`, BuildBetter treats it as authoritative: transcript status is immediately `completed`, transcript source is `api`, and BuildBetter does not submit the recording for automatic transcription. This remains true whether or not you also provide media.

### Success response

`201 Created` with a `Location` header pointing to `/v3/rest/recordings/{recordingId}`.

```json theme={null}
{
  "id": "0198f3b0-6e2d-7b11-8e12-91b2f91f6001",
  "name": "Renewal discussion",
  "recordedAt": "2026-07-28T15:00:00.000Z",
  "asset": {
    "status": "waiting",
    "url": null,
    "errorCode": null
  },
  "transcript": {
    "status": "completed",
    "source": "api"
  },
  "participants": [
    {
      "speaker": 0,
      "person": {
        "id": "0198f3b0-6e2d-7b11-8e12-91b2f91f6201",
        "firstName": "Pat",
        "lastName": "Lee",
        "email": "pat@example.com"
      }
    }
  ],
  "record": {
    "id": "0198f3b0-6e2d-7b11-8e12-91b2f91f6301",
    "externalId": "crm-call-4815",
    "source": {
      "id": "0198f3b0-6e2d-7b11-8e12-91b2f91f6401",
      "type": "api",
      "externalEntityName": "recordings"
    },
    "values": [
      {
        "fieldId": "0198f3b0-6e2d-7b11-8e12-91b2f91f6101",
        "name": "contract_value",
        "type": "integer",
        "value": 125000
      }
    ]
  },
  "createdAt": "2026-07-28T15:05:00.000Z",
  "updatedAt": "2026-07-28T15:05:00.000Z"
}
```

The recording, folder associations, metadata record, metadata values, participants, and supplied transcript are created atomically. If validation fails, correct the request and retry with the same `record.externalId`; the failed request does not reserve it. After a successful create, another request using the same external ID in the `recordings` source returns `409 Conflict` with the existing recording's public UUID:

```json theme={null}
{
  "message": "recording-external-id-conflict",
  "recordingId": "0198f3b0-6e2d-7b11-8e12-91b2f91f6001"
}
```

If the original `201 Created` response was lost, treat this conflict as recovery of that recording and continue with the returned `recordingId`. The API does not create or update another recording in this case.

### Create errors

| Status                     | When it is returned                                                                                                                                                                                                          |
| -------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400 Bad Request`          | The body is malformed, `record` or `record.externalId` is missing, an unknown property is supplied, a metadata field appears more than once, participant assignments conflict, or transcript ordering/timing is invalid.     |
| `401 Unauthorized`         | Authentication is missing or invalid.                                                                                                                                                                                        |
| `403 Forbidden`            | The principal cannot create recordings in the organization.                                                                                                                                                                  |
| `404 Not Found`            | A referenced public person, metadata field, or folder does not exist or is not visible under the authenticated user or organization context.                                                                                 |
| `409 Conflict`             | The canonical `record.externalId` already exists. The response uses `message: recording-external-id-conflict` and returns the existing public `recordingId`.                                                                 |
| `413 Payload Too Large`    | The supplied transcript exceeds the utterance or total-character limit. The response includes `errorCode`: `too_many_utterances`, `transcript_too_large`, or the defensive fallback `recording_payload_too_large`.           |
| `422 Unprocessable Entity` | A metadata field belongs to another source or receives a value that does not match its field type. When applicable, the response identifies the failed value with `externalId`, public `fieldId`, `fieldType`, and `reason`. |

For example, an oversized transcript returns a stable limit code:

```json theme={null}
{
  "message": "Transcript exceeds 10000 utterances",
  "errorCode": "too_many_utterances"
}
```

A typed metadata failure includes field-specific details that can be shown to the caller:

```json theme={null}
{
  "message": "Metadata value does not match the field type",
  "externalId": "crm-call-4815",
  "fieldId": "0198f3b0-6e2d-7b11-8e12-91b2f91f6101",
  "fieldType": "integer",
  "reason": "Expected a safe integer"
}
```

## Get a recording

<Card title="GET /v3/rest/recordings/{recordingId}" icon="file-audio">
  Returns the current recording, asset, transcript, participant, and canonical metadata state.

  ```bash theme={null}
  curl "https://api.buildbetter.app/v3/rest/recordings/0198f3b0-6e2d-7b11-8e12-91b2f91f6001" \
    -H "X-BuildBetter-Api-Key: ORGANIZATION_API_KEY"
  ```
</Card>

### Success response

`200 OK`. The response uses the same recording resource shape returned by create. `record` can be `null` for older recordings that predate canonical API metadata records.

```json theme={null}
{
  "id": "0198f3b0-6e2d-7b11-8e12-91b2f91f6001",
  "name": "Renewal discussion",
  "recordedAt": "2026-07-28T15:00:00.000Z",
  "asset": {
    "status": "completed",
    "url": "https://media.example.invalid/temporary-playback",
    "errorCode": null
  },
  "transcript": {
    "status": "completed",
    "source": "api"
  },
  "participants": [],
  "record": {
    "id": "0198f3b0-6e2d-7b11-8e12-91b2f91f6301",
    "externalId": "crm-call-4815",
    "source": {
      "id": "0198f3b0-6e2d-7b11-8e12-91b2f91f6401",
      "type": "api",
      "externalEntityName": "recordings"
    },
    "values": []
  },
  "createdAt": "2026-07-28T15:05:00.000Z",
  "updatedAt": "2026-07-28T15:12:00.000Z"
}
```

`asset.url` is a temporary playback URL derived at request time and may expire. The asset status can be `waiting`, `queued`, `uploading`, `processing`, `completed`, `failed`, or `cancelled`. `asset.errorCode` provides a stable failure code when one is available.

The asset and transcript states are independent. For example, a supplied transcript can be complete while media is still queued, or uploaded media can be complete while automatic transcription is still processing.

Unknown or inaccessible recording UUIDs return `404 Not Found`.

## Get a transcript

<Card title="GET /v3/rest/recordings/{recordingId}/transcript" icon="file-lines">
  Returns transcript text as sentence-level utterances or contiguous same-speaker monologues.

  ```bash theme={null}
  curl "https://api.buildbetter.app/v3/rest/recordings/0198f3b0-6e2d-7b11-8e12-91b2f91f6001/transcript?type=utterance" \
    -H "X-BuildBetter-Api-Key: ORGANIZATION_API_KEY"
  ```
</Card>

The optional `type` query parameter accepts:

* `utterance`: sentence-level transcript rows. This is the default.
* `monologue`: contiguous rows from the same speaker combined into blocks.

### Utterance response

`200 OK`

```json theme={null}
{
  "type": "utterance",
  "recordingId": "0198f3b0-6e2d-7b11-8e12-91b2f91f6001",
  "utterances": [
    {
      "recordingId": "0198f3b0-6e2d-7b11-8e12-91b2f91f6001",
      "startSec": 0,
      "endSec": 2.5,
      "speaker": 0,
      "text": "Thanks for making time today."
    }
  ]
}
```

### Monologue response

`200 OK`

```json theme={null}
{
  "type": "monologue",
  "recordingId": "0198f3b0-6e2d-7b11-8e12-91b2f91f6001",
  "monologues": [
    {
      "recordingId": "0198f3b0-6e2d-7b11-8e12-91b2f91f6001",
      "startSec": 0,
      "endSec": 8.1,
      "speaker": 0,
      "text": "Thanks for making time today. I wanted to review the renewal."
    }
  ]
}
```

Transcript entries do not include internal database IDs. Unknown or inaccessible recording UUIDs return `404 Not Found`; an unsupported `type` returns `400 Bad Request`.

## Attach a direct upload

Create the recording first, then create an upload nested beneath its public UUID. Do not provide `assetUrl` when you intend to use this flow.

<Steps>
  <Step title="Create the recording">
    Call `POST /v3/rest/recordings` and store the returned recording `id`.
  </Step>

  <Step title="Create an upload">
    Call `POST /v3/rest/recordings/{recordingId}/uploads` with the file name and byte size.
  </Step>

  <Step title="Transfer the file">
    Send the file to the temporary `uploadUrl` using a resumable upload client. Treat this URL as a short-lived secret and do not log or persist it.
  </Step>

  <Step title="Poll status">
    Poll `GET /v3/rest/recordings/{recordingId}/uploads/{uploadId}` until the phase is `ready`, `failed`, or `cancelled`.
  </Step>
</Steps>

### Create an upload

<Card title="POST /v3/rest/recordings/{recordingId}/uploads" icon="upload">
  ```bash theme={null}
  curl -X POST "https://api.buildbetter.app/v3/rest/recordings/0198f3b0-6e2d-7b11-8e12-91b2f91f6001/uploads" \
    -H "X-BuildBetter-Api-Key: ORGANIZATION_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "fileName": "renewal-discussion.mp4",
      "fileSize": 73400320
    }'
  ```
</Card>

`fileName` must be non-blank and `fileSize` must be an integer from `1` through `9,007,199,254,740,991` containing the file's size in bytes. The request also accepts optional public UUIDs `folderId` and `batchId`. The folder must be visible to the principal. Upload batches are user-scoped, so an organization API key cannot attach an upload to a batch. `inMobileApp` defaults to `false` and is reserved for BuildBetter mobile clients.

Browser requests authenticated as a user must include their `Origin` header so the temporary upload destination can be scoped for CORS. Server-to-server requests using an organization API key do not require `Origin`.

`201 Created`

```json theme={null}
{
  "recordingId": "0198f3b0-6e2d-7b11-8e12-91b2f91f6001",
  "uploadId": "0198f3b0-6e2d-7b11-8e12-91b2f91f6501",
  "uploadUrl": "https://upload.example.invalid/temporary-target",
  "timeoutSeconds": 3600,
  "phase": "waiting"
}
```

The real `uploadUrl` is temporary and unique to this attempt. Begin the transfer before `timeoutSeconds` elapses.

A recording can have only one active media ingestion. A different create request while an upload or URL ingestion is active returns `409 Conflict`. If this exact request succeeded but its response was lost, retrying with the same recording, `fileName`, `fileSize`, source, `folderId`, and `batchId` returns the existing unexpired waiting upload attempt, including the same `uploadId` and `uploadUrl`. This recovery does not create a second Mux destination. The same status can ask a caller to retry after an in-progress credit recharge. Unknown recording, folder, or batch UUIDs return `404 Not Found`.

| Status             | When it is returned                                                                         |
| ------------------ | ------------------------------------------------------------------------------------------- |
| `400 Bad Request`  | The body is invalid, or a browser user request omits its `Origin` header.                   |
| `401 Unauthorized` | Authentication is invalid or recording upload is not allowed.                               |
| `403 Forbidden`    | The principal cannot access the recording or related destination.                           |
| `404 Not Found`    | The recording, folder, or batch UUID is unknown or inaccessible.                            |
| `409 Conflict`     | The recording already has an active media ingestion, or a credit recharge is still pending. |

### Get upload status

<Card title="GET /v3/rest/recordings/{recordingId}/uploads/{uploadId}" icon="spinner">
  ```bash theme={null}
  curl "https://api.buildbetter.app/v3/rest/recordings/0198f3b0-6e2d-7b11-8e12-91b2f91f6001/uploads/0198f3b0-6e2d-7b11-8e12-91b2f91f6501" \
    -H "X-BuildBetter-Api-Key: ORGANIZATION_API_KEY"
  ```
</Card>

`200 OK`

```json theme={null}
{
  "recordingId": "0198f3b0-6e2d-7b11-8e12-91b2f91f6001",
  "uploadId": "0198f3b0-6e2d-7b11-8e12-91b2f91f6501",
  "phase": "encoding",
  "encodeProgress": 42,
  "errorCode": null
}
```

| Phase        | Meaning                                                                                           |
| ------------ | ------------------------------------------------------------------------------------------------- |
| `waiting`    | The upload destination is waiting for file bytes.                                                 |
| `encoding`   | The media provider is encoding the asset. `encodeProgress` can contain a value from `0` to `100`. |
| `finalizing` | Encoding is complete and BuildBetter is finalizing storage.                                       |
| `ready`      | The media is available from the recording resource.                                               |
| `failed`     | The upload or processing failed.                                                                  |
| `cancelled`  | The upload was cancelled or expired before completion.                                            |

`encodeProgress` is `null` outside encoding or when live progress is unavailable. On failure, `errorCode` can be `upload_failed`, `upload_expired`, or `processing_failed`.

The `uploadId` must belong to the `recordingId` in the path. Unknown, inaccessible, or mismatched IDs return `404 Not Found`.

### Cancel an upload

<Card title="DELETE /v3/rest/recordings/{recordingId}/uploads/{uploadId}" icon="xmark">
  Cancels an upload while it is still waiting for file bytes.

  ```bash theme={null}
  curl -X DELETE "https://api.buildbetter.app/v3/rest/recordings/0198f3b0-6e2d-7b11-8e12-91b2f91f6001/uploads/0198f3b0-6e2d-7b11-8e12-91b2f91f6501" \
    -H "X-BuildBetter-Api-Key: ORGANIZATION_API_KEY"
  ```
</Card>

`200 OK`

```json theme={null}
{
  "recordingId": "0198f3b0-6e2d-7b11-8e12-91b2f91f6001",
  "uploadId": "0198f3b0-6e2d-7b11-8e12-91b2f91f6501",
  "phase": "cancelled",
  "encodeProgress": null,
  "errorCode": null
}
```

Cancellation releases the waiting media claim, so you can create a new upload for the same recording. Repeating the DELETE after a successful cancellation returns the same `cancelled` state. Once processing has begun, cancellation returns `409 Conflict`; continue polling the existing attempt instead. Unknown or mismatched IDs return `404 Not Found`.

## Ingest media from a URL

As an alternative to direct upload, include `assetUrl` in `POST /v3/rest/recordings`. BuildBetter validates locally decidable HTTP or HTTPS URL policy, creates the recording and a durable ingestion attempt atomically, and schedules media transfer asynchronously. DNS resolution and destination validation happen in the durable worker, not in the create request. The create response does not wait for the media transfer.

Poll `GET /v3/rest/recordings/{recordingId}` and inspect `asset.status`. A retryable transfer remains `queued`; terminal failures use `failed` and may include `asset.errorCode`. Transient DNS failures and a successful HTTP response containing zero media bytes are retryable; empty media is never published as a completed asset. Do not start a nested upload while URL ingestion is active, because the recording permits one active media ingestion at a time.

The URL must be directly retrievable with an unauthenticated `GET`. BuildBetter accepts common AAC, FLAC, MP4, MPEG, Ogg, WAV, WebM, and QuickTime audio/video content types, plus `application/octet-stream`, up to 5 GiB.

## Ask the recorder to join a meeting

<Warning>
  This command requires an authenticated BuildBetter user. Organization API keys are not accepted and return `403 Forbidden`.
</Warning>

<Card title="POST /v3/rest/recordings/meeting-joins" icon="video">
  ```bash theme={null}
  curl -X POST "https://api.buildbetter.app/v3/rest/recordings/meeting-joins" \
    -H "Authorization: Bearer USER_ACCESS_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "type": "meetingUrl",
      "joinUrl": "https://meet.example.com/example-room"
    }'
  ```
</Card>

`type` must be `meetingUrl`, and `joinUrl` must be a non-blank meeting URL supported by the recorder.

When the join request is accepted, the endpoint returns `201 Created`:

```json theme={null}
{
  "recordingId": "0198f3b0-6e2d-7b11-8e12-91b2f91f6601"
}
```

The response identifies the recording resource immediately; joining, recording, media processing, and transcription continue asynchronously.

| Status             | When it is returned                                           |
| ------------------ | ------------------------------------------------------------- |
| `400 Bad Request`  | The body does not match the meeting URL request shape.        |
| `401 Unauthorized` | User authentication is missing or invalid.                    |
| `403 Forbidden`    | An organization API key is used for this user-only operation. |
| `404 Not Found`    | The authenticated user cannot be resolved.                    |
| `409 Conflict`     | The provided meeting URL is not supported for joining.        |
| `502 Bad Gateway`  | The recorder provider could not create the bot.               |

An authenticated user whose organization is not allowed to record receives `401 Unauthorized`. If an active recording already exists for the same meeting identity, the endpoint returns that recording's public UUID rather than creating another recording.

## Legacy GraphQL transcript migration

Move transcript reads to `GET /v3/rest/recordings/{recordingId}/transcript`. Existing GraphQL integrations should avoid `interview.monologues` and use the REST transcript endpoint with `type=monologue` when contiguous same-speaker blocks are required.
