Response format
Every successful response shares the same envelope: ok, a request_id (also returned as the X-Request-Id header), a data object whose kind tells you what it holds, and a usage block.
Response envelope
Every successful response shares the same envelope: ok, a request_id (also sent as the X-Request-Id header), a data object whose kind names what it holds, and a usage block.
There are four data.kind shapes: transcript (single video), video_list (channel / playlist / search), transcript_batch (batch), and transcript_job (the 202 envelope and job polling, see the transcription guide).
Single transcript
The /video endpoint returns kind: "transcript" with either a joined text string or a timestamped segments array, never both. The timestamps request field chooses: true (the default) returns segments, false returns text. The unwanted key is absent, not null, so check for presence, not truthiness. Each segment's start and duration are in seconds, everything needed to render timestamps or export SRT/VTT. source says where the words came from: "captions" for an existing caption track, "audio" for AI transcription (billed by length, see usage.credits_spent).
Every transcript also carries the same metadata block, whichever platform or path served it: video_id (the item's id on its platform: the 11-character YouTube id, TikTok's numeric id, the Instagram shortcode, or the URL for a direct file), url (the canonical link, accepted as-is by every transcript endpoint), platform, title (the post caption on TikTok and Instagram), channel (the creator, as the platform names them: a channel name, a TikTok @handle, an Instagram username), duration in seconds, language (the caption track's code, or the language detected during AI transcription), thumbnail_url (TikTok and Instagram serve signed, expiring poster URLs, so copy the image rather than hotlinking it), and source. Every key is always present; null means unknown, never omitted. A cache hit, an inline audio result, a job poll and a webhook delivery all carry exactly these keys.
{ "ok": true, "request_id": "req_…", "data": { "kind": "transcript", "video_id": "dQw4w9WgXcQ", "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "platform": "youtube", "title": "Example video", "channel": "Example Channel", "duration": 212, "language": "en", "thumbnail_url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/mqdefault.jpg", "source": "captions", "segments": [ { "start": 0, "duration": 3.5, "text": "We're no strangers to love" } ] }, "usage": { "credits_spent": 1, "balance": 99, "bytes": 14233 } }
Video list (channel / playlist / search)
The /channel, /playlist, and /search endpoints return kind: "video_list", a videos array of metadata plus a next_cursor for pagination. Fetch the transcripts separately via the batch endpoint. A channel, profile or playlist that does not exist (or is not public) answers 404 with error.code: "not_found"; an existing one with nothing to list answers 200 with an empty videos array, free of charge. An upstream fault while listing answers 503, never an empty page.
{ "ok": true, "request_id": "req_…", "data": { "kind": "video_list", "source": "search", "platform": "tiktok", "videos": [ { "videoId": "7398765432101234567", "url": "https://www.tiktok.com/@examplecreator/video/7398765432101234567", "title": "the post caption", "duration": 58, "channel": "@examplecreator", "publishedAt": "2026-08-30T14:02:00Z", "stats": { "plays": 1240000 } } ], "next_cursor": "eyJvIjoxMH0" }, "usage": { "credits_spent": 1, "balance": 98, "bytes": 0 } }
Batch
The batch endpoint returns kind: "transcript_batch" with one entry per requested video in data.results. Batch entries carry both text and segments (the timestamps flag is not accepted on batch), plus outcome: ok, processing, or error. A failed entry carries the same error block as a request-level failure; entries without text do not ship the text fields at all.
Entries with no caption track are transcribed from audio by default: those come back with outcome: "processing" plus a job_id and poll_url, cost nothing on the batch call, and are billed on delivery at the audio rate. Re-send the same batch once they finish and the text is returned normally — polling is optional. Send mode: "captions" to have captionless entries fail as error with code no_captions (and a retry_with naming the audio mode) instead.
{ "ok": true, "request_id": "req_…", "data": { "kind": "transcript_batch", "results": [ { "video_id": "dQw4w9WgXcQ", "outcome": "ok", "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "title": "Example video", "channel": "Example Channel", "duration": 212, "language": "en", "thumbnail_url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/mqdefault.jpg", "source": "captions", "text": "Full transcript text …", "segments": [ { "start": 0, "duration": 3.5, "text": "Full transcript …" } ], "bytes": 14233 }, { "video_id": "9bZkp7q19f0", "outcome": "processing", "job_id": "asr_…", "poll_url": "/api/v2/transcripts/jobs/asr_…" }, { "video_id": "jNQXAC9IVRw", "outcome": "error", "error": { "code": "no_captions", "number": 4103, "message": "No caption track (manual or auto-generated) is available.", "docs": "https://transcriptfetch.com/docs/errors/no_captions", "retry_with": { "mode": "audio" } } } ] }, "usage": { "credits_spent": 1, "balance": 97 } }
Field reference
data.kind:transcript,video_list,transcript_batch, ortranscript_job.text/segments: exactly one on single-transcript responses (chosen bytimestamps); both on batch entries and completed job results. Segments are{ start, duration, text }cues in seconds (speakeradded when diarized).next_cursor: pass back ascursorfor the next page of a list,nullwhen exhausted.outcome(batch results):ok,processing, orerror; failed entries carryerror(the standard block), andprocessingentries (captionless videos being transcribed from audio) carryjob_idandpoll_url.video_id,url,platform,title,channel,duration,language,thumbnail_url(transcripts): the metadata block above, on every transcript-bearing response including job polls, webhooks and batch entries. Always present;nullwhen unknown.source(transcripts):"captions"or"audio".usage.credits_spent: credits charged for this request. Failures are never charged and carry nousageblock.usage.balance: remaining credit balance (nullfor unlimited accounts).usage.bytes: upstream bytes this request consumed (absent on batch, which reports per-entrybytes).