The official Node.js / TypeScript client for the TranscriptFetch API. Fetch transcripts as clean, typed data, with built-in retries, idempotency, and a typed error hierarchy.
- Transcripts from YouTube, TikTok, Instagram, podcasts, and direct media file URLs
- YouTube channel, playlist, and search listing
- Typed responses (full TypeScript types, ESM + CommonJS)
- Automatic retries on 429 and 5xx with backoff
- Auto-generated idempotency keys on writes
- Auto-paginating async iterators
- Zero runtime dependencies (uses the built-in
fetch, Node 18+)
npm install transcriptfetchimport { TranscriptFetch } from "transcriptfetch";
// apiKey falls back to the TRANSCRIPTFETCH_API_KEY env var
const tf = new TranscriptFetch("tf_live_...");
const t = await tf.transcripts.video("https://youtu.be/aircAruvnKk");
console.log(t.title);
console.log(t.text);
for (const seg of t.segments) {
console.log(`[${seg.start.toFixed(1)}] ${seg.text}`);
}
console.log("credits left:", t.usage?.balance);Keep your key server-side. Never ship it to the browser.
transcripts.video() and transcripts.batch() accept:
| Input | Example |
|---|---|
| YouTube URL or bare video ID | https://youtu.be/aircAruvnKk, aircAruvnKk |
| TikTok video URL | https://www.tiktok.com/@user/video/7137723462233555205 |
| Instagram post or reel URL | https://www.instagram.com/reel/Cxyz.../ |
| Podcast episode or feed | https://open.spotify.com/episode/..., https://podcasts.apple.com/..., https://feeds.example.com/show.xml |
| Direct media file URL | https://example.com/talk.mp3 |
The string is sent to the API as-is, so the SDK never has to be upgraded for the API to accept a new input.
A podcast link is resolved to that episode's audio automatically, and the
response carries a podcast block naming the show and episode:
const t = await tf.transcripts.video("https://open.spotify.com/episode/...");
console.log(t.podcast?.show, "-", t.podcast?.episode);channel(), playlist(), and search() are YouTube-only concepts and take
YouTube handles, IDs, and queries.
When a source has no captions the API transcribes its audio, which takes longer
than one request. You get a transcript back with status: "processing" and a
jobId instead of text. Poll it:
let t = await tf.transcripts.video("https://example.com/episode.mp3");
while (t.status === "processing") {
await new Promise((r) => setTimeout(r, 5_000));
// Polling is free: credits are charged once, on delivery.
const polled = await tf.transcripts.job(t.jobId!);
if (polled.status === "failed") throw new Error(polled.error?.message ?? "job failed");
t = polled;
}
console.log(t.text);Everything else answers in one call, so status is null there and no polling is
needed.
await tf.transcripts.video(video); // single transcript (text + segments)
await tf.transcripts.batch(videoIds); // up to 50 transcripts in one call
await tf.transcripts.channel(channel, { limit, cursor }); // a YouTube channel's videos (metadata)
await tf.transcripts.playlist(playlist, { limit, cursor }); // a YouTube playlist's videos
await tf.transcripts.search(query, { limit, cursor }); // search YouTube
await tf.transcripts.job(jobId); // poll an async transcription job (free)
await tf.me(); // validate the key, read the balance (free)
await tf.health(); // unauthenticated liveness probeSkip cursor bookkeeping with the auto-paginating iterators:
for await (const video of tf.transcripts.iterChannel("@lexfridman", { limit: 10 })) {
console.log(video.videoId, video.title);
}Or page manually via page.nextCursor and the cursor option.
Every failure maps to a typed subclass so you can branch with instanceof:
import {
TranscriptFetch,
InsufficientCreditsError,
RateLimitError,
APIError,
} from "transcriptfetch";
try {
await tf.transcripts.video("bad");
} catch (err) {
if (err instanceof InsufficientCreditsError) {
// 402: top up at /pricing
} else if (err instanceof RateLimitError) {
console.log("retry after", err.retryAfter); // 429
} else if (err instanceof APIError) {
console.log(err.status, err.code, err.requestId);
}
}The full hierarchy: AuthenticationError, InvalidRequestError, InsufficientCreditsError, IdempotencyConflictError, RateLimitError, UpstreamUnavailableError, InternalServerError (all extend APIError), plus APIConnectionError and APITimeoutError for transport failures. All extend TranscriptFetchError.
const tf = new TranscriptFetch({
apiKey: "tf_live_...", // or TRANSCRIPTFETCH_API_KEY
baseUrl: "https://transcriptfetch.com",
timeout: 30_000, // ms
maxRetries: 2, // retries 429 + 5xx
});- API docs: https://transcriptfetch.com/docs
- Python SDK: https://github.com/TranscriptFetch/python-sdk
This package follows Semantic Versioning. Breaking changes only land in a new major version.
MIT