easytranscriber · 0.1.0 · Node.js 22+
JavaScript & TypeScript SDK
A server-only, typed client for all ten EasyTranscriber REST endpoints. ESM and CommonJS are included; requests use native fetch.
Try the API — no card requiredInstall and make your first request
Install the published npm package in your server application. Use Node.js 22 or later.
npm install easytranscriberCreate an API key in Dashboard → API Keys. Keys begin with et_. Read your server environment explicitly: the SDK does not automatically load environment variables or .env files. It sends Authorization: Bearer <key> on every request.
window exists. Use a server endpoint to serve your frontend instead.Use an .mjs file (or a project configured for ESM) for the following example. Transcription does not include a summary: summarization is a separate, billed JSON request.
import { EasyTranscriber } from "easytranscriber";
const apiKey = process.env.EASYTRANSCRIBER_API_KEY;
if (!apiKey) throw new Error("Configure EASYTRANSCRIBER_API_KEY on the server");
const client = new EasyTranscriber({ apiKey });
const transcript = await client.transcribe({
url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
});
console.log(transcript.transcript, transcript.credits_used);
// A separate request: 1 additional credit, even for a cached transcript.
const summary = await client.summarize({
transcript: transcript.transcript,
video_id: transcript.video_id,
language: "English",
});
console.log(summary.summary, summary.credits_used);Constructor and transport options
new EasyTranscriber(options: EasyTranscriberOptions) accepts the following options. Types, including EasyTranscriberOptions, are exported from easytranscriber.
| Option | Default | Contract |
|---|---|---|
| apiKey: string | Required | Nonempty API key; whitespace-only strings are rejected. |
| baseUrl?: string | https://www.easytranscriber.com/api/v1 | Include /api/v1. HTTP(S) URL without credentials, query, or fragment; trailing slashes are removed. Use HTTPS for production and send keys only to trusted endpoints. |
| timeoutMs?: number | 330000 | Per-attempt timeout, including response-body reading; integer 1–2147483647. |
| maxRetries?: number | 2 | Additional attempts for HTTP 429 only; integer 0–10. Set 0 to disable retries. |
| retryDelayMs?: number | 1000 | Initial exponential-backoff delay; integer 0–2147483647 milliseconds. |
| maxRetryDelayMs?: number | 60000 | Maximum retry wait; integer 0–2147483647 milliseconds. A longer Retry-After stops retries. |
| fetch?: typeof globalThis.fetch | globalThis.fetch | Optional server-side fetch adapter; a function is required. |
Invalid constructor options throw synchronously (usually TypeError); browser execution throws Error. GET requests encode supplied fields as query parameters, omitting undefined; POST requests serialize a JSON body. The SDK rejects redirects using redirect: "error" so a billed POST is not redirected and credentials are not forwarded elsewhere. Native fetch redirect failures become NetworkError.
All ten methods
Paths below are relative to /api/v1. Every method returns a Promise for a plain JSON object, not a { data: ... } envelope. Request fields retain their API snake_case names. Required request objects have no implicit defaults; omitted optional values use the server defaults shown here.
| Method | REST mapping | Request fields / defaults | Return type | Credits |
|---|---|---|---|---|
| transcribe(request: TranscribeRequest) | POST /transcribe | url: string (required YouTube video URL). | Promise<TranscribeResponse> | Cached: 0; new: 1, or 2 over two hours. |
| summarize(request: SummarizeRequest) | POST /summarize | transcript: string required; video_id?: string; language?: string. Omitted language matches the source (Auto). | Promise<SummarizeResponse> | 1 additional credit per call. |
| credits() | GET /credits | No arguments. | Promise<CreditsResponse> | Free. |
| search(request: SearchRequest) | GET /youtube/search | q: string required; type?: "video" | "channel" (default "video"); page_token?: string. | Promise<SearchResponse> | 1 per page. |
| resolveChannel(request: ChannelRequest) | GET /youtube/channel/resolve | channel: string required (@handle, channel URL, or UC… ID). | Promise<ResolveChannelResponse> | Free. |
| channelLatest(request: ChannelRequest) | GET /youtube/channel/latest | channel: string required. Recent RSS uploads; no page_token parameter. | Promise<ChannelLatestResponse> | Free. |
| channelSearch(request: ChannelSearchRequest) | GET /youtube/channel/search | channel: string and q: string required; page_token?: string. | Promise<ChannelSearchResponse> | 1 per page. |
| channelVideos(request: ChannelVideosRequest) | GET /youtube/channel/videos | channel: string required; page_token?: string. | Promise<ChannelVideosResponse> | 1 per page. |
| playlistVideos(request: PlaylistVideosRequest) | GET /youtube/playlist/videos | playlist: string required (playlist ID or URL); page_token?: string. | Promise<PlaylistVideosResponse> | 1 per page. |
| comments(request: CommentsRequest) | GET /youtube/comments | video: string required (video URL or ID); page_token?: string; max_results?: number (default 20, server bounds to 1–100; zero falls back to 20); order?: "time" | "relevance" (default "relevance"). | Promise<CommentsResponse> | 1 per page. |
PageRequest is also exported and defines page_token?: string. Omit it for the first page. Search, channel search, channel videos, playlist videos, and comments support it; no method automatically requests another page.
For summaries, video_id is optional and associates the summary with history. Omission returns video_id: null. Use language display names such as "English" or "Spanish", or "Auto" to match the source—not ISO codes such as "en". Omitted or unsupported language values use the server default (match the source). The SDK exposes buffered JSON summaries, not streaming.
Typed responses and nullability
Import models with import type. These are compile-time TypeScript interfaces, not runtime validators: the client checks that a successful response is a JSON object, but does not validate each field. Nullable fields remain null; timestamps remain strings rather than Date objects.
import { EasyTranscriber, type TranscribeResponse } from "easytranscriber";
async function getTranscript(client: EasyTranscriber): Promise<TranscribeResponse> {
return client.transcribe({ url: "https://youtu.be/dQw4w9WgXcQ" });
}subscribers is YouTube’s decimal string or null, not a number. Latest-video thumbnails are non-null strings; other video thumbnails can be null. reply_count can exceed replies.length: only available replies are returned. Free endpoints do not all include a credits_used field.
// All of these interfaces are exported by "easytranscriber".
interface TranscribeResponse {
video_id: string;
video_title: string | null;
transcript: string;
method: "yt-dlp" | "innertube" | "deepgram";
cached: boolean;
credits_used: number;
}
interface SummarizeResponse {
summary: string;
video_id: string | null;
credits_used: number;
}
interface CreditsResponse { balance: number }
interface SearchResult {
video_id: string | null;
channel_id: string;
title: string;
channel: string;
published_at: string;
thumbnail: string | null;
}
interface SearchResponse {
results: SearchResult[];
next_page_token: string | null;
credits_used: number;
}
interface ResolveChannelResponse {
channel_id: string;
name: string;
handle: string | null;
subscribers: string | null;
thumbnail: string | null;
}
interface VideoListItem {
video_id: string;
title: string;
published_at: string;
thumbnail: string | null;
}
interface LatestVideo extends VideoListItem { thumbnail: string }
interface ChannelLatestResponse {
channel_id: string;
videos: LatestVideo[];
}
interface ChannelSearchResponse extends SearchResponse { channel_id: string }
interface ChannelVideosResponse {
channel_id: string;
videos: VideoListItem[];
next_page_token: string | null;
total_results: number | null;
credits_used: number;
}
interface PlaylistVideosResponse {
playlist_id: string;
videos: VideoListItem[];
next_page_token: string | null;
total_results: number | null;
credits_used: number;
}
interface Comment {
author: string;
text: string;
likes: number;
published_at: string;
updated_at: string;
}
interface CommentThread extends Comment {
reply_count: number;
replies: Comment[];
}
interface CommentsResponse {
video_id: string;
comments: CommentThread[];
next_page_token: string | null;
total_results: number | null;
credits_used: number;
}
interface APIErrorResponse {
error: { code: string; message: string };
}Explicit, credit-aware pagination
Each list or search call retrieves one page. Preserve the original query, channel, playlist, or video parameters and pass the returned next_page_token as page_token for the next page. A null token means there is no next page. Nullable total_results is not a reason to keep paging. The example deliberately requests at most two pages, each costing one credit.
import { EasyTranscriber } from "easytranscriber";
const apiKey = process.env.EASYTRANSCRIBER_API_KEY;
if (!apiKey) throw new Error("Configure EASYTRANSCRIBER_API_KEY on the server");
const client = new EasyTranscriber({ apiKey });
const first = await client.channelVideos({ channel: "@GoogleDevelopers" });
console.log(first.videos);
// Explicitly opt into at most one more billed page.
if (first.next_page_token !== null) {
const second = await client.channelVideos({
channel: "@GoogleDevelopers",
page_token: first.next_page_token,
});
console.log(second.videos);
}Errors, timeouts, and safe retries
REST errors use { error: { code, message } }. The SDK turns non-success HTTP responses into APIError; malformed or missing error fields fall back to a generic HTTP message and code: "http_error".
| Exported error | Fields | Meaning |
|---|---|---|
| APIError | message: string; status: number; code: string; retryAfterMs: number | null | Non-success HTTP response. 400: invalid request; 401: authentication failure; 402: insufficient credits; 429: rate limit. Other HTTP errors, including 5xx, use the same class. |
| NetworkError | message: string | Request or response-transfer failure, including rejected redirects with native fetch. No retry; credits may have been spent. |
| TimeoutError | message: string; timeoutMs: number | Per-attempt deadline exceeded while fetching or reading the body. No retry; credits may have been spent. |
| ResponseError | message: string; status: number | Successful HTTP response was invalid JSON, null, an array, or another non-object value. No retry; credits may have been spent. |
Only HTTP 429 is retried. With defaults, there are at most three total attempts. Backoff starts at 1 second and doubles, capped by maxRetryDelayMs. A valid Retry-After header (seconds or HTTP date) is a minimum wait; the client waits the larger of backoff and Retry-After. If Retry-After exceeds the maximum wait, it immediately throws instead. Missing or invalid Retry-After uses backoff.
The timeout applies separately to each attempt, not to the entire operation or backoff waits. Network failures, timeouts, invalid successful responses, and 5xx responses are never automatically retried. Do not wrap billed requests in a blind retry loop: an uncertain response does not prove that the server did not finish or charge credits.
import {
EasyTranscriber, APIError, NetworkError, TimeoutError, ResponseError,
} from "easytranscriber";
const apiKey = process.env.EASYTRANSCRIBER_API_KEY;
if (!apiKey) throw new Error("Configure EASYTRANSCRIBER_API_KEY on the server");
const client = new EasyTranscriber({ apiKey, maxRetries: 2 });
try {
const result = await client.transcribe({ url: "https://youtu.be/dQw4w9WgXcQ" });
console.log(result.credits_used);
} catch (error) {
if (error instanceof APIError) {
if (error.status === 401) console.error("Check the server API key.");
else if (error.status === 402) console.error("Check your credit balance.");
else if (error.status === 429) {
// SDK retries are exhausted or Retry-After exceeds the configured limit.
console.error("Rate limited; retry delay (ms):", error.retryAfterMs);
} else if (error.status === 400) console.error("Correct the request:", error.code);
else console.error("API request failed:", error.status, error.code);
} else if (error instanceof TimeoutError) {
console.error("Attempt timed out after", error.timeoutMs, "ms; do not blindly retry.");
} else if (error instanceof NetworkError || error instanceof ResponseError) {
console.error("Completion is uncertain; credits may already have been spent.");
} else {
throw error;
}
}Credits and service limits
Free accounts can use the API. Current request limits are Free: 10/minute, Plus: 10/minute, Pro: 50/minute, and Scale: 300/minute. Free calls still count toward request limits.
Cached transcripts cost 0 credits. A new transcript costs 1 credit, or 2 for a video longer than two hours; the maximum supported video duration is four hours. Every summary costs 1 additional credit, even when the transcript was cached. Credit balance, channel resolution, and channel latest calls are free. Search, channel search, channel videos, playlist videos, and comments cost 1 credit per requested page. Read returned credits_used where present and use credits() to retrieve the balance.
For endpoint-level validation and HTTP examples, see the REST API reference. Browse the SDK overview, the Python SDK reference, or the JavaScript SDK source on GitHub.