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.
Endpoint overview
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
GET /v3/rest/recordings
Returns recordings visible to the authenticated user or organization.
Query parameters
Success response
200 OK
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
POST /v3/rest/recordings
Creates the recording resource and returns its public UUID immediately. Media processing, when requested, continues asynchronously.
Request body
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, 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: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-negativestartSec 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}.
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:
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
For example, an oversized transcript returns a stable limit code:
Get a recording
GET /v3/rest/recordings/{recordingId}
Returns the current recording, asset, transcript, participant, and canonical metadata state.
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.
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
GET /v3/rest/recordings/{recordingId}/transcript
Returns transcript text as sentence-level utterances or contiguous same-speaker monologues.
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
Monologue response
200 OK
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 provideassetUrl when you intend to use this flow.
1
Create the recording
Call
POST /v3/rest/recordings and store the returned recording id.2
Create an upload
Call
POST /v3/rest/recordings/{recordingId}/uploads with the file name and byte size.3
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.4
Poll status
Poll
GET /v3/rest/recordings/{recordingId}/uploads/{uploadId} until the phase is ready, failed, or cancelled.Create an upload
POST /v3/rest/recordings/{recordingId}/uploads
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
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.
Get upload status
GET /v3/rest/recordings/{recordingId}/uploads/{uploadId}
200 OK
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
DELETE /v3/rest/recordings/{recordingId}/uploads/{uploadId}
Cancels an upload while it is still waiting for file bytes.
200 OK
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, includeassetUrl 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
POST /v3/rest/recordings/meeting-joins
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:
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 toGET /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.