Sections

MeetWave API · Guides

Get a Zoom meeting transcript with an API

Zoom's API has a transcript only with cloud recording and audio transcription on; for speakers and names, send the audio to MeetWave.

The short answer

Zoom's recordings API lists a TRANSCRIPT file, a VTT, only when the meeting was recorded to the cloud on a Pro or higher account with Create audio transcript turned on. When it is there, download it. When it is not, when it has "Unknown Speaker" lines, or the recording is local, take the recording's audio file, download it with your Zoom token on your side, and send it to MeetWave: you get JSON with speakers and the names said in the audio.

Get API key — first hour free

What Zoom's API gives you

GET https://api.zoom.us/v2/meetings/{meetingId}/recordings returns the meeting's recording_files. Each has a file_type and a download_url; download it with your OAuth token as Authorization: Bearer, and follow the redirect it may answer with. The app needs the scope cloud_recording:read:list_recording_files:admin (Zoom API reference, read 2026-10-21).

file_typeMeaning
TRANSCRIPTZoom's audio transcript, a VTT file. Only when "Create audio transcript" is on.
M4AThe audio only. The file to send for a transcript with speakers and names.
MP4The video. Works too when there is no M4A; it is larger to download and upload.
CCClosed captions, a VTT file.
CHATThe in-meeting chat, a TXT file.

The TRANSCRIPT file exists only with cloud recording on a Pro, Business, Education or Enterprise account and the Create audio transcript setting on. Zoom puts each phrase under a participant, marks the ones it cannot place "Unknown Speaker" for you to rename in the web portal, and makes the transcript in English unless another of its 19 languages is chosen (Zoom support, read 2026-10-21).

What Zoom gives and what you get here

Zoom TRANSCRIPT fileRecording audio through MeetWave
NeedsPro or higher, cloud recording, "Create audio transcript" on before the meetingThe recording's audio: the cloud M4A or MP4, or a local recording file
SpeakersEach phrase under the participant Zoom attributes it to, or "Unknown Speaker"Each line has one speaker, with times for every word
Speaker namesZoom's attribution; an unknown speaker is renamed by hand in the web portalThe name each voice is called in the audio, also for several people on one microphone
LanguagesEnglish by default; 19 languages listed by Zoom100 languages, detected or named
FormatVTTJSON, TXT, SRT or VTT
Price per audio hourIncluded in the Zoom plan$0.12 text, $0.15 with speakers, $0.20 with names

Zoom's transcript or the audio: one script for both

The script takes a meeting id and does both paths:

  1. Gets a Server-to-Server OAuth token: POST https://zoom.us/oauth/token with grant_type=account_credentials, valid for one hour (Zoom).
  2. Lists the recording's files.
  3. Zoom made a transcript: saves it as zoom-transcript.vtt.
  4. You need speakers and names: downloads the M4A (or the MP4) with your Zoom token, uploads it to MeetWave and prints the transcript with speaker names.

Your Zoom token never leaves your machine: MeetWave gets the audio file, not access to your Zoom account. Already have the file, from the cloud or a local recording? Pass its path instead of the meeting id.

#!/usr/bin/env bash
# Turn a Zoom cloud recording into a transcript with speaker names.
#
# Zoom's own transcript (the TRANSCRIPT file) exists only when audio transcription
# is on; when Zoom made one, it is saved as zoom-transcript.vtt. The audio file
# (M4A, or the MP4 when there is no M4A) is downloaded here, with your Zoom token,
# then sent to MeetWave for a transcript with speakers and their names.
#
#   export ZOOM_ACCOUNT_ID=... ZOOM_CLIENT_ID=... ZOOM_CLIENT_SECRET=...   # Server-to-Server OAuth app
#   export MEETWAVE_API_KEY=mwk_...
#   bash zoom-recording.sh <meeting id>
#   bash zoom-recording.sh zoom-audio.m4a      # an audio file you already downloaded
#
# Needs curl and jq.
set -euo pipefail

API_BASE="${MEETWAVE_API_BASE:-https://api.meetwave.io}"
API_BASE="${API_BASE%/}"

if [ -z "${MEETWAVE_API_KEY:-}" ]; then
  echo "Set MEETWAVE_API_KEY first. Get a key with one free hour: https://meetwave.io/speech-to-text-api?utm_source=github&utm_medium=integration&utm_campaign=api" >&2
  exit 1
fi
if [ "$#" -ne 1 ]; then
  echo "Usage: bash zoom-recording.sh <meeting id | audio file>" >&2
  exit 1
fi

# What to do when the API refuses the key or the balance is empty.
hint() {
  case "$1" in
    401) echo "Check MEETWAVE_API_KEY, or get a key: https://meetwave.io/speech-to-text-api?utm_source=github&utm_medium=integration&utm_campaign=api" >&2 ;;
    402) echo "Top up your balance: https://app.meetwave.io/developers?utm_source=github&utm_medium=integration&utm_campaign=api" >&2 ;;
  esac
}

# Calls an API; prints the answer, or prints the error answer and stops.
call() {
  local body status
  body=$(curl -s -w '\n%{http_code}' "$@")
  status="${body##*$'\n'}"
  body="${body%$'\n'*}"
  if [ "$status" -ge 400 ]; then
    echo "$status $body" >&2
    if [ -n "${MEETWAVE_CALL:-}" ]; then hint "$status"; fi
    exit 1
  fi
  echo "$body"
}
meetwave() { MEETWAVE_CALL=1 call -A "meetwave-examples/zoom" -H "Authorization: Bearer $MEETWAVE_API_KEY" "$@"; }

if [ -f "$1" ]; then
  AUDIO="$1"
else
  # 1. A Server-to-Server OAuth token, valid for one hour; list the recording's files
  ZOOM_TOKEN=$(call -X POST https://zoom.us/oauth/token -u "$ZOOM_CLIENT_ID:$ZOOM_CLIENT_SECRET" \
    -d grant_type=account_credentials -d "account_id=$ZOOM_ACCOUNT_ID" | jq -r .access_token)
  FILES=$(call "https://api.zoom.us/v2/meetings/$1/recordings" -H "Authorization: Bearer $ZOOM_TOKEN" | jq .recording_files)

  # 2. Zoom made a transcript: save it as it is
  TRANSCRIPT_URL=$(echo "$FILES" | jq -r 'map(select(.file_type == "TRANSCRIPT"))[0].download_url // empty')
  if [ -n "$TRANSCRIPT_URL" ]; then
    curl -sfL -H "Authorization: Bearer $ZOOM_TOKEN" -o zoom-transcript.vtt "$TRANSCRIPT_URL"
    echo "Zoom's transcript saved to zoom-transcript.vtt"
  fi

  # 3. Download the audio on your side, with your Zoom token
  FILE=$(echo "$FILES" | jq -c '(map(select(.file_type == "M4A")) + map(select(.file_type == "MP4")))[0] // empty')
  [ -n "$FILE" ] || { echo "This recording has no M4A or MP4 file." >&2; exit 1; }
  AUDIO="zoom-audio.$(echo "$FILE" | jq -r '.file_extension | ascii_downcase')"
  curl -sfL -H "Authorization: Bearer $ZOOM_TOKEN" -o "$AUDIO" "$(echo "$FILE" | jq -r .download_url)"
fi

# 4. Upload it to MeetWave and transcribe it with speakers and their names
UPLOAD=$(meetwave -X POST "$API_BASE/v1/uploads")
curl -sf -T "$AUDIO" "$(echo "$UPLOAD" | jq -r .upload_url)" || { echo "Upload failed" >&2; exit 1; }
ID=$(meetwave "$API_BASE/v1/transcriptions" -H "Content-Type: application/json" \
  -d "$(jq -n --arg id "$(echo "$UPLOAD" | jq -r .id)" '{upload_id: $id, speakers: true, speaker_names: true}')" | jq -r .id)

# 5. Wait while it is queued or processing, then print it
while :; do
  TRANSCRIPTION=$(meetwave "$API_BASE/v1/transcriptions/$ID")
  case "$(echo "$TRANSCRIPTION" | jq -r .status)" in
    queued|processing) sleep 5 ;;
    completed) break ;;
    *) echo "Transcription failed: $(echo "$TRANSCRIPTION" | jq -c .error)" >&2; exit 1 ;;
  esac
done
echo "$TRANSCRIPTION" | jq -r '.transcript.segments[]
  | "[\(.start | floor / 60 | floor | tostring | if length < 2 then "0" + . else . end):\(.start | floor % 60 | tostring | if length < 2 then "0" + . else . end)] \(.speaker // "Speaker"): \(.text)"'
echo
echo "$TRANSCRIPTION" | jq -r '"\(.id): \(.billing.audio_seconds) s of audio, $\(.billing.cost_usd)"'

Each step of the MeetWave part is explained in Transcriptions.

Without polling: Zoom's webhook, then ours

  1. Subscribe your Zoom app to recording.completed. Its body carries the recording_files and a download_token that works for 24 hours (Zoom events, read 2026-10-21).
  2. On that event, download the M4A with the download_token as Authorization: Bearer.
  3. Upload it with POST /v1/uploads and create the transcription with speaker_names: true and your webhook_url.
  4. MeetWave sends transcription.completed to that address; check its signature and fetch the transcript with GET /v1/transcriptions/{id}. See Webhooks.

Zoom also sends recording.transcript_completed when its own transcript is ready.

Get API key — first hour free