TranscriptFetch REST API

REST API (current version v2, path-prefixed) for fetching timestamped transcripts from YouTube, TikTok, Instagram, Spotify, Apple Podcasts, podcast RSS feeds, and direct media files, with AI audio transcription when captions are missing, plus YouTube channel/playlist/search discovery, batch up to 500, async jobs with polling or signed callbacks, cursor pagination, Idempotency-Key replay, and credit-based billing where failures are free. Auth via bearer API key (tf_live_...); a hosted MCP server at /mcp exposes the same surface as five tools over OAuth or API key.

Operations 8

POST /api/v2/transcripts/video Fetch a transcript (YouTube, TikTok, Instagram, podcast, or file URL) #
POST /api/v2/transcripts/channel List a channel's, profile's, show's, or feed's items #
POST /api/v2/transcripts/playlist List a playlist's videos or episodes #
POST /api/v2/transcripts/search Search for videos or episodes #
GET /api/v2/transcripts/jobs/{jobId} Poll an audio-transcription job #
POST /api/v2/transcripts/batch Fetch up to 500 transcripts in one call (plan-dependent) #
GET /api/v2/me Validate your key & check balance #
GET /api/v2/health Health check #

Documentation

Specifications

Other Resources

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/transcriptfetch-rest-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

transcriptfetch-api-v2-openapi.json Raw ↑
{"openapi":"3.1.0","info":{"title":"TranscriptFetch API","version":"2.0.0","description":"Fetch video transcripts from YouTube, TikTok, and Instagram, plus podcasts (Spotify, Apple Podcasts, or an RSS feed) and direct media file URLs. Videos without a caption track are transcribed automatically. YouTube additionally resolves playlists, channels, and keyword searches into video lists. Successful (`ok`) caption fetches and video lists cost 1 credit each; audio transcription costs 1 credit per started minute of audio (minimum 1 credit). Requests that fail or return no transcript are free."},"servers":[{"url":"https://transcriptfetch.com","description":"Production"}],"externalDocs":{"description":"Documentation","url":"https://transcriptfetch.com/docs"},"security":[{"bearerAuth":[]}],"tags":[{"name":"Transcripts","description":"Transcript and video-list endpoints"},{"name":"System","description":"Service health and metadata"}],"paths":{"/api/v2/transcripts/video":{"post":{"tags":["Transcripts"],"summary":"Fetch a transcript (YouTube, TikTok, Instagram, podcast, or file URL)","description":"Returns a transcript - text plus timestamped segments. Accepts YouTube, TikTok, and Instagram URLs (or a bare TikTok video id), direct media file URLs, and podcast links (Spotify, Apple Podcasts, or an RSS feed), which resolve to the episode's audio automatically and come back with a podcast block naming the show and episode. Podcast transcriptions may include best-effort speaker diarization: when labels were produced, each segment carries a `speaker` integer (0, 1, ...) identifying who is talking and a top-level `diarized: true` marks the transcript. Speaker ids are hints from voice separation, not named identification, and non-podcast sources never carry them. When no captions exist the audio is transcribed automatically: when we can determine the media length, media under 20 minutes simply waits (the request is held open for up to 45 seconds) and returns the finished transcript, so no polling is needed; when the length cannot be determined, only short-form platforms (TikTok and Instagram) are held inline. Longer media, or a transcription still running when the 45-second hold expires, returns 202 with a job to poll instead - the work continues either way, so the same request is safe to retry and will hit the cache once it finishes. Supply callback_url to have the finished transcript POSTed to you instead of polling. Every failure carries an ai_fallback block saying whether captions were definitively unavailable and whether retrying would work.","operationId":"fetchVideoTranscript","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoRequest"},"examples":{"example":{"value":{"video":"dQw4w9WgXcQ"}}}}}},"externalDocs":{"description":"Full API reference: fields, response shape, and error codes","url":"https://transcriptfetch.com/docs/endpoints"},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"examples":{"example":{"value":{"ok":true,"request_id":"req_…","data":{"kind":"transcript","video_id":"dQw4w9WgXcQ","platform":"youtube","title":"Example video","thumbnailUrl":"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}}}}}}},"202":{"description":"Accepted - audio transcription started. Poll poll_url for the result, or supply callback_url to have it delivered.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobAcceptedEnvelope"}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"402":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"502":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"}}}},"/api/v2/transcripts/channel":{"post":{"tags":["Transcripts"],"summary":"List a channel's, profile's, show's, or feed's items","description":"Resolve a creator or show into a paginated list of videos or episodes (metadata only), newest first. The platform is read from the input: a YouTube channel, a TikTok or Instagram profile URL, a Spotify or Apple Podcasts show URL, or any podcast RSS/Atom feed URL. Every row's url is accepted by the transcript and batch endpoints as-is. Pass since_video_id to get back only the items newer than one you've already seen - a page with nothing new is free, which makes this safe to poll on a schedule.","operationId":"fetchChannelVideos","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChannelRequest"},"examples":{"example":{"value":{"channel":"@lexfridman","limit":10}}}}}},"externalDocs":{"description":"Full API reference: fields, response shape, and error codes","url":"https://transcriptfetch.com/docs/endpoints"},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"examples":{"example":{"value":{"ok":true,"request_id":"req_…","data":{"kind":"video_list","source":"channel_videos","platform":"youtube","videos":[{"videoId":"dQw4w9WgXcQ","url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","title":"Example video","duration":212,"channel":"Example Channel","publishedAt":"2009-10-25T00:00:00Z","stats":{"plays":1600000000}}],"next_cursor":"eyJvIjoxMH0"},"usage":{"credits_spent":1,"balance":98,"bytes":0}}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"402":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"502":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"}}}},"/api/v2/transcripts/playlist":{"post":{"tags":["Transcripts"],"summary":"List a playlist's videos or episodes","description":"Resolve a playlist into a paginated list of videos or episodes (metadata only). The platform is read from the input: a YouTube playlist, a TikTok playlist URL, or a Spotify playlist URL. Instagram and Apple Podcasts have no public playlists; list the profile or show through the channel endpoint instead.","operationId":"fetchPlaylistVideos","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaylistRequest"},"examples":{"example":{"value":{"playlist":"PLrAXtmRdnEQy6nuLMt9H1aZIuhcGOcZQ4","limit":10}}}}}},"externalDocs":{"description":"Full API reference: fields, response shape, and error codes","url":"https://transcriptfetch.com/docs/endpoints"},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"examples":{"example":{"value":{"ok":true,"request_id":"req_…","data":{"kind":"video_list","source":"playlist","platform":"youtube","videos":[{"videoId":"dQw4w9WgXcQ","url":"https://www.youtube.com/watch?v=dQw4w9WgXcQ","title":"Example video","duration":212,"channel":"Example Channel","publishedAt":"2009-10-25T00:00:00Z","stats":{"plays":1600000000}}],"next_cursor":"eyJvIjoxMH0"},"usage":{"credits_spent":1,"balance":98,"bytes":0}}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"402":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"502":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"}}}},"/api/v2/transcripts/search":{"post":{"tags":["Transcripts"],"summary":"Search for videos or episodes","description":"Resolve a keyword search into a paginated list of videos or podcast episodes (metadata only). Searches YouTube by default; set platform to search TikTok, Instagram, Spotify, Apple Podcasts, or the open podcast index instead. Every row carries a url the transcript endpoint accepts unchanged, plus duration and publish time where the source provides them.","operationId":"searchVideos","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"},"examples":{"example":{"value":{"query":"how transformers work","platform":"tiktok","limit":10}}}}}},"externalDocs":{"description":"Full API reference: fields, response shape, and error codes","url":"https://transcriptfetch.com/docs/endpoints"},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"examples":{"example":{"value":{"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}}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"402":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"},"502":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"}}}},"/api/v2/transcripts/jobs/{jobId}":{"get":{"tags":["Transcripts"],"summary":"Poll an audio-transcription job","description":"Returns the state of a transcription job created when a request escalated to audio. 200 for any job your account owns (an unknown id, or a job belonging to someone else, is a 404): read the status field, which is \"processing\" while the job is running, \"completed\" with the transcript in data, or \"failed\" with an error. The job_id and a ready-to-use poll_url come back in the 202 that started it. Free to poll on our side - credits are charged once, on delivery (1 credit per started minute of audio, minimum 1). NOTE for RapidAPI subscribers: every poll is still a billable request on your Hub plan, so prefer callback_url, or simply re-send the original request once the job has had time to finish (the result is cached, so it returns instantly and the delivery is charged once, not twice). Most transcriptions never reach this endpoint at all: when we can determine the media length, media under 20 minutes is returned synchronously, and short-form platforms (TikTok and Instagram) are held inline even when we cannot.","operationId":"getTranscriptJob","parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string"}}],"externalDocs":{"description":"Full API reference: fields, response shape, and error codes","url":"https://transcriptfetch.com/docs/endpoints"},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobStatusEnvelope"},"examples":{"example":{"value":{"ok":true,"request_id":"req_…","status":"completed","job_id":"asr_m3k1x9qz4vb2p7","data":{"kind":"transcript","video_id":"dQw4w9WgXcQ","title":"Example video","text":"We're no strangers to love …","segments":[{"start":0,"duration":3.5,"text":"We're no strangers to love"}]},"usage":{"credits_spent":1,"balance":942,"bytes":18244}}}}}}},"401":{"$ref":"#/components/responses/Error"},"404":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/api/v2/transcripts/batch":{"post":{"tags":["Transcripts"],"summary":"Fetch up to 500 transcripts in one call (plan-dependent)","description":"Fetch a batch of transcripts concurrently. Batch size follows your plan: up to 50 entries on the free tier, Basic and Pro, and up to 500 on Mega and Scale (a request over your plan's cap fails whole with a clear error before anything is fetched or charged). Accepts the same inputs as /transcripts/video - YouTube, TikTok and Instagram URLs as well as bare YouTube IDs. Charges 1 credit per successfully fetched transcript; failed videos are free. Entries with no caption track are transcribed from audio by default: those come back with outcome \"processing\" and a job_id, cost nothing on this call, and are charged on delivery at the audio rate. Re-send the same batch once they have finished and the text is returned normally - polling is optional. Send mode: \"captions\" to keep the old behaviour and have captionless entries fail as no_transcript instead.","operationId":"fetchTranscriptsBatch","parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchRequest"},"examples":{"example":{"value":{"video_ids":["dQw4w9WgXcQ","https://www.tiktok.com/@user/video/7137723462233555205"],"mode":"auto"}}}}}},"externalDocs":{"description":"Full API reference: fields, response shape, and error codes","url":"https://transcriptfetch.com/docs/endpoints"},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BatchEnvelope"},"examples":{"example":{"value":{"ok":true,"request_id":"req_…","data":{"kind":"transcript_batch","results":[{"video_id":"dQw4w9WgXcQ","outcome":"ok","title":"Example video","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}}}}}}},"400":{"$ref":"#/components/responses/Error"},"401":{"$ref":"#/components/responses/Error"},"402":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/Error"}}}},"/api/v2/me":{"get":{"tags":["System"],"summary":"Validate your key & check balance","description":"Validates the API key and returns the account's remaining credit balance. Free - never billed. Useful for programmatic balance checks and as a credential test in integrations.","operationId":"me","externalDocs":{"description":"Full API reference: fields, response shape, and error codes","url":"https://transcriptfetch.com/docs/endpoints"},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope"},"examples":{"example":{"value":{"ok":true,"request_id":"req_…","data":{"kind":"me","user_id":"user_…","credits":250},"usage":{"credits_spent":0,"balance":250,"bytes":0}}}}}}},"401":{"$ref":"#/components/responses/Error"}}}},"/api/v2/health":{"get":{"tags":["System"],"summary":"Health check","description":"Public liveness probe for uptime monitoring. Returns 200 whenever the API is serving. No authentication required and no credits used.","operationId":"healthCheck","security":[],"externalDocs":{"description":"Full API reference: fields, response shape, and error codes","url":"https://transcriptfetch.com/docs/endpoints"},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"},"examples":{"example":{"value":{"status":"ok","service":"transcriptfetch-api","version":"2.0.0","time":"2026-06-16T00:00:00.000Z"}}}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Send your API key as `Authorization: Bearer <key>`."}},"parameters":{"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":false,"description":"Unique key (≤255 chars) to safely retry a request without being charged twice. Replays return the original response with `Idempotent-Replayed: true`. Reusing a key with a different body is a 409 conflict.","schema":{"type":"string","maxLength":255}}},"schemas":{"VideoRequest":{"type":"object","required":["video"],"properties":{"video":{"type":"string","minLength":1,"maxLength":2000,"description":"A video URL or 11-character YouTube video ID. Accepts YouTube (watch, youtu.be, /shorts/), TikTok, and Instagram URLs, plus direct media file URLs (mp4/mp3/wav/…). Also accepts podcast links: a Spotify or Apple Podcasts episode URL, or a podcast RSS feed URL, which is resolved to that episode's audio automatically. Videos without captions fall back to AI transcription."},"mode":{"type":"string","enum":["captions","audio","auto"],"default":"auto","description":"Where the text may come from. \"captions\" reads an existing caption track and fails if there is none, which is the only way to avoid audio transcription. \"audio\" skips captions and transcribes the audio. \"auto\" (the default) tries captions first and transcribes the audio when there are none. Short media may finish inline after a wait of up to 45 seconds; longer work returns 202 with a job to poll or deliver by callback. Audio is charged only on delivery: 1 credit per started minute of audio, minimum 1 (a 20-minute video is 20; the 4-hour cap is 240). Caption fetches are always 1 credit."},"timestamps":{"default":true,"oneOf":[{"type":"boolean"},{"type":"string","enum":["none","segment"]}],"description":"Which form the transcript comes back in. true (the default) returns the `segments` array, each with start, duration and text (podcast episodes transcribed from audio may also carry a `speaker` id per segment - see the podcast notes). false returns a single joined `text` string instead. On the single-transcript 200 exactly one of the two is present, never both, since segments already contain every word the joined text does; job results and batch entries carry both text and segments. The older strings \"segment\" and \"none\" mean the same two things and are still accepted."},"callback_url":{"type":"string","format":"uri","maxLength":2000,"description":"Where to POST the finished transcript when a request escalates to audio transcription, instead of polling the job. The delivery body is a trimmed envelope - {ok, status, job_id, data} on success, {ok, status, job_id, error} on failure - without the usage and request_id the poll URL adds. When a signing secret is configured on our side, the body is signed with HMAC-SHA256 over the exact bytes and sent as an X-TranscriptFetch-Signature: sha256=<hex> header so you can verify it came from us. Must be a public https URL on the standard port; the URL is checked again at delivery time, so an unreachable or private address still gets a 202 but never receives a delivery. The job stays pollable either way, so a missed delivery is never a lost transcript."},"ai_fallback":{"type":"boolean","default":false,"deprecated":true,"description":"Legacy alias for \"mode\", still supported. true is identical to \"mode\": \"audio\"; omitted or false is \"mode\": \"auto\". Send one or the other, not both. Note that false never disabled the fallback - audio was still transcribed when no captions existed - which is why the field was replaced."}}},"ChannelRequest":{"type":"object","required":["channel"],"properties":{"channel":{"type":"string","minLength":1,"maxLength":2000,"description":"YouTube: @handle, /channel/UC… URL, or UC… ID. TikTok: profile URL (tiktok.com/@user). Instagram: profile URL (instagram.com/user/). Spotify: show URL (open.spotify.com/show/…). Apple Podcasts: show URL (podcasts.apple.com/…/id…). Any other URL is fetched as a podcast RSS/Atom feed."},"limit":{"type":"integer","minimum":1,"maximum":50,"default":5,"description":"Max videos to return per page. Defaults to 5."},"cursor":{"type":"string","maxLength":256,"description":"Opaque pagination cursor from a previous response's next_cursor (max 256 characters). Omit for the first page. Cursors are scoped to the listing (and, for search, the platform) that issued them. Offset-based sources cannot page past the first 2000 items."},"since_video_id":{"type":"string","minLength":1,"maxLength":2000,"description":"Newest video ID you have already seen. The response is trimmed to videos newer than it, and a page with nothing newer costs no credits - so you can poll a channel for new uploads for free."}}},"PlaylistRequest":{"type":"object","required":["playlist"],"properties":{"playlist":{"type":"string","minLength":1,"maxLength":2000,"description":"YouTube: playlist URL or PL… ID. TikTok: playlist URL (tiktok.com/@user/playlist/name-id). Spotify: playlist URL (open.spotify.com/playlist/…)."},"limit":{"type":"integer","minimum":1,"maximum":50,"default":5,"description":"Max videos to return per page. Defaults to 5."},"cursor":{"type":"string","maxLength":256,"description":"Opaque pagination cursor from a previous response's next_cursor (max 256 characters). Omit for the first page. Cursors are scoped to the listing (and, for search, the platform) that issued them. Offset-based sources cannot page past the first 2000 items."}}},"SearchRequest":{"type":"object","required":["query"],"properties":{"query":{"type":"string","minLength":1,"maxLength":2000,"description":"Keyword search query."},"platform":{"type":"string","enum":["youtube","tiktok","instagram","spotify","apple","rss"],"default":"youtube","description":"Where to search: youtube (default), tiktok, instagram, spotify, apple, or rss. Every result's url is accepted by the transcript and batch endpoints as-is. rss searches the open podcast index via Apple's directory and returns episode audio (enclosure) URLs."},"limit":{"type":"integer","minimum":1,"maximum":50,"default":5,"description":"Max videos to return per page. Defaults to 5."},"cursor":{"type":"string","maxLength":256,"description":"Opaque pagination cursor from a previous response's next_cursor (max 256 characters). Omit for the first page. Cursors are scoped to the listing (and, for search, the platform) that issued them. Offset-based sources cannot page past the first 2000 items."}}},"BatchRequest":{"type":"object","required":["video_ids"],"properties":{"video_ids":{"type":"array","minItems":1,"maxItems":500,"items":{"type":"string","minLength":1,"maxLength":200},"description":"Video URLs or bare YouTube IDs, from any supported platform. Up to your plan's batch cap: 50 on free/Basic/Pro, 500 on Mega/Scale. Also accepted as the legacy key videoIds (send one or the other, not both)."},"mode":{"type":"string","enum":["auto","captions"],"description":"Where each entry's text may come from. \"auto\" (the default) reads captions and transcribes the audio when there are none. \"captions\" reads an existing caption track only, so a captionless video fails as no_transcript - the behaviour batch had before audio fallback existed. Note \"audio\" is not accepted here: skipping captions for 50 videos at once is a job queue, not a batch fetch."}}},"Health":{"type":"object","properties":{"status":{"type":"string","enum":["ok"]},"service":{"type":"string"},"version":{"type":"string"},"time":{"type":"string","format":"date-time"}}},"Usage":{"type":"object","properties":{"credits_spent":{"type":"integer"},"balance":{"type":["integer","null"],"description":"Remaining balance, or null for unlimited (admin) accounts."},"bytes":{"type":"integer"}}},"Segment":{"type":"object","properties":{"start":{"type":"number","description":"Start time in seconds."},"duration":{"type":"number","description":"Cue duration in seconds."},"text":{"type":"string"},"speaker":{"type":"integer","description":"Speaker id (0, 1, …) on diarized podcast transcripts only; absent everywhere else."}}},"Video":{"type":"object","description":"One row of a listing. `url` is accepted as-is by the transcript and batch endpoints. The platform is not repeated per row: the page's `platform` says it.","properties":{"videoId":{"type":"string"},"url":{"type":"string","format":"uri"},"title":{"type":"string"},"duration":{"type":["number","null"],"description":"Seconds, when the source reports it."},"channel":{"type":["string","null"]},"publishedAt":{"type":["string","null"],"format":"date-time","description":"Upload time. Exact for TikTok, Instagram and podcasts; approximate on YouTube, whose listings only say \"2 days ago\", so it is exact to the day for recent videos and up to a year off for old ones."},"stats":{"type":["object","null"],"description":"Engagement counts where the source exposes them.","properties":{"plays":{"type":["integer","null"]}}}}},"SuccessEnvelope":{"type":"object","required":["ok","request_id","data","usage"],"properties":{"ok":{"type":"boolean","enum":[true]},"request_id":{"type":"string"},"data":{"oneOf":[{"$ref":"#/components/schemas/TranscriptData"},{"$ref":"#/components/schemas/VideoListData"},{"$ref":"#/components/schemas/MeData"}],"discriminator":{"propertyName":"kind"}},"usage":{"$ref":"#/components/schemas/Usage"}}},"TranscriptData":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["transcript"]},"video_id":{"type":"string"},"title":{"type":["string","null"]},"source":{"type":"string","enum":["captions","audio"],"description":"Where the words came from: an existing caption track, or AI transcription of the audio (billed by length, see usage.credits_spent)."},"thumbnailUrl":{"type":["string","null"],"description":"Poster image for the video. TikTok and Instagram serve signed, expiring URLs, so copy the image rather than hotlinking it."},"diarized":{"type":"boolean","description":"True when speaker diarization produced labels (podcast transcriptions only); segments then carry `speaker` ids."},"text":{"type":["string","null"]},"segments":{"type":["array","null"],"items":{"$ref":"#/components/schemas/Segment"}},"podcast":{"type":"object","description":"Present only when the input was a podcast link. Spotify and Apple do not host podcast audio; both read the publisher's RSS feed, so the link is resolved to that feed and the episode's own audio file. These fields say which show and episode were matched, so you can verify the resolution was correct.","properties":{"show":{"type":["string","null"]},"episode":{"type":["string","null"]},"published_at":{"type":["string","null"],"format":"date-time"},"feed_url":{"type":["string","null"],"format":"uri"},"audio_url":{"type":"string","format":"uri"},"resolved_via":{"type":"string","enum":["spotify","apple","rss","direct"]}}}}},"VideoListData":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["video_list"]},"source":{"type":"string","description":"Which listing produced the page: channel, playlist or search."},"platform":{"type":"string","enum":["youtube","tiktok","instagram","spotify","apple","rss"],"description":"Where the rows came from: the `platform` field on search, the platform detected from the URL on channel and playlist."},"videos":{"type":"array","items":{"$ref":"#/components/schemas/Video"}},"next_cursor":{"type":["string","null"],"description":"Pass back as `cursor` for the next page; null when exhausted."}}},"MeData":{"type":"object","required":["kind"],"properties":{"kind":{"type":"string","enum":["me"]},"user_id":{"type":"string"},"credits":{"type":["integer","null"],"description":"Remaining balance, or null for unlimited (admin) accounts."}}},"TranscriptJobData":{"type":"object","required":["kind"],"description":"Placeholder data block on a 202: identifies what is being transcribed while the job runs.","properties":{"kind":{"type":"string","enum":["transcript_job"]},"video_id":{"type":"string"},"platform":{"type":"string"}}},"JobAcceptedEnvelope":{"type":"object","required":["ok","request_id","status","job_id","poll_url"],"description":"Returned with HTTP 202 when a request escalates to audio transcription. Poll poll_url (or wait for the callback_url delivery); credits are charged on delivery, not here.","properties":{"ok":{"type":"boolean","enum":[true]},"request_id":{"type":"string"},"status":{"type":"string","enum":["processing"]},"job_id":{"type":"string"},"poll_url":{"type":"string","description":"Path of the job endpoint to poll."},"data":{"$ref":"#/components/schemas/TranscriptJobData"}}},"JobStatusEnvelope":{"type":"object","required":["ok","request_id","status","job_id"],"description":"Response of the job poll endpoint. data holds the transcript once status is completed and is null while processing (usage is absent then); a failed job has ok:false and an error block instead.","properties":{"ok":{"type":"boolean"},"request_id":{"type":"string"},"status":{"type":"string","enum":["processing","completed","failed"]},"job_id":{"type":"string"},"data":{"description":"The finished transcript; null until the job completes.","oneOf":[{"$ref":"#/components/schemas/TranscriptData"},{"type":"null"}]},"usage":{"$ref":"#/components/schemas/Usage"},"error":{"$ref":"#/components/schemas/ErrorBlock"}}},"BatchEnvelope":{"type":"object","required":["ok","request_id","data","usage"],"properties":{"ok":{"type":"boolean","enum":[true]},"request_id":{"type":"string"},"data":{"type":"object","properties":{"kind":{"type":"string","enum":["transcript_batch"]},"results":{"type":"array","items":{"$ref":"#/components/schemas/BatchResult"}}}},"usage":{"$ref":"#/components/schemas/Usage"}}},"BatchResult":{"type":"object","required":["video_id","outcome"],"description":"One entry per requested video. Exactly three outcomes: \"ok\" carries the transcript, \"processing\" carries the audio-transcription job to poll, \"error\" carries the same error block a request-level failure does.","properties":{"video_id":{"type":"string"},"outcome":{"type":"string","enum":["ok","processing","error"]},"title":{"type":["string","null"],"description":"outcome \"ok\" only."},"source":{"type":"string","enum":["captions","audio"],"description":"outcome \"ok\" only."},"text":{"type":["string","null"],"description":"outcome \"ok\" only."},"segments":{"type":["array","null"],"items":{"$ref":"#/components/schemas/Segment"},"description":"outcome \"ok\" only."},"bytes":{"type":"integer","description":"outcome \"ok\" only."},"job_id":{"type":"string","description":"outcome \"processing\" only: the audio-transcription job started for a captionless entry. Re-send the batch later or poll poll_url."},"poll_url":{"type":"string","description":"outcome \"processing\" only: path to poll for the finished transcript."},"error":{"$ref":"#/components/schemas/ErrorBlock"}}},"ErrorBlock":{"type":"object","required":["code","number","message","docs"],"description":"Every failure carries this block. Branch on code (a stable string) or number (a stable integer whose thousands digit is the family: 1 request, 2 account, 3 input, 4 content, 5 transient, 9 ours; 5xxx means retry with backoff). At most one of retry_with, details, issues is present. Failures are never charged.","properties":{"code":{"type":"string","enum":["invalid_request","invalid_cursor","unauthorized","idempotency_conflict","not_found","insufficient_credits","batch_too_large","rate_limited","unsupported_platform","endpoint_platform_mismatch","podcast_feed_not_found","audio_ineligible","invalid_input","audio_too_long","drm_protected","private","unavailable","members_only","age_restricted","region_blocked","live_stream","was_live","no_captions","captions_disabled","no_speech","no_audio_stream","upstream_error","timeout","connection","proxy_unavailable","parse_error","upstream_unavailable","internal_error","unknown"]},"number":{"type":"integer","enum":[1001,1002,1101,1201,1301,2001,2002,2101,3001,3002,3003,3004,3005,3006,3007,4001,4002,4003,4004,4005,4101,4102,4103,4104,4105,4106,5001,5002,5003,5004,5005,5101,9001,9002]},"message":{"type":"string","description":"Prose for humans. Never parse it."},"docs":{"type":"string","format":"uri","description":"https://transcriptfetch.com/docs/errors/<code>"},"retry_with":{"type":"object","description":"Present when a different request would succeed: the fields to change, e.g. { \"mode\": \"audio\" } to transcribe a captionless video, or { \"endpoint\": \"/api/v2/transcripts/video\" } for a platform this endpoint does not list.","additionalProperties":true},"details":{"type":"object","description":"Structured specifics for the few codes that document one: batch_too_large { max, sent }, p

# --- truncated at 32 KB (32 KB total) ---
# Full source: https://raw.githubusercontent.com/api-evangelist/transcriptfetch/refs/heads/main/openapi/transcriptfetch-api-v2-openapi.json