GitHubLog in
SDKs & integrations

Node SDK

The official, typed Node.js / TypeScript client. Fetch transcripts, channels, playlists, and search as structured data, ESM + CommonJS, zero runtime dependencies.

Official SDK. Source + issues on GitHub. Your key falls back to the TRANSCRIPTFETCH_API_KEY env var, keep it server-side. One credit per successful caption fetch (AI Fallback Transcription, on by default for videos without captions, bills per started 5 minutes of audio); failed/blocked/no-transcript requests are free.

Using n8n? There is a community node that wraps this API for n8n workflows, no code required: set up the n8n node.

Install

BASH
npm install transcriptfetch@2.4.0

SDK 2.4.0 requires Node 18+ (uses the built-in fetch). Use a currently supported Node.js release for a new project. Ships types for TypeScript.

Quickstart

Get an API key (50 free credits a month) at the dashboard, then:

TYPESCRIPT
import { TranscriptFetch } from "transcriptfetch";

// apiKey falls back to the TRANSCRIPTFETCH_API_KEY env var
const tf = new TranscriptFetch({ apiKey: "tf_live_..." });

const t = await tf.transcripts.video("https://youtu.be/aircAruvnKk");
if (t.status === "processing") {
  console.log("Still transcribing; poll this job:", t.jobId);
} else {
  console.log(t.title);
  for (const seg of t.segments) {
    console.log(`[${seg.start.toFixed(1)}] ${seg.text}`);
  }
}

console.log("credits left:", t.usage?.balance);

The single-video HTTP response returns timestamped segments by default and omits text. The SDK normalizes that missing text to null; join the segment text if you need both forms locally. timestamps: false requests plain text through the SDK:

TYPESCRIPT
const transcript = await tf.transcripts.video("https://youtu.be/aircAruvnKk", {
  mode: "captions", timestamps: false,
});
// Captions mode returns a finished transcript or throws; it never starts a job.
console.log(transcript.text);

Endpoints

video/channel/playlist accept URLs or raw IDs (normalized automatically).

TYPESCRIPT
await tf.transcripts.video(video);     // single transcript
await tf.transcripts.channel(channel, { limit, cursor });   // a channel's videos (metadata)
await tf.transcripts.playlist(playlist, { limit, cursor });               // a playlist's videos
await tf.transcripts.search(query, { limit, cursor });                    // search YouTube
await tf.transcripts.batch(videoIds);                                     // up to your plan’s batch limit
await tf.transcripts.batch(videoIds, { mode: "captions" });               // captions only - no audio fallback
// captionless entries come back outcome "processing" with a jobId: re-send the
// batch later, or poll tf.transcripts.job(jobId)
await tf.health();                                                        // unauthenticated liveness probe

SDK 2.4.0 adds mode, timestamps, and callbackUrl options to video(), plus the tf.monitors resource. Channel since_video_id and YouTube listing filters still use request() with the API’s snake_case field names. The generic method retains retries, idempotency keys, and typed errors. Batch limits are 50 on free/Basic/Pro and 500 on Mega/Scale.

For example:

TYPESCRIPT
const { data } = await tf.request("POST", "/api/v2/transcripts/channel", {
  body: { channel: "@lexfridman", tab: "shorts", limit: 10 },
  idempotent: true,
});

Monitors

SDK 2.4.0 supports the full monitor API. Creation starts scheduled checks for new videos. Every check, the initial baseline included, costs 1 credit; optional transcripts cost extra.

TYPESCRIPT
const client = new TranscriptFetch({ timeout: 120_000 });
const monitor = await client.monitors.create("@lexfridman", {
  intervalMinutes: 60, transcripts: true,
});
// Save monitor.webhookSecret securely: it is only returned on creation.
const current = await client.monitors.get(monitor.id);
const all = await client.monitors.list({ since: current.lastEventId ?? undefined });
for await (const event of client.monitors.iterEvents(monitor.id, { limit: 10 })) {
  console.log(event.type, event.data);
}
await client.monitors.update(monitor.id, { status: "paused" });
// await client.monitors.check(monitor.id) checks now and may spend credits.
// await client.monitors.delete(monitor.id) removes the monitor and its events.

Pass webhookUrl to create() for push delivery, or use events() / iterEvents() to read events. update(id, { webhookUrl: null }) removes a webhook; omitted settings stay unchanged. check() can return error on a listing failure even with HTTP 200. Options and results use camelCase, including intervalMinutes, nextCursor and hasNew. See the monitor guide for signature verification and billing.

Long media and jobs

AI Fallback Transcription can return 202 with a job instead of a finished transcript. The SDK exposes status: "processing" and jobId; poll tf.transcripts.job(jobId) until status is "completed" or "failed". A failed job is a returned result, so check its status and error. Short media may finish inline. To request a transcription webhook, pass callbackUrl to video(). The full contract, including what AI Fallback Transcription costs, is in the transcription guide.

Pagination

List endpoints are cursor-paginated. Iterate every result with an async generator:

TYPESCRIPT
// Iterate every result without managing cursors
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.

Errors

All errors subclass TranscriptFetchError. API errors carry .status, .code, .message, and .requestId.

TYPESCRIPT
import {
  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(err.retryAfter);            // 429
  } else if (err instanceof APIError) {
    console.log(err.status, err.code, err.requestId);
  }
}

Reliability built in. Automatic retries on 429 (honoring Retry-After) and 5xx with exponential backoff; every write auto-sends an Idempotency-Key so a retried request is never double-charged. Configure via new TranscriptFetch({ apiKey, baseUrl, timeout: 30000, maxRetries: 2 }).

Prefer raw HTTP? The API is plain HTTPS/JSON, see the endpoint reference for every route and the response format.