Skip to main content
Paid plans only Daily offers two transcription approaches via the REST API:
  • Real-time — start, update, and stop transcription during an active call using the /rooms/{room_name}/transcription endpoints.
  • Post-call — submit a recording ID or media URL to the Batch Processor after a call ends to generate a transcript (and optionally a summary).
Both approaches deliver lifecycle notifications via webhooks.

Real-time transcription

Real-time transcription runs during an active call and streams text to all in-call participants as speech is detected. Transcripts can optionally be saved to storage as WebVTT files.

Starting transcription

curl --request POST \
     --url https://api.daily.co/v1/rooms/my-room/transcription/start \
     --header 'Authorization: Bearer DAILY_API_KEY' \
     --header 'Content-Type: application/json' \
     --data '{
       "language": "en",
       "model": "nova-2-general",
       "punctuate": true
     }'
language
string
default:"'en'"
BCP-47 language tag. Examples: 'en', 'es', 'fr', 'de', 'ja', 'pt-BR'. Available languages depend on the Deepgram model in use.
model
string
Deepgram model name. Examples: 'nova-2-general', 'nova-2', 'enhanced', 'base'. Higher-tier models offer better accuracy at higher cost.
profanity_filter
boolean
When true, Deepgram replaces profane words with asterisks.
redact
Array<string> | boolean
Entities to redact. Pass true to redact all supported entities, or an array of strings (e.g. ['pci', 'ssn']).
endpointing
number | boolean
How long Deepgram waits for silence before ending an utterance. Pass a number (milliseconds) or true/false.
punctuate
boolean
When true, Deepgram adds punctuation to the transcript.
instanceId
string
Identifier for this transcription instance. Required when running multiple simultaneous transcriptions in the same room.
participants
string[]
Array of session IDs. When provided, only those participants’ audio is transcribed. Omit to transcribe everyone.
Check out Deepgram’s language support documentation for the model/language combination that works best for your use case.
See the start transcription endpoint for the full API reference.

Updating transcription

Change which participants are transcribed mid-call:
# Transcribe specific participants only
curl --request POST \
     --url https://api.daily.co/v1/rooms/my-room/transcription/update \
     --header 'Authorization: Bearer DAILY_API_KEY' \
     --header 'Content-Type: application/json' \
     --data '{ "participants": ["af5d66d0-d3fd-4cb8-a2ba-c681e429c8ba"] }'
# Restore transcription for everyone (empty array = all)
curl --request POST \
     --url https://api.daily.co/v1/rooms/my-room/transcription/update \
     --header 'Authorization: Bearer DAILY_API_KEY' \
     --header 'Content-Type: application/json' \
     --data '{ "participants": [] }'
Include instanceId to target a specific instance when running multiple transcriptions simultaneously. See the update transcription endpoint for the full API reference.

Stopping transcription

curl --request POST \
     --url https://api.daily.co/v1/rooms/my-room/transcription/stop \
     --header 'Authorization: Bearer DAILY_API_KEY' \
     --header 'Content-Type: application/json'
To stop a named instance: --data '{ "instanceId": "primary" }' If transcript storage is enabled, the final WebVTT file is written when transcription stops. Listen for the transcript.ready-to-download webhook to know when the file is available. See the stop transcription endpoint for the full API reference.

Auto-starting transcription

Instead of starting transcription manually, you can have it begin automatically when a meeting owner joins by setting auto_start_transcription on their meeting token. Pair it with auto_transcription_settings on the room to configure the model and language. Meeting token (triggers auto-start for that owner):
curl --request POST \
     --url https://api.daily.co/v1/meeting-tokens \
     --header 'Authorization: Bearer DAILY_API_KEY' \
     --header 'Content-Type: application/json' \
     --data '{
       "properties": {
         "room_name": "my-room",
         "is_owner": true,
         "auto_start_transcription": true
       }
     }'
Room config (sets the transcription options used when auto-start fires):
curl --request POST \
     --url https://api.daily.co/v1/rooms/my-room \
     --header 'Authorization: Bearer DAILY_API_KEY' \
     --header 'Content-Type: application/json' \
     --data '{
       "properties": {
         "auto_transcription_settings": {
           "model": "nova-2-general",
           "language": "en"
         }
       }
     }'
See the auto_start_transcription meeting token property and auto_transcription_settings room property for full details.

Transcript storage

By default, real-time transcripts are streamed to call participants but not saved. To persist transcripts as WebVTT files, enable storage at the room or domain level. Room level:
curl --request POST \
     --url https://api.daily.co/v1/rooms/my-room \
     --header 'Authorization: Bearer DAILY_API_KEY' \
     --header 'Content-Type: application/json' \
     --data '{"properties": {"enable_transcription_storage": true}}'
Domain level:
curl --request POST \
     --url https://api.daily.co/v1/ \
     --header 'Authorization: Bearer DAILY_API_KEY' \
     --header 'Content-Type: application/json' \
     --data '{"properties": {"enable_transcription_storage": true}}'
While transcription is active, the file is written every two minutes. The final version is written when transcription ends. Transcripts are stored in Daily’s cloud by default. To use your own S3 bucket, configure the transcription_bucket domain property:
curl --request POST \
     --url https://api.daily.co/v1/ \
     --header 'Authorization: Bearer DAILY_API_KEY' \
     --header 'Content-Type: application/json' \
     --data '{
       "properties": {
         "transcription_bucket": {
           "bucket_name": "my-bucket",
           "bucket_region": "us-east-1",
           "assume_role_arn": "arn:aws:iam::123456789000:role/DAILY_ROLE",
           "allow_api_access": true
         }
       }
     }'
The setup process is the same as configuring a custom S3 bucket for recordings.

Post-call transcription

The Batch Processor generates transcripts and summaries from recordings or any publicly accessible audio/video URL — no active call required.

Submitting a job

Specify a preset (transcript or summarize), an input source, and an output path:
curl --request POST \
     --url https://api.daily.co/v1/batch-processor \
     --header 'Authorization: Bearer DAILY_API_KEY' \
     --header 'Content-Type: application/json' \
     --data '{
       "preset": "transcript",
       "inParams": {
         "sourceType": "recordingId",
         "recordingId": "uuiasdfe-8ba2-4ee6-bd15-003a92c18245"
       },
       "outParams": {
         "s3Config": { "s3KeyTemplate": "transcript" }
       }
     }'
The response returns a job ID:
{ "id": "02c2508e-8835-4f3e-bcf2-e319d00f0eec" }
Output formats for transcript jobs: txt, srt, vtt, json. The summarize preset produces a plain text file.

Retrieving batch job results

Poll job status or get a signed download URL using the job ID returned from submit:
EndpointDescription
Get jobGET /batch-processor/{jobId} — check status and see output locations
Get job access linkGET /batch-processor/{jobId}/access-link — get a signed download URL
List jobsGET /batch-processor — list all jobs for your domain
Delete jobDELETE /batch-processor/{jobId} — delete a job and its output

Events via webhooks

Subscribe to transcription lifecycle events from the Daily dashboard or via the webhooks REST API. Events are delivered as POST requests to your webhook URL.

Real-time transcription events

transcript.started

Fired when real-time transcription begins. Includes the instanceId and, if storage is enabled, the S3 path where the transcript will be written.
{
  "version": "1.1.0",
  "type": "transcript.started",
  "payload": {
    "id": "68f65d4c-a4dc-4179-bbb1-c12432afb924",
    "info": { "instanceId": "a1f2f6b7-b1ac-4202-85e5-d446cb6c3d3f" },
    "room_name": "my-transcript-room",
    "mtg_session_id": "a2bc876d-2816-4732-9c87-01e0b8c0b01b",
    "status": "t_in_progress",
    "out_params": {
      "s3": { "key": "domain/room/1733234355482.vtt", "bucket": "daily-co-transcription-staging", "region": "us-west-2" }
    }
  },
  "event_ts": 1733234355.505
}
See the transcript.started reference.

transcript.ready-to-download

Fired when transcription ends and the saved transcript file is available. This is the completion signal — use out_params.s3 to locate the file, or use the get transcript link endpoint to get a signed URL.
Despite the name, transcript.ready-to-download is the equivalent of “transcription stopped” — it fires when the transcript reaches a finished state. You may also receive a transcript.error event if a storage error occurred alongside completion.
{
  "version": "1.1.0",
  "type": "transcript.ready-to-download",
  "payload": {
    "id": "68f65d4c-a4dc-4179-bbb1-c12432afb924",
    "out_params": {
      "s3": { "key": "domain/room/1733234355482.vtt", "bucket": "daily-co-transcription-staging", "region": "us-west-2" }
    },
    "room_name": "my-transcript-room",
    "duration": 124.405,
    "status": "t_finished"
  },
  "event_ts": 1733234480.097
}
See the transcript.ready-to-download reference.

transcript.error

Fired if an error occurs during transcription or before it could start. Includes an error field with details. May fire alongside transcript.started or transcript.ready-to-download depending on when the error occurred.
{
  "version": "1.1.0",
  "type": "transcript.error",
  "payload": {
    "id": "8d5c03f3-cff3-43c7-b112-0f78989f659f",
    "room_name": "my-room",
    "status": "t_error",
    "error": "User is not authorized to perform: s3:PutObject on resource..."
  },
  "event_ts": 1733234946.616
}
See the transcript.error reference.

Batch Processor events

batch-processor.job-finished

Fired when a Batch Processor job completes. The output field contains S3 locations for all generated files (transcript in all formats, and summary if requested).
{
  "version": "1.0.0",
  "type": "batch-processor.job-finished",
  "payload": {
    "id": "1dae15c5-6a85-4d65-ab00-4b19bb054b84",
    "preset": "summarize",
    "status": "finished",
    "output": {
      "transcription": [
        { "format": "json", "s3Config": { "key": "staging/1dae15c5.../transcript/soap.json", "bucket": "...", "region": "us-west-2" } },
        { "format": "txt",  "s3Config": { "key": "staging/1dae15c5.../transcript/soap.txt",  "bucket": "...", "region": "us-west-2" } },
        { "format": "vtt",  "s3Config": { "key": "staging/1dae15c5.../transcript/soap.vtt",  "bucket": "...", "region": "us-west-2" } }
      ],
      "summary": { "format": "txt", "s3Config": { "key": "staging/1dae15c5.../transcript/soap", "bucket": "...", "region": "us-west-2" } }
    }
  },
  "event_ts": 1711402301.747
}
See the batch-processor.job-finished reference.

batch-processor.error

Fired when a Batch Processor job fails. Includes an error field with details.
{
  "version": "1.0.0",
  "type": "batch-processor.error",
  "payload": {
    "id": "bcb9f15d-2b00-4d98-be5f-23888e2df2ca",
    "preset": "summarize",
    "status": "error",
    "error": "transcript job failed: Error: Failed to download: 403 Forbidden"
  },
  "event_ts": 1711402402.539
}
See the batch-processor.error reference.

Accessing saved transcripts

Both real-time transcripts (when enable_transcription_storage is on) and Batch Processor transcripts are accessible via the /transcript endpoints. Use these to list, download, and clean up transcripts after the fact.

Listing transcripts

curl --request GET \
     --url https://api.daily.co/v1/transcript \
     --header 'Authorization: Bearer DAILY_API_KEY'
Returns an array of transcript objects for your domain. Each object includes a transcriptId, room and session identifiers, duration, and status.
curl --request GET \
     --url https://api.daily.co/v1/transcript/<TRANSCRIPT_ID>/access-link \
     --header 'Authorization: Bearer DAILY_API_KEY'
Returns a signed URL to download the WebVTT file. For Batch Processor transcripts, use the get job access link endpoint instead.

Deleting a transcript

curl --request DELETE \
     --url https://api.daily.co/v1/transcript/<TRANSCRIPT_ID> \
     --header 'Authorization: Bearer DAILY_API_KEY'
EndpointDescription
List transcriptsGET /transcript
Get transcriptGET /transcript/{transcriptId}
Get transcript linkGET /transcript/{transcriptId}/access-link
Delete transcriptDELETE /transcript/{transcriptId}

The transcript object

A transcript object represents a single transcription session:
{
  "transcriptId": "a759ae14-0327-4bf6-ac0d-192a0323f45b",
  "domainId": "04b89420-cf8b-4ea5-a428-02cc0a0e2a59",
  "roomId": "ad9e57d6-dcb8-4bb0-8100-247146b75713",
  "mtgSessionId": "586f7f8b-2a4f-4091-aa85-99a36094cb4e",
  "duration": 14,
  "status": "t_finished",
  "outParams": {
    "key": "my-domain/my-room/1699441864907.vtt",
    "region": "us-west-2",
    "bucket": "test-daily-meeting-recordings"
  }
}
Key status values:
  • "t_finished" — transcription is complete and the file is available
  • "isVttAvailable": true — the WebVTT file can be downloaded

Transcription overview

Billing, storage options, permissions, and a comparison of real-time vs. post-call approaches.

daily-js guide

Start and manage transcription from a call object with full parameter reference and a live captions example.

Daily React guide

Use the useTranscription hook for reactive transcription state in React apps.