npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@transcript1/sdk

v1.5.0

Published

TypeScript SDK for transcribing videos from YouTube, X, TikTok, Instagram, Facebook, Bluesky, Twitch, Dailymotion, Pinterest, LinkedIn and Snapchat, plus audio/video files, with transcript1.

Readme

@transcript1/sdk

TypeScript SDK for the transcript1 transcription API. Transcribe supported public video links or upload audio and video files from server code. Download transcripts as text, SRT, VTT or JSON.

Full documentation: https://transcript1.com/docs/sdk

Install

npm i @transcript1/sdk

Node 20.19+. Bun and Deno should work through their node: compatibility; the automated tests run on Node only. Use the SDK server-side only; never ship an API key to a browser.

The examples use ES modules. Save them as .mjs files, or set "type": "module" in package.json for .js files. require() also works on Node 20.19+.

API key

The client reads your API key from TRANSCRIPT1_API_KEY. You can also pass it to the constructor:

import { Transcript1 } from '@transcript1/sdk';

const t1 = new Transcript1(); // uses TRANSCRIPT1_API_KEY
const other = new Transcript1({ apiKey: 't1_...' });

Without a key the constructor throws a Transcript1Error with the code missing_api_key.

Transcribe

transcribe creates a job and waits for its transcript. It returns a completed transcription with a transcript field. Failed or canceled jobs reject.

const done = await t1.transcribe('https://youtu.be/dQw4w9WgXcQ'); // YouTube link
console.log(done.transcript.text);

await t1.transcribe('./interview.m4a', { speakers: true }); // file path
await t1.transcribe(buffer, { filename: 'call.mp3' }); // Buffer or Uint8Array
await t1.transcribe(blob, { filename: 'call.mp3' }); // Blob (a File brings its own name)
await t1.transcribe(stream, { filename: 'call.mp3', size: 4_200_000 }); // stream or async iterable

Strings starting with http:// or https:// are links; other strings are file paths. Use public video links from YouTube, X, TikTok, Instagram, Facebook, Bluesky, Twitch, Dailymotion, Pinterest, LinkedIn and Snapchat or upload audio and video files. The additional sources accept HTTPS links to completed Twitch VODs/clips, Dailymotion videos, native Pinterest video pins, LinkedIn public video posts and Snapchat Spotlight. No supplied account cookies, private/login-required content, live streams or collections. Pinterest selects its first native video block, not external embeds. See the exact accepted hosts and paths in Sources and options. URL time parameters do not trim media. Twitch, Pinterest and Snapchat use auto or stt, not captions. Keep returned media_id strings opaque, including Twitch clip and Spotlight IDs. SDK 1.1.0 and older reject Twitch, Dailymotion, Pinterest, LinkedIn and Snapchat links locally; use 1.2.0 or newer. Files can be up to 2,000,000,000 bytes, with extensions mp3, wav, m4a, flac, ogg, opus, mp4, mov, webm or mkv.

Download

download waits for the job if needed and returns a string.

const text = await t1.download(done.id); // plain text
const withTimes = await t1.download(done.id, { timestamps: true }); // text only
const srt = await t1.download(done.id, { format: 'srt' });
const vtt = await t1.download(done.id, { format: 'vtt' });
const json = await t1.download(done.id, { format: 'json' }); // the API's JSON text

timestamps: true with a format other than text rejects with invalid_input.

Background jobs

const job = await t1.transcriptions.create('./long-interview.mp4'); // returns once the job is queued
const now = await t1.transcriptions.get(job.id); // current state, at once
const finished = await t1.transcriptions.get(job.id, { wait: true }); // until it ends

get(id, { wait: true }) returns the finished object whether its status is completed, failed or canceled. Check the status before reading its transcript. Pass includeWords: true to request word timings when available.

YouTube and X, plus TikTok, Bluesky, Dailymotion and LinkedIn, use native captions when a usable track is available with source: 'auto', falling back to AI transcription. source: 'captions' is supported on these six platforms and never falls back to AI. Other platforms use AI transcription; Facebook caption retrieval remains unproven and is not enabled. Dailymotion supports direct SRT, not caption playlists. Bluesky captions require a supported trusted PDS (not arbitrary federated hosts). LinkedIn supports public native posts, not LinkedIn Learning. Speaker labels require AI transcription. Captions and AI transcription have the same per-minute price. transcript_source is youtube_captions, x_captions, tiktok_captions, bluesky_captions, dailymotion_captions, linkedin_captions or speech_to_text. caption_kind is manual, auto_generated or null. Null can mean native metadata does not establish authorship; it does not imply manual captions. Dailymotion en-auto/fr-auto tracks are auto-generated. X and the four new native caption paths have segment timing only, with words: null, and no speaker labels. source: 'stt' and speaker identification bypass captions; source: 'captions' cannot be combined with speakers or uploads.

List, delete, account

for await (const item of t1.transcriptions.list({ status: 'completed', limit: 50 })) {
  console.log(item.id, item.created_at);
}

await t1.transcriptions.delete(job.id); // cancels a running job, deletes a finished one
const account = await t1.account();

list fetches pages as you iterate. Params: status, platform, limit (page size, 1 to 100, API default 20) and signal. Filter by platform with youtube, x, tiktok, instagram, facebook, bluesky, twitch, dailymotion, pinterest, linkedin, snapchat or upload.

Options

The constructor accepts apiKey, baseUrl, fetch and maxRetries (default 2). baseUrl defaults to TRANSCRIPT1_API_URL when set, otherwise https://api.transcript1.com.

transcribe and transcriptions.create:

| Option | Meaning | | --- | --- | | speakers | label speakers | | language | auto (default) or an ISO 639-1 code | | translateTo | translate the transcript into this ISO 639-1 language (two lowercase letters) | | source | auto, captions or stt; links only | | fresh | links: skip reusing a result from the last 24 hours | | filename | required for Buffers, Blobs without a name and streams | | size | required for streams, in bytes | | idempotencyKey | defaults to a new UUID per call | | onProgress | progress callback | | signal | an AbortSignal |

download: format (text, srt, vtt, json), timestamps, translation, signal.

transcriptions.get: wait, includeWords, translation, onProgress, signal. Progress events are reported while waiting.

translation is { mode: 'only' | 'both', language: 'de' }: segments carry the translation in that language next to the original text (both) or instead of it (only). Omit it to get the original text only (the SDK sends translation=none). A language the transcription has no translation for rejects with translation_not_found (404); a malformed language is invalid_request (422). Transcription.translations lists every translation ({ language, status }), oldest first.

Returned objects keep the API's field names (snake_case).

Translate

const job = await t1.transcribe('https://youtu.be/dQw4w9WgXcQ', { translateTo: 'pl' });
console.log(job.translation); // { language: 'pl', status: 'completed' }
console.log(job.transcript.segments[0].translation);

// translate an already completed transcription later (idempotent)
const { status } = await t1.transcriptions.translate(job.id, 'de');
const german = await t1.download(job.id, { format: 'srt', translation: { mode: 'only', language: 'de' } });

translate resolves to { transcription_id, language, status }. It rejects with not_found (404), transcript_not_ready (409, the transcript is not completed yet) or invalid_request (422, bad language).

Live transcription

Stream raw PCM (16-bit, mono) and get subtitle lines back while people speak, optionally translated in real time. See Live transcription for the protocol, billing and limits.

const live = await t1.live.start({ language: 'en', translateTo: 'es', sampleRate: 16000 });

live.on('line', line => {
  // the full current state of one line: replace it by line.id
  console.log(line.id, line.text, line.translation?.text);
});

live.send(pcm); // Int16Array, Uint8Array or ArrayBuffer of 16-bit mono samples
const result = await live.end(); // { reason, transcriptionId, minutesCharged, connectedMs, audioMs }
  • start() resolves once the session has started, so no event is missed.
  • live.lines holds every line so far, in order. live.finalize() closes the current line, for example when the video pauses.
  • Other events: started, usage, warning, reconnecting, reconnected, ended and error. on() returns a function that removes the handler.
  • A dropped connection reconnects by itself within 2 minutes, with backoff from 0.5 to 8 seconds, and the last 10 seconds of audio are sent again. If end() gets no answer from the server within 30 seconds, the SDK reads the session once and resolves with what it reports, or with reason disconnected.
  • t1.live.capabilities() lists the languages and translation targets; t1.live.get(id) reads a session.
  • The live client uses the built-in WebSocket (Node 22+, Bun, Deno, browsers). On Node 20, pass one in: new Transcript1({ WebSocket }), for example from the ws package.

In a browser

Never put an API key in a browser. Use the @transcript1/sdk/live entry with a short-lived OAuth access token instead; it is asked for again before every request, so it can be refreshed:

import { createLiveClient } from '@transcript1/sdk/live';

const live = createLiveClient({ getAccessToken: async () => getOauthAccessToken() });
const session = await live.start({ translateTo: 'de' });

Progress

await t1.transcribe('./interview.m4a', {
  onProgress(event) {
    if (event.type === 'upload') {
      console.log(`${event.sent} of ${event.total} bytes`);
    } else if (event.type === 'status') {
      console.log(event.status);
    } else if (event.type === 'waiting_slot') {
      console.log(`all job slots are busy, retrying in ${event.retryAfter} s`);
    } else {
      console.log('reconnecting');
    }
  },
});

Abort

Pass an AbortSignal to set a time limit or cancel a call:

await t1.transcribe(url, { signal: AbortSignal.timeout(10 * 60_000) });

Aborting before any request rejects with aborted and no id. During an upload, the SDK tries to delete the job if it is still awaiting_upload, then rejects with aborted and the id. Aborting while waiting leaves the job running; the error carries transcriptionId, so you can resume with get(id, { wait: true }).

Errors

Catch Transcript1Error to read API, job and SDK error codes:

import { Transcript1Error } from '@transcript1/sdk';

try {
  await t1.transcribe('./call.mp3');
} catch (error) {
  if (error instanceof Transcript1Error) {
    console.error(error.code, error.message, error.transcriptionId);
  }
}

| Field | Meaning | | --- | --- | | code | API code (insufficient_minutes, not_found, ...), a job failure code (no_captions, no_speech, ...), canceled, or a local code | | status | HTTP status; absent for job and local errors | | details | extra fields of the API's error, e.g. minutes_needed, action_url | | retryable | whether trying again can help | | transcriptionId | the job, whenever one exists | | transcription | the last object seen, when there is one |

Local codes: missing_api_key, invalid_input, aborted, network_error, upload_token_refused. A non-JSON error body gives http_<status>.

A job that failed without an error object rejects with transcription_failed. If the API ever returns a completed job without its transcript, transcribe rejects with internal_error.

transcribe resolves with a CompletedTranscription: a Transcription whose status is completed and whose transcript is always present, so done.transcript.text type-checks.

Waiting and retries

While waiting for a job, the SDK retries dropped connections and HTTP 429, 500, 502, 503 and 504 responses without a retry limit. Reconnection waits increase from 2 to 4, 8, 16 and then 30 seconds; use signal to bound the total time. Other requests retry up to maxRetries times. When your plan's active-job limit is reached, transcribe and transcriptions.create wait for a slot and retry.

Plans

See plans and prices at https://transcript1.com/#pricing.

Support

[email protected]