Skip to content
GitHubLog in
Response format

Response format

Every endpoint answers with the same JSON envelope. Check ok, branch on data.kind, and keep request_id for support.

  • 200 result
  • 202 job
  • 4xx / 5xx error
Envelope

Four top-level fields

200 OKapplication/json
{
  "ok": true,
  "request_id": "req_8f2c…",
  "data": {
    "kind": "transcript",
    "video_id": "dQw4w9WgXcQ",
    "platform": "youtube",
    "language": "en",
    "source": "captions",
    "segments": [ … ]
  },
  "usage": { "credits_spent": 1, "balance": 99, "bytes": 14233 }
}
  1. okboolean
    true on a 200 and a 202, false on every error.
  2. request_idstring
    Unique per call, and also sent as the X-Request-Id header. Include it when you contact support.
  3. dataobject
    The result. data.kind names its shape; the kinds are listed below.
  4. usageobject
    Credits charged for this call, your remaining balance and the bytes it used. An error carries none because failures are free, and neither does a 202: a job is charged on delivery.
Transcript shape

Segments or one string

Set timestamps in the request to choose how a single transcript comes back. The other key is absent, not null.

data
{
  "source": "captions",
  "segments": [
    { "start": 0, "duration": 3.5,
      "text": "Welcome back to the channel." },
    { "start": 3.5, "duration": 4.1,
      "text": "Today we're building a transcript search tool." },
    { "start": 7.6, "duration": 4.4,
      "text": "First, we fetch the captions for every video." },
    …
  ]
}
Rendered
  1. 0:00.0Welcome back to the channel.
  2. 0:03.5Today we're building a transcript search tool.
  3. 0:07.6First, we fetch the captions for every video.
  4. 0:12.0Then we index the text by timestamp.
The default. Each cue has a start and a duration in seconds.
Source

Where the text came from

data.source says which path produced the transcript. The response shape is the same either way.

"captions"
Caption trackRead from the video's existing captions, in the same request. 1 credit.
"audio"
AI Fallback TranscriptionOn by default: when a video has no captions, or you send mode: "audio", the audio is transcribed. Charged on delivery: 1 credit per started 5 minutes of audio. Send mode: "captions" to never pay for it. How AI Fallback Transcription works →
Async jobs

When you get a 202

AI Fallback Transcription that can't finish inside the inline wait of about 45 seconds answers 202 with a job instead of a transcript. Poll poll_url, or pass a callback_url and the result is POSTed to you. The finished job carries the same transcript data as a 200 and is charged only on delivery.

Polling a job →
202 Accepted
{
  "ok": true,
  "request_id": "req_…",
  "status": "processing",
  "job_id": "asr_m3k1x9qz4vb2p7",
  "poll_url": "/api/v2/transcripts/jobs/asr_m3k1x9qz4vb2p7",
  "data": {
    "kind": "transcript_job",
    "video_id": "dQw4w9WgXcQ",
    "platform": "youtube"
  }
}
Errors

Same envelope, ok is false

An error replaces data with error and carries no usage, because failures are free. Branch on error.code, not the message. When retry_with is present, it names what to change and send again.

All error codes →
422 Unprocessable Entity
{
  "ok": false,
  "request_id": "req_…",
  "error": {
    "code": "no_captions",
    "number": 4103,
    "message": "No caption track (manual or auto-generated) is available. Send mode \"auto\" to transcribe the audio instead.",
    "docs": "https://transcriptfetch.com/docs/errors/no_captions",
    "retry_with": {
      "mode": "auto"
    }
  }
}
Data kinds

data.kind

Branch on data.kind rather than on the endpoint you called: a batch entry and a single fetch carry the same transcript shape.

kindWhat data holds
transcriptA single transcript: video metadata plus segments or text.
video_listA page of videos from a channel, profile, playlist or search, plus next_cursor.
playlist_listA page of playlists: a YouTube channel's playlists or podcasts tab, or a playlist search, plus next_cursor.
channel_listA page of channels from a YouTube channel search, plus next_cursor.
transcript_batchOne entry per requested video in data.results, each with an outcome.
transcript_jobThe 202 envelope and job polling for AI Fallback Transcription.
monitorA monitor. The other monitor endpoints answer monitor_list (your monitors and limits), monitor_event_list (a page of events), monitor_check (a check's result) and monitor_deleted.

Single transcript

The /video endpoint returns kind: "transcript" with either segments or text, never both. The other key is absent, not null, so check for presence, not truthiness.

timestampsReturnsShape
true (default)segmentsAn array; each segment's start and duration are in seconds, enough to render timestamps or export SRT/VTT
falsetextOne joined string

Every transcript carries the same metadata, whichever platform or path served it. 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.

FieldWhat it holds
video_idThe 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
urlThe canonical link, accepted as-is by every transcript endpoint
platformyoutube, tiktok, instagram or file
titleThe title; the post caption on TikTok and Instagram
channelThe creator, as the platform names them: a channel name, a TikTok @handle, an Instagram username
durationLength in seconds
languageThe caption track's code, or the language detected during AI Fallback Transcription
thumbnail_urlThe poster image. TikTok and Instagram URLs are signed and expire, so copy the image rather than hotlinking it
source"captions" for an existing caption track, "audio" for AI Fallback Transcription (on by default when there are no captions; billed by length, see usage.credits_spent)
JSON
{
  "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
  }
}

/channel, /playlist and /search return kind: "video_list": a videos array of metadata and a next_cursor for the next page. Fetch the transcripts separately with the batch endpoint.

StatusWhen
404The channel, profile or playlist does not exist or is not public; error.code is "not_found"
200It exists but has nothing to list: an empty videos array, free of charge
503An upstream fault while listing, never an empty page

A list can also carry a notice saying why a page is empty or may be incomplete, such as "This channel has no Live tab."

JSON
{
  "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
  }
}

Playlist and channel lists

Some YouTube listings return playlists or channels instead of videos. Both page with next_cursor like a video list, and every row's url is accepted as-is by the playlist or channel endpoint.

kindWhenEach row carries
playlist_listA channel's playlists or podcasts tab, or a search with type: "playlist"In playlists: playlistId, url, title, channel, videoCount
channel_listA search with type: "channel"In channels: channelId, url, handle, title, subscribers (YouTube's rounded label), videoCount (always null: YouTube's channel results no longer show one), description

The channel and search references show a full example of each.

Batch

The batch endpoint returns kind: "transcript_batch", one entry per requested video in data.results. Entries carry both text and segments (batch does not accept timestamps), plus an outcome:

outcomeThe entry
okThe transcript
processingNo caption track, so it went to AI Fallback Transcription (the default): a job_id and poll_url, nothing charged on the batch call, billed on delivery at the audio rate. Re-send the same batch once it finishes and the text comes back normally; polling is optional
errorThe same error block as a request-level failure, and no text fields

Send mode: "captions" to have captionless entries fail as error with code no_captions (and a retry_with naming the audio mode) instead.

JSON
{
  "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": "auto"
          }
        }
      }
    ]
  },
  "usage": {
    "credits_spent": 1,
    "balance": 97
  }
}

Markdown and chunks for AI (beta)

Two optional request fields on /video and /batch repackage the transcript for an AI pipeline. Both are free, both work on every platform, and both are absent unless you ask, so a request without them answers exactly as before.

  • "markdown": true adds markdown: a title line, a line with the channel, length and link, then paragraphs that each open with their time. On YouTube each time links to that moment in the video. Use it when a model will quote or cite the transcript.
  • "chunks": true adds chunks: the transcript split into pieces sized for an embedding model or vector store. Each piece is { index, text, start, end, url, tokens }, where url opens the video at start (on YouTube; on TikTok and Instagram it is the video's own link). Pieces break between caption lines, never mid-word, and each repeats the end of the previous one so a sentence on the boundary is never lost. start and end are null when a transcript has no timings.
  • Size the pieces with "chunks": { "max_tokens": 500, "overlap_tokens": 50 } (those are the defaults): max_tokens from 100 to 2000, overlap_tokens from 0 to 200 and smaller than max_tokens. Tokens are counted with OpenAI's o200k_base encoding, close to what Claude and Gemini count.
BASH
curl -X POST https://transcriptfetch.com/api/v2/transcripts/video \
  -H "Authorization: Bearer $TRANSCRIPTFETCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"video": "aircAruvnKk", "timestamps": false, "chunks": {"max_tokens": 500, "overlap_tokens": 50}}'
JSON
"chunks": [
  {
    "index": 0,
    "text": "This is a 3. It's sloppily written and rendered at an extremely low resolution of 28x28 pixels, …",
    "start": 4.22,
    "end": 139.18,
    "url": "https://www.youtube.com/watch?v=aircAruvnKk&t=4s",
    "tokens": 494
  },
  {
    "index": 1,
    "text": "There are many many variants of neural networks, …",
    "start": 125.04,
    "end": 272.34,
    "url": "https://www.youtube.com/watch?v=aircAruvnKk&t=125s",
    "tokens": 494
  }
]

That 18-minute video makes 9 pieces with the defaults. On batch, every ok entry gains the same keys. A captionless video that goes to AI Fallback Transcription remembers the options: its job poll and its webhook answer with markdown and chunks too. v1 does not take these fields.

Search with transcripts (beta)

Add transcripts to a YouTube, TikTok or Instagram video search and every video row comes back with a transcript, so one call goes from a query to what the videos say:

BASH
curl -X POST https://transcriptfetch.com/api/v2/transcripts/search \
  -H "Authorization: Bearer $TRANSCRIPTFETCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "how neural networks learn", "captions": true, "limit": 10, "transcripts": {"timestamps": false, "chunks": true}}'

Each row's transcript has the same shape as a batch entry without video_id: an outcome (ok, processing or error), then text, segments, markdown and chunks as asked, or the error block. transcripts: true fetches caption transcripts with timestamps; the object takes mode, timestamps, markdown and chunks. The mode defaults to "captions" here, so a search never starts paid AI Fallback Transcription unless you send "mode": "auto" (captionless rows then come back processing with a job, as in a batch). limit can be at most 10 with transcripts, and type must be a video search.

It costs the page's credit plus 1 credit per caption transcript delivered; failed rows are free. An account that cannot cover the page and every row is refused with 402 before anything is fetched.

Field reference

  • data.kind: transcript, video_list, playlist_list, channel_list, transcript_batch, transcript_job, or me; the monitor endpoints answer with monitor, monitor_list, monitor_event_list, monitor_check and monitor_deleted.
  • text / segments: exactly one on single-transcript responses (chosen by timestamps); both on batch entries and completed job results. Segments are { start, duration, text } cues in seconds (speaker added when diarized).
  • next_cursor: pass back as cursor for the next page of a list, null when exhausted.
  • outcome (batch results): ok, processing, or error; failed entries carry error (the standard block), and processing entries (captionless videos in AI Fallback Transcription) carry job_id and poll_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; null when unknown.
  • source (transcripts): "captions" or "audio".
  • markdown, chunks (transcripts, beta): present only when the request asked for them; see above.
  • usage.credits_spent: credits charged for this request. Failures are never charged and carry no usage block.
  • usage.balance: remaining credit balance (null for unlimited accounts).
  • usage.bytes: upstream bytes this request consumed (absent on batch, which reports per-entry bytes).