Speech-to-text
Whisper large-v3 transcription with an async jobs API, URL and YouTube endpoints, cleaned and bilingual subtitle modes, and an OpenAI-compatible drop-in.
Overview
All transcription runs Whisper large-v3 locally, one job at a time. Subtitle cleanup (perfect-srt) and translation modes use Gemini Flash. Auth is a key or an approved session (require_api_key); the key needs the media capability. Job results live in memory and expire about one hour after completion.
The jobs API is the canonical way to transcribe over HTTP. The synchronous endpoints still exist but die on the public edge's 100-second timeout for long files.
Jobs API
Submit an uploaded file
POST /jobs (multipart/form-data)
curl -X POST https://maximind.dev/jobs \
-H "x-api-key: $MAXIMIND_API_KEY" \
-F "file=@lesson.mp4" -F "type=perfect-srt" \
-F "language=vi" -F "target_language=English"
{"job_id": "a1b2c3d4e5f6", "type": "perfect-srt"}
| Name | Type | Required | Description |
|---|---|---|---|
file |
file | Yes | Any audio or video file. Public uploads are capped at about 100 MB by the edge. |
type |
string | No | Output mode, default transcribe. See the table below. Unknown value: HTTP 400 unknown type: …. |
language |
string | No | Source language code (vi, en, zh). Default: auto-detect. |
target_language |
string | No | Language name for bilingual-srt (second line) and translated-srt (only output). Default English. |
with_segments |
boolean | No | Include per-segment detail in meta. Default false. |
Output modes
type |
Output |
|---|---|
transcribe |
Plain text transcript in the source language. |
translate |
Whisper English translation as text. |
srt |
Standard Whisper SRT. |
perfect-srt |
AI-cleaned SRT, resegmented to at most 10 words per line, sentence-boundary aware. |
bilingual-srt |
Each cue shows the original line with a target_language translation underneath. |
translated-srt |
Single-track SRT fully in target_language. |
word-srt |
Word-level timestamped SRT. |
Submit a URL
POST /jobs/url (application/x-www-form-urlencoded or multipart)
Same parameters as /jobs with url instead of file. YouTube, TikTok or a direct audio/video URL. Free native captions are used when available; otherwise the media is downloaded and run through Whisper.
curl -X POST https://maximind.dev/jobs/url \
-H "x-api-key: $MAXIMIND_API_KEY" \
-d "url=https://www.youtube.com/watch?v=VIDEO_ID" \
-d "type=bilingual-srt" -d "language=en" -d "target_language=Vietnamese"
{"job_id": "…", "type": "bilingual-srt"}
Poll
GET /jobs/{job_id}
curl https://maximind.dev/jobs/a1b2c3d4e5f6 -H "x-api-key: $MAXIMIND_API_KEY"
In progress:
{"id": "a1b2c3d4e5f6", "type": "perfect-srt", "status": "running", "stage": "transcribing audio",
"progress": 0.42, "elapsed_seconds": 12.3, "filename": "lesson.mp4", "error": null}
Done (meta, is_srt and result appear only when status is done):
{"id": "a1b2c3d4e5f6", "type": "perfect-srt", "status": "done", "stage": "done",
"progress": 1.0, "elapsed_seconds": 47.8, "filename": "lesson.mp4", "error": null,
"meta": {"language": "vi", "source": "whisper"}, "is_srt": true,
"result": "1\n00:00:00,000 --> 00:00:01,200\n..."}
| Field | Meaning |
|---|---|
status |
queued, running, done, failed (or cancelling). |
stage |
Human-readable progress label. |
progress |
0 to 1. |
error |
Failure message when failed. |
result |
Text or SRT body. |
is_srt |
true when result is SRT. |
meta |
language, source (youtube_captions, whisper, …) and segments when with_segments=true. |
Unknown or expired id: HTTP 404 no such job (or it expired after 1h).
Cancel or forget
DELETE /jobs/{job_id}
Cancels a queued or running job (in-flight Whisper work may still finish in the background) or drops a finished one.
curl -X DELETE https://maximind.dev/jobs/a1b2c3d4e5f6 -H "x-api-key: $MAXIMIND_API_KEY"
{"id": "…", "status": "…", "stage": "…", "cancelled": true}
Synchronous endpoints
These block until done and are only safe for short media through the public edge.
POST /transcribe-url (JSON) — direct audio or video URL to a JSON transcript. Fields: url (required), mode (transcribe | translate | both, default transcribe), language (auto), with_segments (false). Returns {text, language, segments, duration_seconds, processing_seconds}; segments is null unless requested. Download failure: HTTP 400 Failed to download: ….
curl -X POST https://maximind.dev/transcribe-url \
-H "x-api-key: $MAXIMIND_API_KEY" -H "Content-Type: application/json" \
-d '{"url":"https://example.com/audio.mp3","mode":"transcribe","language":"vi"}'
POST /youtube-to-srt (JSON) — YouTube URL to a raw .srt file (application/x-subrip). Fields: url (required), language (falls back to en, also names the file {video_id}_{lang}.srt). The X-SRT-Source header is captions or whisper.
curl -X POST https://maximind.dev/youtube-to-srt \
-H "x-api-key: $MAXIMIND_API_KEY" -H "Content-Type: application/json" \
-d '{"url":"https://www.youtube.com/watch?v=VIDEO_ID","language":"en"}' -o out.srt -D -
POST /audio/transcriptions (multipart) — drop-in for OpenAI's /v1/audio/transcriptions. Fields: file (required), model (ignored; always Whisper large-v3), language, response_format (json → {"text": "…"}, text, srt; verbose_json and vtt fall through to json).
curl -X POST https://maximind.dev/audio/transcriptions \
-H "x-api-key: $MAXIMIND_API_KEY" \
-F "file=@clip.m4a" -F "response_format=srt" -F "language=en"
GET /v1/transcript — platform captions first, Whisper fallback; see Files for the media API.
Whisper hallucination guard
Silent or empty audio (often a failed in-browser ffmpeg.wasm extraction) makes Whisper invent text. The server pre-checks for silent audio and returns HTTP 422, decodes without conditioning on previous text, and scrubs known hallucinated phrases from the output.
Errors
| Status | Meaning |
|---|---|
| 400 | Unknown type; download failed; URL unreadable. |
| 401 | Missing or invalid key; no session. |
| 403 | Pending account. |
| 404 | Job unknown or expired after 1 h. |
| 422 | Silent or empty audio detected. |
| 524 | Public edge timeout at about 100 s on a synchronous call. Use jobs. |
Limits and costs
- Public edge: 100 s per request, 100 MB body. Use the jobs API for anything longer.
- Job results expire one hour after completion.
- The in-browser extractor in the web app at
/app/has a memory ceiling of about 2 GB; extract audio locally withffmpegfor very large videos. - Defined rates:
file_transcribe_per_min2 credits per minute,translate_per_min30 credits per minute. The/jobs*,/transcribe-url,/youtube-to-srtand/audio/transcriptionshandlers do not currently record usage, so these calls are not metered in practice. See Limits.