ytranscript-api 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # ytranscript-api
2
+
3
+ Official JavaScript/TypeScript client for the [yTranscript API](https://ytranscript.com/developers) — reliable YouTube transcript extraction without IP bans, proxy management, or player-response scraping.
4
+
5
+ - **No IP bans** — the managed extraction layer absorbs YouTube's bot detection for you
6
+ - **Captionless videos work** — automatic Whisper speech-to-text fallback
7
+ - **Simple, typed** — one method, structured segments, zero dependencies
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install ytranscript-api
13
+ ```
14
+
15
+ ## Quickstart
16
+
17
+ ```ts
18
+ import { YTranscript } from "ytranscript-api";
19
+
20
+ const yt = new YTranscript("yk_live_..."); // get a key at ytranscript.com/developers
21
+
22
+ const t = await yt.transcript("dQw4w9WgXcQ"); // video ID or any YouTube URL
23
+ console.log(t.lang); // "en"
24
+ console.log(t.segments[0]); // { text, offset, duration }
25
+ console.log(t.text); // full transcript as one string
26
+
27
+ console.log(yt.lastQuota); // { limit, used, rateLimitRemaining }
28
+ ```
29
+
30
+ ### Options
31
+
32
+ ```ts
33
+ await yt.transcript("https://youtu.be/dQw4w9WgXcQ", { lang: "es" });
34
+
35
+ new YTranscript(key, { timeoutMs: 60_000 });
36
+ ```
37
+
38
+ ### Error handling
39
+
40
+ ```ts
41
+ import { YTranscriptError } from "ytranscript-api";
42
+
43
+ try {
44
+ await yt.transcript(videoId);
45
+ } catch (err) {
46
+ if (err instanceof YTranscriptError) {
47
+ err.code; // "no_transcript" | "quota_exceeded" | "rate_limited" | ...
48
+ err.status; // HTTP status
49
+ }
50
+ }
51
+ ```
52
+
53
+ ## Pricing & keys
54
+
55
+ Plans start at $29/mo for 10,000 units (captions = 1 unit, Whisper = 15 units). Get an API key at **[ytranscript.com/developers](https://ytranscript.com/developers)**.
56
+
57
+ ## License
58
+
59
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ YTranscript: () => YTranscript,
24
+ YTranscriptError: () => YTranscriptError,
25
+ default: () => index_default
26
+ });
27
+ module.exports = __toCommonJS(index_exports);
28
+ var DEFAULT_BASE_URL = "https://ytranscript.com/api/v1";
29
+ var YTranscriptError = class extends Error {
30
+ code;
31
+ status;
32
+ constructor(code, status, message) {
33
+ super(message);
34
+ this.name = "YTranscriptError";
35
+ this.code = code;
36
+ this.status = status;
37
+ }
38
+ };
39
+ var YTranscript = class {
40
+ apiKey;
41
+ baseUrl;
42
+ timeoutMs;
43
+ /** Quota headers from the most recent successful request */
44
+ lastQuota = null;
45
+ constructor(apiKey, options = {}) {
46
+ if (!apiKey || !apiKey.startsWith("yk_")) {
47
+ throw new YTranscriptError(
48
+ "unauthorized",
49
+ 0,
50
+ "An API key (yk_live_...) is required. Get one at https://ytranscript.com/developers"
51
+ );
52
+ }
53
+ this.apiKey = apiKey;
54
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
55
+ this.timeoutMs = options.timeoutMs ?? 12e4;
56
+ }
57
+ /**
58
+ * Fetch the transcript for a video. Accepts a video ID or any YouTube URL.
59
+ */
60
+ async transcript(videoIdOrUrl, options = {}) {
61
+ const params = new URLSearchParams();
62
+ if (/^[\w-]{11}$/.test(videoIdOrUrl)) {
63
+ params.set("videoId", videoIdOrUrl);
64
+ } else {
65
+ params.set("url", videoIdOrUrl);
66
+ }
67
+ if (options.lang) params.set("lang", options.lang);
68
+ let response;
69
+ try {
70
+ response = await fetch(`${this.baseUrl}/transcript?${params}`, {
71
+ headers: { Authorization: `Bearer ${this.apiKey}` },
72
+ signal: AbortSignal.timeout(this.timeoutMs)
73
+ });
74
+ } catch (err) {
75
+ throw new YTranscriptError(
76
+ "network_error",
77
+ 0,
78
+ `Request failed: ${err instanceof Error ? err.message : "unknown network error"}`
79
+ );
80
+ }
81
+ const limit = Number(response.headers.get("x-quota-limit"));
82
+ const used = Number(response.headers.get("x-quota-used"));
83
+ const rateRemaining = Number(response.headers.get("x-ratelimit-remaining"));
84
+ if (Number.isFinite(limit) && Number.isFinite(used)) {
85
+ this.lastQuota = {
86
+ limit,
87
+ used,
88
+ rateLimitRemaining: Number.isFinite(rateRemaining) ? rateRemaining : 0
89
+ };
90
+ }
91
+ const body = await response.json().catch(() => null);
92
+ if (!response.ok) {
93
+ const code = body?.error?.code ?? "fetch_failed";
94
+ const message = body?.error?.message ?? `Request failed with status ${response.status}`;
95
+ throw new YTranscriptError(code, response.status, message);
96
+ }
97
+ const segments = body.segments ?? [];
98
+ return {
99
+ videoId: body.video_id,
100
+ lang: body.lang,
101
+ whisper: Boolean(body.whisper),
102
+ cached: Boolean(body.cached),
103
+ unitsCharged: Number(body.units_charged ?? 1),
104
+ segments,
105
+ text: segments.map((s) => s.text).join(" ")
106
+ };
107
+ }
108
+ };
109
+ var index_default = YTranscript;
110
+ // Annotate the CommonJS export names for ESM import in node:
111
+ 0 && (module.exports = {
112
+ YTranscript,
113
+ YTranscriptError
114
+ });
@@ -0,0 +1,62 @@
1
+ /**
2
+ * ytranscript-api — official JavaScript/TypeScript client for the yTranscript API.
3
+ *
4
+ * Docs & API keys: https://ytranscript.com/developers
5
+ */
6
+ interface TranscriptSegment {
7
+ /** Segment text */
8
+ text: string;
9
+ /** Start offset in milliseconds */
10
+ offset: number;
11
+ /** Duration in milliseconds */
12
+ duration: number;
13
+ }
14
+ interface Transcript {
15
+ videoId: string;
16
+ /** BCP-47-ish language code of the returned transcript */
17
+ lang: string;
18
+ /** True when the transcript was produced by Whisper speech-to-text (captionless video) */
19
+ whisper: boolean;
20
+ /** True when served from cache */
21
+ cached: boolean;
22
+ /** Quota units this request consumed (captions: 1, Whisper: 15) */
23
+ unitsCharged: number;
24
+ segments: TranscriptSegment[];
25
+ /** Convenience: all segment text joined with spaces */
26
+ text: string;
27
+ }
28
+ interface QuotaInfo {
29
+ limit: number;
30
+ used: number;
31
+ rateLimitRemaining: number;
32
+ }
33
+ type ErrorCode = "unauthorized" | "quota_exceeded" | "rate_limited" | "bad_request" | "no_transcript" | "fetch_failed" | "network_error";
34
+ declare class YTranscriptError extends Error {
35
+ readonly code: ErrorCode;
36
+ readonly status: number;
37
+ constructor(code: ErrorCode, status: number, message: string);
38
+ }
39
+ interface ClientOptions {
40
+ /** Override the API base URL (rarely needed) */
41
+ baseUrl?: string;
42
+ /** Request timeout in milliseconds (default 120s — Whisper fallbacks take time) */
43
+ timeoutMs?: number;
44
+ }
45
+ interface TranscriptOptions {
46
+ /** Preferred language code (e.g. "en", "es"). Defaults to the video's source language. */
47
+ lang?: string;
48
+ }
49
+ declare class YTranscript {
50
+ private readonly apiKey;
51
+ private readonly baseUrl;
52
+ private readonly timeoutMs;
53
+ /** Quota headers from the most recent successful request */
54
+ lastQuota: QuotaInfo | null;
55
+ constructor(apiKey: string, options?: ClientOptions);
56
+ /**
57
+ * Fetch the transcript for a video. Accepts a video ID or any YouTube URL.
58
+ */
59
+ transcript(videoIdOrUrl: string, options?: TranscriptOptions): Promise<Transcript>;
60
+ }
61
+
62
+ export { type ClientOptions, type ErrorCode, type QuotaInfo, type Transcript, type TranscriptOptions, type TranscriptSegment, YTranscript, YTranscriptError, YTranscript as default };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * ytranscript-api — official JavaScript/TypeScript client for the yTranscript API.
3
+ *
4
+ * Docs & API keys: https://ytranscript.com/developers
5
+ */
6
+ interface TranscriptSegment {
7
+ /** Segment text */
8
+ text: string;
9
+ /** Start offset in milliseconds */
10
+ offset: number;
11
+ /** Duration in milliseconds */
12
+ duration: number;
13
+ }
14
+ interface Transcript {
15
+ videoId: string;
16
+ /** BCP-47-ish language code of the returned transcript */
17
+ lang: string;
18
+ /** True when the transcript was produced by Whisper speech-to-text (captionless video) */
19
+ whisper: boolean;
20
+ /** True when served from cache */
21
+ cached: boolean;
22
+ /** Quota units this request consumed (captions: 1, Whisper: 15) */
23
+ unitsCharged: number;
24
+ segments: TranscriptSegment[];
25
+ /** Convenience: all segment text joined with spaces */
26
+ text: string;
27
+ }
28
+ interface QuotaInfo {
29
+ limit: number;
30
+ used: number;
31
+ rateLimitRemaining: number;
32
+ }
33
+ type ErrorCode = "unauthorized" | "quota_exceeded" | "rate_limited" | "bad_request" | "no_transcript" | "fetch_failed" | "network_error";
34
+ declare class YTranscriptError extends Error {
35
+ readonly code: ErrorCode;
36
+ readonly status: number;
37
+ constructor(code: ErrorCode, status: number, message: string);
38
+ }
39
+ interface ClientOptions {
40
+ /** Override the API base URL (rarely needed) */
41
+ baseUrl?: string;
42
+ /** Request timeout in milliseconds (default 120s — Whisper fallbacks take time) */
43
+ timeoutMs?: number;
44
+ }
45
+ interface TranscriptOptions {
46
+ /** Preferred language code (e.g. "en", "es"). Defaults to the video's source language. */
47
+ lang?: string;
48
+ }
49
+ declare class YTranscript {
50
+ private readonly apiKey;
51
+ private readonly baseUrl;
52
+ private readonly timeoutMs;
53
+ /** Quota headers from the most recent successful request */
54
+ lastQuota: QuotaInfo | null;
55
+ constructor(apiKey: string, options?: ClientOptions);
56
+ /**
57
+ * Fetch the transcript for a video. Accepts a video ID or any YouTube URL.
58
+ */
59
+ transcript(videoIdOrUrl: string, options?: TranscriptOptions): Promise<Transcript>;
60
+ }
61
+
62
+ export { type ClientOptions, type ErrorCode, type QuotaInfo, type Transcript, type TranscriptOptions, type TranscriptSegment, YTranscript, YTranscriptError, YTranscript as default };
package/dist/index.js ADDED
@@ -0,0 +1,88 @@
1
+ // src/index.ts
2
+ var DEFAULT_BASE_URL = "https://ytranscript.com/api/v1";
3
+ var YTranscriptError = class extends Error {
4
+ code;
5
+ status;
6
+ constructor(code, status, message) {
7
+ super(message);
8
+ this.name = "YTranscriptError";
9
+ this.code = code;
10
+ this.status = status;
11
+ }
12
+ };
13
+ var YTranscript = class {
14
+ apiKey;
15
+ baseUrl;
16
+ timeoutMs;
17
+ /** Quota headers from the most recent successful request */
18
+ lastQuota = null;
19
+ constructor(apiKey, options = {}) {
20
+ if (!apiKey || !apiKey.startsWith("yk_")) {
21
+ throw new YTranscriptError(
22
+ "unauthorized",
23
+ 0,
24
+ "An API key (yk_live_...) is required. Get one at https://ytranscript.com/developers"
25
+ );
26
+ }
27
+ this.apiKey = apiKey;
28
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
29
+ this.timeoutMs = options.timeoutMs ?? 12e4;
30
+ }
31
+ /**
32
+ * Fetch the transcript for a video. Accepts a video ID or any YouTube URL.
33
+ */
34
+ async transcript(videoIdOrUrl, options = {}) {
35
+ const params = new URLSearchParams();
36
+ if (/^[\w-]{11}$/.test(videoIdOrUrl)) {
37
+ params.set("videoId", videoIdOrUrl);
38
+ } else {
39
+ params.set("url", videoIdOrUrl);
40
+ }
41
+ if (options.lang) params.set("lang", options.lang);
42
+ let response;
43
+ try {
44
+ response = await fetch(`${this.baseUrl}/transcript?${params}`, {
45
+ headers: { Authorization: `Bearer ${this.apiKey}` },
46
+ signal: AbortSignal.timeout(this.timeoutMs)
47
+ });
48
+ } catch (err) {
49
+ throw new YTranscriptError(
50
+ "network_error",
51
+ 0,
52
+ `Request failed: ${err instanceof Error ? err.message : "unknown network error"}`
53
+ );
54
+ }
55
+ const limit = Number(response.headers.get("x-quota-limit"));
56
+ const used = Number(response.headers.get("x-quota-used"));
57
+ const rateRemaining = Number(response.headers.get("x-ratelimit-remaining"));
58
+ if (Number.isFinite(limit) && Number.isFinite(used)) {
59
+ this.lastQuota = {
60
+ limit,
61
+ used,
62
+ rateLimitRemaining: Number.isFinite(rateRemaining) ? rateRemaining : 0
63
+ };
64
+ }
65
+ const body = await response.json().catch(() => null);
66
+ if (!response.ok) {
67
+ const code = body?.error?.code ?? "fetch_failed";
68
+ const message = body?.error?.message ?? `Request failed with status ${response.status}`;
69
+ throw new YTranscriptError(code, response.status, message);
70
+ }
71
+ const segments = body.segments ?? [];
72
+ return {
73
+ videoId: body.video_id,
74
+ lang: body.lang,
75
+ whisper: Boolean(body.whisper),
76
+ cached: Boolean(body.cached),
77
+ unitsCharged: Number(body.units_charged ?? 1),
78
+ segments,
79
+ text: segments.map((s) => s.text).join(" ")
80
+ };
81
+ }
82
+ };
83
+ var index_default = YTranscript;
84
+ export {
85
+ YTranscript,
86
+ YTranscriptError,
87
+ index_default as default
88
+ };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "ytranscript-api",
3
+ "version": "0.1.0",
4
+ "description": "Reliable YouTube transcript extraction API client — no IP bans, no proxy management, Whisper fallback for captionless videos.",
5
+ "keywords": [
6
+ "youtube",
7
+ "transcript",
8
+ "youtube transcript api",
9
+ "captions",
10
+ "subtitles",
11
+ "whisper",
12
+ "speech-to-text"
13
+ ],
14
+ "homepage": "https://ytranscript.com/developers",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/nnnoel/ytranscript.git",
18
+ "directory": "sdk/js"
19
+ },
20
+ "license": "MIT",
21
+ "author": "yTranscript <support@ytranscript.com>",
22
+ "type": "module",
23
+ "main": "./dist/index.cjs",
24
+ "module": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js",
30
+ "require": "./dist/index.cjs"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "README.md"
36
+ ],
37
+ "scripts": {
38
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
39
+ "prepublishOnly": "npm run build"
40
+ },
41
+ "devDependencies": {
42
+ "tsup": "^8.0.0",
43
+ "typescript": "^5.4.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=18"
47
+ }
48
+ }