Developer documentation
Whisper Web API
Transcribe audio and video, translate speech into English, and download timestamped text and subtitles.
https://whisperweb.dev/v1Quickstart
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
Create
POST /transcriptions returns 202 for a new task. Save its id; media validation happens next.
- 2
Wait
GET /transcriptions/{id} at least five seconds apart until completed or failed. A webhook can notify you instead.
- 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.srtSwipe 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
| Scope | Required for |
|---|---|
| transcriptions:write | All /uploads operations, including GET; POST /transcriptions |
| transcriptions:read | List tasks, read status and download results |
| transcriptions:delete | DELETE /transcriptions/{id}; not selected by default |
| usage:read | 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
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
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
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
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
| POST /uploads field | Type | Required | Meaning |
|---|---|---|---|
| size_bytes | integer | Yes | Exact byte count, from 1 to 5,000,000,000 |
| content_type | string | Yes | 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
| JSON field | Type | Required | Values / default |
|---|---|---|---|
| source | object | Yes | Exactly one of { upload_id: string } or { audio_url: string } |
| model | string | No | whisperweb-standard |
| task | string | No | transcribe (default) or translate_to_english |
| language | string | No | auto (default) or a supported code from the OpenAPI enum |
| max_cost_credits | integer | No | 1–36,000; maximum audio-second credits you authorize for this task |
| webhook_id | string | No | 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
| Task status | What it means | What to do |
|---|---|---|
| validating | Downloading or checking the media and its measured cost | Wait; no completed transcript is available |
| queued | Media passed validation and credits are reserved | Continue polling or wait for the webhook |
| processing | Transcription is running | Continue polling or wait for the webhook |
| completed | The result is saved and credits are settled | Fetch /result before expires_at |
| failed | The task could not finish; error explains why | 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
| One-time purchase | Transcription minutes | Audio-second credits |
|---|---|---|
| $10 | 1,000 | 60,000 |
| $25 | 2,500 | 150,000 |
| $100 | 10,000 | 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
| HTTP / task state | Examples | Recovery |
|---|---|---|
| 400 | INVALID_REQUEST, INVALID_IDEMPOTENCY_KEY | Check fields, types and the idempotency key |
| 401 / 403 | INVALID_API_KEY, INSUFFICIENT_SCOPE, ACCOUNT_SUSPENDED | Check the key, permissions and account status |
| 402 | INSUFFICIENT_CREDITS | Add API credits before submitting work |
| 404 | NOT_FOUND | Check the ID and Developer account; deleted records are not returned |
| 409 | IDEMPOTENCY_CONFLICT, UPLOAD_NOT_READY, RESULT_NOT_READY, TASK_IN_PROGRESS | Fix the conflict or wait for the required state |
| 410 | UPLOAD_EXPIRED, RESULT_EXPIRED | Provide a new input if the expired output is still needed |
| 413 / 415 / 422 | REQUEST_TOO_LARGE, INVALID_CONTENT_TYPE, UNSUPPORTED_LANGUAGE | Reduce the JSON body, use application/json, or use a supported language code |
| 429 | RATE_LIMITED, UPLOAD_LIMIT, CONCURRENCY_LIMIT | Wait for Retry-After; do not bypass the limit with another key |
| 503 | SERVICE_ERROR | Retry later; reuse the original key and body for an uncertain create |
| 200 + status: failed | COST_LIMIT_EXCEEDED, INSUFFICIENT_CREDITS, INVALID_MEDIA, NO_AUDIO_TRACK, NO_SPEECH, URL_FETCH_FAILED | 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
Register
Add a public HTTPS endpoint in Console → Webhooks and save the signing secret shown once.
- 2
Attach
Include that endpoint's webhook_id in POST /transcriptions.
- 3
Verify and accept
Check the signature on the raw body, deduplicate the event, then acknowledge it with 2xx.
- 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
| Header | Purpose |
|---|---|
| webhook-id | Stable event ID; matches the id in the event body |
| webhook-timestamp | Unix timestamp used in signature verification |
| webhook-signature | 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
| Tool | What it does | Credits |
|---|---|---|
| get_account | Balance, pricing and limits | Free |
| estimate_cost | Quote for an audio length | Free |
| transcribe_url | Start a task from a media URL or a YouTube / Facebook / X video link | Per audio second |
| create_upload | Signed PUT URL for a local file | Free |
| transcribe_upload | Start a task from a finished upload | Per audio second |
| start_upload | In-chat file uploader (MCP Apps) | Per audio second when started |
| get_transcription | Task status, duration and cost | Free |
| get_transcript | Text, timestamped segments, SRT, VTT or JSON, paged | Free |
| list_transcriptions | Recent tasks | Free |
| delete_transcription | Delete a finished result (separate permission) | 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/mcpSwipe 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.
- POST
/uploads
Create a private signed upload session.
- POST
/uploads/{id}/complete
Verify the stored bytes and mark the upload ready.
- GET
/uploads/{id}
Read upload status and expiry.
- POST
/transcriptions
Create a task, or recover an identical idempotent request.
- GET
/transcriptions
List API tasks with cursor pagination.
- GET
/transcriptions/{id}
Read task status, usage and errors.
- GET
/transcriptions/{id}/result
Retrieve JSON, TXT, SRT or VTT.
- DELETE
/transcriptions/{id}
Delete a completed or failed task's result.
- GET
/usage
Read balances, limits and paginated credit activity.