Sections
MeetWave API · Batch
Transcriptions
Send an audio or video file or a link, ask for speakers and their names, then poll or wait for a webhook and fetch the result as JSON, TXT, SRT or VTT.
From file to transcript
One run from your key to a subtitle file: upload meeting.mp3, create the transcription with speakers and their names, wait until it ends, save it as SRT. Each id comes from the answer before it. Set MEETWAVE_API_KEY first; the curl version also needs jq. The sections below explain each step.
#!/usr/bin/env bash
# Save as quickstart.sh, then run: bash quickstart.sh
export MEETWAVE_API_KEY=mwk_... # your key, from the console
# Stops the run and prints the answer when it holds no id
need_id() { [ -n "$1" ] && [ "$1" != null ] || { echo "$2"; exit 1; }; }
# 1. Upload the file
UPLOAD=$(curl -s -X POST https://api.meetwave.io/v1/uploads \
-H "Authorization: Bearer $MEETWAVE_API_KEY")
UPLOAD_ID=$(echo "$UPLOAD" | jq -r .id)
need_id "$UPLOAD_ID" "$UPLOAD"
curl -sf -T meeting.mp3 "$(echo "$UPLOAD" | jq -r .upload_url)" || { echo "Upload failed"; exit 1; }
# 2. Create the transcription, with speakers and their names
CREATED=$(curl -s https://api.meetwave.io/v1/transcriptions \
-H "Authorization: Bearer $MEETWAVE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $UPLOAD_ID" \
-d '{"upload_id": "'"$UPLOAD_ID"'", "speakers": true, "speaker_names": true}')
TRANSCRIPTION_ID=$(echo "$CREATED" | jq -r .id)
need_id "$TRANSCRIPTION_ID" "$CREATED"
# 3. Wait while it is queued or processing
while :; do
ANSWER=$(curl -s https://api.meetwave.io/v1/transcriptions/$TRANSCRIPTION_ID \
-H "Authorization: Bearer $MEETWAVE_API_KEY")
STATUS=$(echo "$ANSWER" | jq -r .status)
case "$STATUS" in
queued|processing) sleep 5 ;;
*) break ;;
esac
done
[ "$STATUS" = completed ] || { echo "$ANSWER"; exit 1; }
# 4. Save the subtitles
curl -s -o meeting.srt https://api.meetwave.io/v1/transcriptions/$TRANSCRIPTION_ID/srt \
-H "Authorization: Bearer $MEETWAVE_API_KEY"
echo "Saved meeting.srt"How it works
- Upload the file, or have a public link to it.
- Create a transcription. It answers at once with an id and the status
queued. - Wait for a webhook, or ask for the transcription every few seconds.
- Read the transcript from the answer, or fetch it as a TXT, SRT or VTT file.
| Request | What it does |
|---|---|
POST /v1/uploads | Get a one-time address to PUT your file to. |
POST /v1/transcriptions | Start a transcription from an upload or a link. |
GET /v1/transcriptions/{id} | Status, cost, and the transcript once completed. |
GET /v1/transcriptions/{id}/{format} | The result as txt, srt or vtt. |
DELETE /v1/transcriptions/{id} | Erase the transcription now. |
Upload a file
Ask for an upload address, then PUT the raw file to it — the bytes themselves, not a form. The address works once and for 1 hour; one upload makes one transcription. Files up to 350 MB and 3 hours long, in any of these formats: mp3, wav, m4a, ogg, opus, flac, aac, wma, webm, mp4, mov, mkv, avi. A video file is fine: only its sound is used.
# 1. Ask for an upload address; keep its id and upload_url
UPLOAD=$(curl -s -X POST https://api.meetwave.io/v1/uploads \
-H "Authorization: Bearer $MEETWAVE_API_KEY")
UPLOAD_ID=$(echo "$UPLOAD" | jq -r .id)
UPLOAD_URL=$(echo "$UPLOAD" | jq -r .upload_url)
# 2. PUT the file to it
curl -T meeting.mp3 "$UPLOAD_URL"The answer: id is the upload id you create the transcription with (UPLOAD_ID in the samples); PUT the file to upload_url before expires_at. Use upload_url exactly as it comes, with its whole query: its form may change, so do not build it yourself or take it apart.
{
"id": "up_8k2m4q7w1x9c3v5b6n0z2p4r7t",
"upload_url": "https://upload.meetwave.io/api-uploads/up_8k2m4q7w1x9c3v5b6n0z2p4r7t?...",
"method": "PUT",
"expires_at": "2026-10-21T13:00:00Z"
}Or send a link
Instead of uploading, pass audio_url: a public http or https address MeetWave downloads the file from. It may redirect up to 3 times, and the download must finish within 10 minutes. A link that needs a login, answers with an error, or points to a private or local network address makes the transcription fail with DOWNLOAD_FAILED, free of charge. A signed link from your own storage works well.
Create a transcription
From the upload, with speakers, their names and a webhook. The id of the answer is the transcription id the next samples read as TRANSCRIPTION_ID.
TRANSCRIPTION_ID=$(curl -s https://api.meetwave.io/v1/transcriptions \
-H "Authorization: Bearer $MEETWAVE_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $UPLOAD_ID" \
-d '{"upload_id": "'"$UPLOAD_ID"'", "speakers": true, "speaker_names": true, "webhook_url": "https://example.com/hooks/meetwave"}' \
| jq -r .id)
echo "$TRANSCRIPTION_ID"From a link, with the language given instead of detected:
curl https://api.meetwave.io/v1/transcriptions \
-H "Authorization: Bearer $MEETWAVE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"audio_url": "https://example.com/recordings/interview.mp3", "language": "en"}'Options
| Option | Type | Meaning |
|---|---|---|
upload_id | string | The id from POST /v1/uploads. Pass this or audio_url, not both. |
audio_url | string | A public http or https link to the file. Pass this or upload_id. |
speakers | boolean | Tell speakers apart: each segment holds one speaker, and each word gets its own times. Default false. |
speakers_expected | integer | How many people speak, when you know it (1–10). Needs speakers. |
speaker_names | boolean | Name each speaker from what is said in the audio. Turns speakers on. Default false. |
language | string | auto (the default) detects it; or a code from the list of languages. |
webhook_url | string | An https address that gets a signed POST when the transcription completes or fails. See Webhooks. |
Sending the same request twice
Send an Idempotency-Key header (any string up to 64 characters) to make a retry safe. For 24 hours the same key with the same body returns the transcription it created the first time, and the same key with another body gets 409 IDEMPOTENCY_CONFLICT. The upload id makes a good key.
The answer
202 with the new transcription; below is the answer to the first request above. A file that is too large or is not audio or video, and an account with no credits left, are refused at once with an error instead.
{
"id": "tr_8k2m4q7w1x9c3v5b6n0z2p4r7t",
"object": "transcription",
"status": "queued",
"created_at": "2026-10-21T12:00:00Z",
"completed_at": null,
"expires_at": null,
"options": {
"speakers": true,
"speakers_expected": null,
"speaker_names": true,
"language": "auto"
},
"audio_duration": null,
"features": {
"speakers": "pending",
"speaker_names": "pending"
},
"billing": null,
"error": null,
"webhook": {
"url": "https://example.com/hooks/meetwave",
"status": "pending",
"attempts": 0,
"last_status_code": null
},
"transcript": null
}Wait for the result
Pass webhook_url and MeetWave calls you when the transcription ends (see Webhooks). Or ask for it every few seconds until the status is completed or failed; each request counts toward the per-minute limit, so a few seconds between requests is plenty.
curl https://api.meetwave.io/v1/transcriptions/$TRANSCRIPTION_ID \
-H "Authorization: Bearer $MEETWAVE_API_KEY"| Status | Meaning |
|---|---|
queued | Accepted; the file is being fetched and measured. |
processing | The audio is being transcribed. |
completed | Done: transcript, billing and expires_at are filled in. |
failed | Stopped; error.code says why. A failed transcription costs nothing. |
The result
A completed transcription holds the transcript and what it cost. If speakers or their names were asked for and could not be found, features says failed for that part (partial when speakers are marked per segment, not per word), and you pay the price of what was delivered.
{
"id": "tr_8k2m4q7w1x9c3v5b6n0z2p4r7t",
"object": "transcription",
"status": "completed",
"created_at": "2026-10-21T12:00:00Z",
"completed_at": "2026-10-21T12:00:41Z",
"expires_at": "2026-10-24T12:00:41Z",
"options": {
"speakers": true,
"speakers_expected": null,
"speaker_names": true,
"language": "auto"
},
"audio_duration": 7.84,
"features": {
"speakers": "done",
"speaker_names": "done"
},
"billing": {
"feature": "batch_names",
"audio_seconds": 8,
"cost_usd": "0.000445"
},
"error": null,
"webhook": {
"url": "https://example.com/hooks/meetwave",
"status": "delivered",
"attempts": 1,
"last_status_code": 200
},
"transcript": {
"language": "en",
"language_source": "detected",
"duration": 7.84,
"text": "Hi, I'm Anna from support. Thanks Anna, this is Mark from the sales team.",
"speakers": [
{
"id": "A",
"name": "Anna"
},
{
"id": "B",
"name": "Mark"
}
],
"segments": [
{
"start": 0.52,
"end": 3.9,
"speaker": "Anna",
"speaker_id": "A",
"text": "Hi, I'm Anna from support.",
"words": [
{
"text": "Hi,",
"start": 0.52,
"end": 0.8
},
{
"text": "I'm",
"start": 0.86,
"end": 1.1
},
{
"text": "Anna",
"start": 1.16,
"end": 1.6
},
{
"text": "from",
"start": 1.7,
"end": 2
},
{
"text": "support.",
"start": 2.1,
"end": 3.9
}
]
},
{
"start": 4.1,
"end": 7.84,
"speaker": "Mark",
"speaker_id": "B",
"text": "Thanks Anna, this is Mark from the sales team.",
"words": [
{
"text": "Thanks",
"start": 4.1,
"end": 4.5
},
{
"text": "Anna,",
"start": 4.55,
"end": 5
},
{
"text": "this",
"start": 5.1,
"end": 5.3
},
{
"text": "is",
"start": 5.3,
"end": 5.45
},
{
"text": "Mark",
"start": 5.5,
"end": 5.9
},
{
"text": "from",
"start": 6,
"end": 6.25
},
{
"text": "the",
"start": 6.25,
"end": 6.4
},
{
"text": "sales",
"start": 6.45,
"end": 6.9
},
{
"text": "team.",
"start": 6.95,
"end": 7.84
}
]
}
]
}
}| Field | Type | Meaning |
|---|---|---|
id | string | Starts with tr_. Keep it to fetch or delete the transcription. |
status | string | queued, processing, completed or failed. |
created_at | string | When it was created, ISO 8601 in UTC. |
completed_at | string | null | When it completed; null until then. |
expires_at | string | null | After this time it is gone and answers 404; null while it runs. |
options | object | The options it was created with, defaults filled in. |
audio_duration | number | null | Seconds, two decimals; null until the file is read. |
features | object | speakers and speaker_names: pending while it runs, then done or failed; off when not asked for. speakers can also be partial: one speaker per segment, billed as text. |
billing | object | null | feature, audio_seconds (the seconds billed: the length rounded up) and cost_usd (US dollars, six decimals, as a string); null until completed. |
error | object | null | code and message when failed; null otherwise. |
webhook | object | null | url, status (pending, delivered or failed), attempts, last_status_code; null without webhook_url. |
transcript | object | null | The transcript once completed, see Transcript format; null before. |
Download TXT, SRT or VTT
GET /v1/transcriptions/{id}/{format} returns the transcript as a file, in UTF-8, with the speaker before each line or cue. Before the transcription completes it answers 409 NOT_READY; a failed transcription answers 409 NO_TRANSCRIPT.
curl -o meeting.srt https://api.meetwave.io/v1/transcriptions/$TRANSCRIPTION_ID/srt \
-H "Authorization: Bearer $MEETWAVE_API_KEY"txt—text/plainsrt—application/x-subripvtt—text/vtt
Delete a transcription
Erases the transcription and its files at once and answers 204; from then on it answers 404. A queued transcription costs nothing. A processing one is stopped and its result thrown away, but the audio already processed is billed.
curl -X DELETE https://api.meetwave.io/v1/transcriptions/$TRANSCRIPTION_ID \
-H "Authorization: Bearer $MEETWAVE_API_KEY"What it costs
Billed per second of audio, rounded up to a whole second, with no minimum. The price is that of the most you asked for:
| billing.feature | Per audio hour | What you asked for |
|---|---|---|
batch_text | $0.12 | Text |
batch_speakers | $0.15 | Text and speakers |
batch_names | $0.20 | Text, speakers and their names |
Before processing starts the file's real length is measured and its cost is held from your credits. If they do not cover it, the transcription fails with INSUFFICIENT_CREDITS and nothing is charged. When it completes, you pay for the seconds processed at the price of what was delivered — if names could not be found, the speakers price; if speakers could not be told apart, or only per segment (partial), the text price. A failed transcription is free.
Deleting a processing transcription bills the seconds already processed; deleting a queued one is free.
How long data is kept
The audio is deleted as soon as it is processed; an upload that was never used is deleted within a day. The result is kept for 72 hours after the transcription ends, or until you delete it, and then answers 404. Your audio and transcripts are never used to train anything.
