Skip to main content
Whisper Web

Developer documentation

Whisper Web API

Transcribe audio and video, translate speech into English, and download timestamped text and subtitles.

https://whisperweb.dev/v1

Quickstart

Turn a direct media URL into a transcript and subtitle file.

Use an active Developer account, an API key with read and write permissions, and API credits. Website Unlimited and Developer API use separate billing and records.

Set WHISPERWEB_API_KEY on your server. Replace the example URL with a direct audio or video file you are authorized to process. Python uses requests; Node.js uses built-in fetch. No Whisper Web SDK is required.

Each example prints a fresh Idempotency-Key before its first POST. Save it. Before rerunning after an uncertain POST response, set WHISPERWEB_REQUEST_ID to that printed value and keep the same request body.

The examples stop waiting after about ten minutes; they do not cancel the service job. Keep the task ID and poll it later. If a create request has an uncertain response, retry with its original Idempotency-Key and body.

  1. 1

    Create

    POST /transcriptions returns 202 for a new task. Save its id; media validation happens next.

  2. 2

    Wait

    GET /transcriptions/{id} at least five seconds apart until completed or failed. A webhook can notify you instead.

  3. 3

    Download

    After completion, GET /transcriptions/{id}/result for JSON, text or subtitles.

Create, poll and download a transcript

# Requires a POSIX shell, curl 7.76+, jq and uuidgen.
set -eu
: "${WHISPERWEB_API_KEY:?Set WHISPERWEB_API_KEY first}"
BASE="https://whisperweb.dev/v1"
# Save the printed key. Set WHISPERWEB_REQUEST_ID to it before retrying an uncertain POST.
REQUEST_ID="${WHISPERWEB_REQUEST_ID:-$(uuidgen)}"
printf 'Idempotency-Key: %s\n' "$REQUEST_ID"
api() {
  path="$1"; shift
  curl --silent --show-error --fail-with-body --max-time 30 \
    -H "Authorization: Bearer $WHISPERWEB_API_KEY" "$BASE$path" "$@"
}

# 1. Create a task. Replace the direct media URL below.
job=$(api /transcriptions -X POST \
  -H "Idempotency-Key: $REQUEST_ID" \
  -H "Content-Type: application/json" \
  -d '{"source":{"audio_url":"https://example.com/recording.mp3"},"task":"transcribe","language":"auto"}')
id=$(printf '%s' "$job" | jq -er '.id')
printf 'Task: %s\n' "$id"

# 2. Poll for up to ten minutes. This only limits this script's waiting.
deadline=$(($(date +%s) + 600))
while :; do
  status=$(printf '%s' "$job" | jq -er '.status')
  case "$status" in
    completed) break ;;
    failed) printf '%s' "$job" | jq '.error' >&2; exit 1 ;;
    validating|queued|processing) ;;
    *) printf 'Unexpected task status: %s\n' "$status" >&2; exit 1 ;;
  esac
  if [ "$(date +%s)" -ge "$deadline" ]; then
    printf 'Stopped waiting. Poll GET /transcriptions/%s later; do not create another task.\n' "$id" >&2
    exit 1
  fi
  sleep 5
  job=$(api "/transcriptions/$id")
done

# 3. Read the JSON result, then save subtitles.
api "/transcriptions/$id/result?format=json" | jq .
api "/transcriptions/$id/result?format=srt" -o transcript.srt

Swipe the code to view long lines.

Authentication

Send a Bearer API key with each API request.

Create a named key in Developer Console → API keys. The full value is shown once. Keep it in server-side secret storage; never put it in frontend code, URLs or public repositories.

Keys belong to one Developer account. Expired or revoked keys return 401; a suspended account returns 403. Create a replacement before revoking a key used by your application.

Scope
transcriptions:write
Required for
All /uploads operations, including GET; POST /transcriptions
Scope
transcriptions:read
Required for
List tasks, read status and download results
Scope
transcriptions:delete
Required for
DELETE /transcriptions/{id}; not selected by default
Scope
usage:read
Required for
GET /usage

Check your API balance

curl --fail-with-body --silent --show-error "https://whisperweb.dev/v1/usage" \
  -H "Authorization: Bearer $WHISPERWEB_API_KEY"

Swipe the code to view long lines.

Files and public URLs

Choose a local file upload or a publicly reachable media URL.

For a local file, follow the four steps below. Completing an upload checks its stored size; decoding and duration checks happen after you create the task. A successful PUT alone does not start transcription.

For a remote file, skip the upload steps and send source.audio_url to POST /transcriptions. Use a direct HTTP or HTTPS media URL. Web pages, private network addresses and unsafe redirects are rejected. Supply either upload_id or audio_url, never both.

One upload belongs to one logical task. To transcribe it again as a new task, create a new upload. The signed URL is valid for one hour; the upload session expires after 24 hours. Treat signed URLs as temporary credentials.

  1. 1

    Create an upload session

    POST /uploads with the file's exact size_bytes and content_type. Save upload.id, upload_url and headers from the 201 response.

  2. 2

    Send the bytes

    PUT the raw file bytes to upload_url using the returned headers. Send exactly the declared size; do not send a multipart/form-data body or your API key to storage.

  3. 3

    Complete the upload

    POST /uploads/{id}/complete with your Bearer key. A 200 response with upload.status = ready means the bytes are ready to submit.

  4. 4

    Create the task

    POST /transcriptions with source.upload_id and an Idempotency-Key. Continue with the same polling and result workflow as the quickstart.

POST /uploads field
size_bytes
Type
integer
Required
Yes
Meaning
Exact byte count, from 1 to 5,000,000,000
POST /uploads field
content_type
Type
string
Required
Yes
Meaning
Media MIME type, for example audio/mpeg or video/mp4; the server also validates the media

201 Created · example upload response

{
  "upload": {
    "id": "upl_example",
    "status": "uploading",
    "size_bytes": 2480128,
    "content_type": "audio/mpeg",
    "expires_at": "2026-11-05T12:00:00Z"
  },
  "upload_url": "https://storage.example.com/temporary-signed-upload",
  "headers": {
    "content-type": "audio/mpeg"
  },
  "method": "PUT",
  "upload_url_expires_in_seconds": 3600
}

Swipe the code to view long lines.

Create a transcription

Transcribe speech in its original language or translate audio into English.

Both tasks cost $0.01 per minute, billed by the second. Use language: auto, or a supported source-language code such as en, es, fr, de, ja or zh. English translation always returns English, regardless of the source language.

Send an Idempotency-Key of 8–128 letters, numbers, dots, colons, underscores or hyphens. A new request returns 202 with duplicate: false; an identical retry returns the existing task with 200 and duplicate: true.

The create response contains status and metadata, not transcript text. HTTP 202 does not guarantee media validation or sufficient credits for the measured duration. Check the task until it reaches a terminal state.

JSON field
source
Type
object
Required
Yes
Values / default
Exactly one of { upload_id: string } or { audio_url: string }
JSON field
model
Type
string
Required
No
Values / default
whisperweb-standard
JSON field
task
Type
string
Required
No
Values / default
transcribe (default) or translate_to_english
JSON field
language
Type
string
Required
No
Values / default
auto (default) or a supported code from the OpenAPI enum
JSON field
max_cost_credits
Type
integer
Required
No
Values / default
1–36,000; maximum audio-second credits you authorize for this task
JSON field
webhook_id
Type
string
Required
No
Values / default
An endpoint registered in the same Developer account

202 Accepted · example task response

{
  "id": "tr_example",
  "status": "validating",
  "model": "whisperweb-standard",
  "task": "transcribe",
  "language": "auto",
  "source_language": null,
  "output_language": null,
  "duration_ms": null,
  "usage": {
    "credits": null,
    "status": "unreserved",
    "price_version": "2026-11-03"
  },
  "error": null,
  "created_at": "2026-11-04T12:00:00Z",
  "completed_at": null,
  "expires_at": "2026-12-04T12:00:00Z",
  "retry_after_ms": 5000,
  "duplicate": false
}

Swipe the code to view long lines.

Task status and results

Wait for completion, then retrieve the format your application needs.

GET /transcriptions/{id} reports status, error and usage. While waiting, use retry_after_ms and poll at least five seconds apart. usage.credits can represent a quoted or reserved amount; only usage.status = settled represents the final charge.

GET /transcriptions/{id}/result?format=json returns the shape below. Use format=txt, srt or vtt for a downloadable file. Segment times are milliseconds; these are speech segments, not word-level timings or speaker labels.

GET /transcriptions lists records with items and next_cursor. Pass next_cursor as cursor for the next page; limit is 1–200 (default 20), and status is an optional filter. DELETE /transcriptions/{id} removes a completed or failed result; active tasks return 409. Billing history remains.

Task status
validating
What it means
Downloading or checking the media and its measured cost
What to do
Wait; no completed transcript is available
Task status
queued
What it means
Media passed validation and credits are reserved
What to do
Continue polling or wait for the webhook
Task status
processing
What it means
Transcription is running
What to do
Continue polling or wait for the webhook
Task status
completed
What it means
The result is saved and credits are settled
What to do
Fetch /result before expires_at
Task status
failed
What it means
The task could not finish; error explains why
What to do
Read error.code before deciding what to submit next

200 OK · example JSON result

{
  "text": "Welcome to the interview.",
  "source_language": "en",
  "output_language": "en",
  "duration_ms": 4200,
  "segments": [
    {
      "start_ms": 0,
      "end_ms": 4200,
      "text": "Welcome to the interview."
    }
  ]
}

Swipe the code to view long lines.

Credits and billing

$0.01 per minute for transcription and translation into English.

One credit is one audio second. The measured duration is rounded up to the next whole second: 62.3 seconds uses 63 credits. Set max_cost_credits to cap a task's cost; a higher measured cost produces a failed task before transcription starts.

Credits are held before processing, charged after the result is saved, and released after confirmed failure. Status reads, result downloads and webhook delivery have no transcription charge. Retry the same task with its original Idempotency-Key to avoid creating another billable task.

Credit packs are one-time purchases, with no automatic recharge or overage charges. Credits appear after payment confirmation. Website Unlimited does not provide API credits; API purchases do not change your website subscription.

GET /usage returns account-wide balances, pricing, limits, items and next_cursor. Use cursor for older activity; before filters by timestamp and api_key_id filters task activity by key. Reserved and released entries are movements, not extra charges to add to settled usage.

One-time purchase
$10
Transcription minutes
1,000
Audio-second credits
60,000
One-time purchase
$25
Transcription minutes
2,500
Audio-second credits
150,000
One-time purchase
$100
Transcription minutes
10,000
Audio-second credits
600,000

Errors, limits and retries

Distinguish a rejected HTTP request from a task that fails after acceptance.

An HTTP error returns error.code, message, request_id and retryable. The X-Request-Id response header also identifies the request. For an accepted task, GET can return HTTP 200 with status: failed and an error; this is a task failure, not a failed GET.

After an uncertain create response, send the same Idempotency-Key and identical body. Changing the body under the same key returns 409. Do not create a new task merely because polling or your client times out. Respect Retry-After on 429; fix permission, balance or validation errors before retrying.

Media limits are 5 GB decimal (5,000,000,000 bytes) and 10 hours. Each account can have 20 nonterminal tasks, 2 concurrent executions per processing phase, and 10 retained uploads totaling at most 10 GB.

Request limits are 120 GET requests and 30 write requests per minute, enforced for both the account and each key. GET /usage returns the configured limits.

HTTP / task state
400
Examples
INVALID_REQUEST, INVALID_IDEMPOTENCY_KEY
Recovery
Check fields, types and the idempotency key
HTTP / task state
401 / 403
Examples
INVALID_API_KEY, INSUFFICIENT_SCOPE, ACCOUNT_SUSPENDED
Recovery
Check the key, permissions and account status
HTTP / task state
402
Examples
INSUFFICIENT_CREDITS
Recovery
Add API credits before submitting work
HTTP / task state
404
Examples
NOT_FOUND
Recovery
Check the ID and Developer account; deleted records are not returned
HTTP / task state
409
Examples
IDEMPOTENCY_CONFLICT, UPLOAD_NOT_READY, RESULT_NOT_READY, TASK_IN_PROGRESS
Recovery
Fix the conflict or wait for the required state
HTTP / task state
410
Examples
UPLOAD_EXPIRED, RESULT_EXPIRED
Recovery
Provide a new input if the expired output is still needed
HTTP / task state
413 / 415 / 422
Examples
REQUEST_TOO_LARGE, INVALID_CONTENT_TYPE, UNSUPPORTED_LANGUAGE
Recovery
Reduce the JSON body, use application/json, or use a supported language code
HTTP / task state
429
Examples
RATE_LIMITED, UPLOAD_LIMIT, CONCURRENCY_LIMIT
Recovery
Wait for Retry-After; do not bypass the limit with another key
HTTP / task state
503
Examples
SERVICE_ERROR
Recovery
Retry later; reuse the original key and body for an uncertain create
HTTP / task state
200 + status: failed
Examples
COST_LIMIT_EXCEEDED, INSUFFICIENT_CREDITS, INVALID_MEDIA, NO_AUDIO_TRACK, NO_SPEECH, URL_FETCH_FAILED
Recovery
Inspect error.code; correct the input or cost limit before an intentional new task

402 · example HTTP error response

{
  "error": {
    "code": "INSUFFICIENT_CREDITS",
    "message": "Add API credits before starting this request.",
    "request_id": "req_example",
    "requestId": "req_example",
    "retryable": false
  }
}

Swipe the code to view long lines.

Webhooks

Receive a signed event when a task completes or fails.

Webhooks are optional; polling remains available. Events carry the task ID and status, not transcript content. Retrieve the result with your API key after a completion event.

Verify the untouched request body with the signing secret and Standard Webhooks headers. The Node.js standardwebhooks package provides Webhook.verify. Verification must happen before parsing or re-serializing the body; reject invalid signatures and stale timestamps.

Return 2xx after durably accepting the event. Deduplicate by event ID and handle out-of-order delivery. Automatic delivery retries are bounded to 10 attempts within a 24-hour window. A manual retry uses the same event ID and starts a new retry window. Delivery failure does not undo the result or charge again.

  1. 1

    Register

    Add a public HTTPS endpoint in Console → Webhooks and save the signing secret shown once.

  2. 2

    Attach

    Include that endpoint's webhook_id in POST /transcriptions.

  3. 3

    Verify and accept

    Check the signature on the raw body, deduplicate the event, then acknowledge it with 2xx.

  4. 4

    Fetch or investigate

    Download the completed result, or inspect the task error. Use Console delivery history to diagnose and retry failed delivery.

Header
webhook-id
Purpose
Stable event ID; matches the id in the event body
Header
webhook-timestamp
Purpose
Unix timestamp used in signature verification
Header
webhook-signature
Purpose
Standard Webhooks signature over the original body

Example event · transcription.completed or transcription.failed

{
  "id": "evt_example",
  "type": "transcription.completed",
  "created_at": "2026-11-04T12:00:00Z",
  "data": {
    "id": "tr_example",
    "status": "completed"
  }
}

Swipe the code to view long lines.

AI agents (MCP)

Use Whisper Web from Claude, ChatGPT, Claude Code, Codex, Cursor and other MCP clients.

The remote MCP server at https://whisperweb.dev/mcp exposes the Developer API to AI agents over Streamable HTTP. Add the URL to your client and sign in with your Whisper Web account; OAuth discovery, PKCE and client registration are automatic, so there is no API key to copy.

MCP uses the same Developer account, credits and records as the REST API: $0.01 per audio minute, billed per second. MCP is available to Developer accounts that have bought API credits; uploads and transcription also need a positive balance. Status checks and reading results do not use credits. Agents are instructed to confirm before spending credits.

Agents can transcribe direct media URLs, single YouTube, Facebook and X (Twitter) video links, files uploaded through a signed URL (create_upload, then PUT with curl, then transcribe_upload), and files picked in the in-chat uploader on clients that support MCP Apps. Only transcribe media you own or are authorized to use.

Deleting transcriptions is a separate permission that is not granted by default. Disconnect any agent in Developer Console → AI agents (MCP); its next call is refused.

Tool
get_account
What it does
Balance, pricing and limits
Credits
Free
Tool
estimate_cost
What it does
Quote for an audio length
Credits
Free
Tool
transcribe_url
What it does
Start a task from a media URL or a YouTube / Facebook / X video link
Credits
Per audio second
Tool
create_upload
What it does
Signed PUT URL for a local file
Credits
Free
Tool
transcribe_upload
What it does
Start a task from a finished upload
Credits
Per audio second
Tool
start_upload
What it does
In-chat file uploader (MCP Apps)
Credits
Per audio second when started
Tool
get_transcription
What it does
Task status, duration and cost
Credits
Free
Tool
get_transcript
What it does
Text, timestamped segments, SRT, VTT or JSON, paged
Credits
Free
Tool
list_transcriptions
What it does
Recent tasks
Credits
Free
Tool
delete_transcription
What it does
Delete a finished result (separate permission)
Credits
Free

Connect a client

# Claude Code
claude mcp add --transport http whisperweb https://whisperweb.dev/mcp

# Codex
codex mcp add whisperweb --url https://whisperweb.dev/mcp
codex mcp login whisperweb

# Cursor and other clients (mcp.json)
{ "mcpServers": { "whisperweb": { "url": "https://whisperweb.dev/mcp" } } }

# Claude: Settings → Connectors → Add custom connector → https://whisperweb.dev/mcp
# ChatGPT: Settings → Apps → Advanced settings → Developer mode → Create app → https://whisperweb.dev/mcp

Swipe the code to view long lines.

Data and retention

API data is cloud-processed and separate from your website recordings.

API keys can access only their own Developer account. Your website recordings and Unlimited benefits remain separate. Keep API keys, webhook secrets and signed storage URLs private.

An unused upload session expires after 24 hours. Its signed PUT URL expires sooner, as shown by upload_url_expires_in_seconds. Temporary source media is cleaned up after a confirmed terminal state; a new transcription requires a new input.

Task results expire 30 days after task creation, not completion. Download before expires_at. A result that is not ready returns 409; an expired result returns 410. Delete a completed or failed task early with a key that has deletion permission.

Request logs exclude media, transcript text, full source URLs and credentials. Deleting a transcript does not erase payment or usage history. Console logs, purchases and webhook deliveries show up to 100 recent records; their filters apply to the loaded records.

API reference

Paths are relative to https://whisperweb.dev/v1. OpenAPI includes the complete request and response schemas.