TypeScript SDK
The stophy package for Node.js and TypeScript: a typed client for search, video details, transcripts, comments, channels, and playlists.
Call the Stophy API from TypeScript without hand-writing fetch. The stophy package
gives you a typed Stophy class over every REST endpoint. It returns the same response
envelope as the API reference.
Install
npm install stophyWorks with Node.js 18 or later. Types ship with the package, so there is no @types install.
Initialize
Create one client and reuse it. Pass your API key explicitly and keep it in an environment variable, never in source.
import { Stophy } from "stophy";
const stophy = new Stophy({ apiKey: process.env.STOPHY_API_KEY! });Keep keys out of source code. Store them in environment variables and never commit them to git. See Authentication.
Make a request
Every method returns the standard response envelope. Read data for the result, or read
creditsRemaining, requestId, and cacheState off the top level. data is typed as
optional, so reach for it with data?. or narrow it first.
const { data } = await stophy.video({
type: "transcript",
videoUrl: "https://www.youtube.com/watch?v=D7liwdjvhWc",
});
console.log(data?.text); // full transcript as a stringvideo() is overloaded on type, so data is typed for the variant you ask for
(transcript, comments, details, replies, livechat) without casts.
The resolved value matches the response envelope:
{
success: true
requestId: string
cacheState: "hit" | "miss"
creditsUsed: number
creditsRemaining: number
warning?: string
data?: object
}Methods
Each method maps to one endpoint. Arguments mirror the request body documented in the API reference, so any field there is valid here.
| Method | Endpoint | Description |
|---|---|---|
stophy.video(body) | POST /v1/video | Details, transcript, comments, replies, or live chat (set type) |
stophy.search(body) | POST /v1/search | Search with filters for type, sort, date, duration, features |
stophy.channel(body) | POST /v1/channel | Channel metadata and content by tab |
stophy.playlist(body) | POST /v1/playlist | Playlist items, paginated |
stophy.credits() | GET /v1/credits | Current credit balance |
stophy.logs(query?) | GET /v1/logs | Recent request logs |
stophy.usage(query?) | GET /v1/usage | Daily credit and request counts |
Examples
// Search
const { data: results } = await stophy.search({
q: "react tutorial",
sortBy: "popularity",
duration: "long",
});
// Channel videos
const { data: channel } = await stophy.channel({
channelUrl: "https://www.youtube.com/@mkbhd",
tab: "video",
});
// Comments, then replies to the first comment
const comments = await stophy.video({
type: "comments",
videoUrl: "https://www.youtube.com/watch?v=D7liwdjvhWc",
sortBy: "top",
});
const replyToken = comments.data?.items[0]?.repliesToken;
if (replyToken) {
const replies = await stophy.video({ type: "replies", continuationToken: replyToken });
}
// Autocomplete
// Account
const balance = await stophy.credits();
console.log(balance.data?.credits);Pagination
Comments, replies, live chat, search, channel tabs, and playlists return a
continuationToken. Pass it back to fetch the next page, and stop when it is missing.
let token: string | undefined;
do {
const page = await stophy.video({
type: "comments",
videoUrl: "https://www.youtube.com/watch?v=D7liwdjvhWc",
continuationToken: token,
});
console.log(page.data?.items);
token = page.data?.continuationToken ?? undefined;
} while (token);Error handling
Non-2xx responses throw a StophyError carrying status, code, message, and
requestId. Branch on code so unknown codes fall through to your default case (see
Error handling).
import { Stophy, StophyError } from "stophy";
const stophy = new Stophy({ apiKey: process.env.STOPHY_API_KEY! });
try {
const { data } = await stophy.video({
type: "details",
videoUrl: "https://www.youtube.com/watch?v=D7liwdjvhWc",
});
console.log(data);
} catch (err) {
if (err instanceof StophyError) {
// err.status, err.code, err.message, err.requestId
switch (err.code) {
case "INSUFFICIENT_CREDITS":
// top up from dashboard
break;
case "CONCURRENCY_LIMITED":
// too many requests in flight; retry once one finishes
break;
case "UNAUTHORIZED":
// check your API key
break;
default:
console.error(err.status, err.code, err.message);
}
} else {
throw err;
}
}