JavaScript / TypeScript SDK

The official JavaScript and TypeScript client for the Speech Revolutions STT API. Every method returns a promise. The SDK runs on Node 18+ and uses native fetch.

Source on GitHub · report an issue · npm

Install

bash
npm install speechrevolutions

Quickstart

transcribe() accepts a local path, a URL, raw bytes, or a Blob. With the default outputType: "json", the SDK parses the response into a result object.

transcribe.ts
import { SpeechRevolutions } from "speechrevolutions";

const client = new SpeechRevolutions(); // SPEECHREVOLUTIONS_API_KEY
const result = await client.transcribe("meeting.mp3", { speakerLabels: true });

console.log(result.text);
for (const u of result.utterances) {
  console.log(`${u.speaker}: ${u.text}`);
}

From a URL or file

transcribe() detects http(s) URLs automatically. Use the transcribeUrl() alias to make the source explicit.

ts
// auto-detected
const result = await client.transcribe("https://example.com/audio.mp3");

// explicit alias
const result2 = await client.transcribeUrl("https://example.com/audio.mp3");

Options

Pass options as the second argument. diarize is an alias for speakerLabels.

ts
await client.transcribe("a.mp3", {
  diarize: true,        // alias for speakerLabels
  outputType: "srt",
  wordTimestamps: true,
  customVocabulary: ["AcmeCorp"],
});
OptionTypeDefault
outputType"txt" | "json" | "srt" | "vtt" | "docx" | "pdf""json"
wordTimestampsbooleantrue
speakerLabelsbooleantrue
diarizeboolean (alias for speakerLabels)—
nltkboolean (punctuation & capitalization)true
tier"standard""standard"
customVocabularystring[]undefined
languagestringundefined
onProgress(event: ProgressEvent) => voidundefined
onUploadProgress(event: ProgressEvent) => voidundefined
progressboolean (render console bars)false

tier: standard is the only tier currently available.

language: an ISO 639-1 code (e.g. "en") skips language detection and transcribes the whole file in that language. Leave it unset to detect the language automatically. See pinning the language.

Live progress

The SDK reports progress for both the file upload and the transcription, as a console bar, a callback, or both. When you enable both, the bars render and your callbacks fire for every event. AssemblyAI and Deepgram expose no percentage for pre-recorded audio.

Files under about 3 MiB may report no transcription progress before they complete. Expect the bar to jump from the end of the upload to done.

ts
// 1. Console bars — an "Uploading" byte bar, then a "Transcribing" bar,
//    rendered to stderr on a single carriage-return-updated line.
await client.transcribe("meeting.mp3", { progress: true });

// 2. Programmatic — read event.percent (0-100, undefined until total is known)
await client.transcribe("meeting.mp3", {
  onProgress(event) {
    // transcription: event.step is e.g. "transcribe"
    console.log(event.percent, event.step);
  },
  onUploadProgress(event) {
    // upload: event.step === "upload"; completed / total are bytes
    console.log("upload", event.percent);
  },
});

// onProgress may also be passed as a 3rd positional argument.

Each ProgressEvent carries completed, total, step, and a computed percent (0–100, or undefined when the total is not yet known).

For a web UI, see Live progress for web apps. It combines both callbacks into a single 0–100 bar for your frontend.

Result shape

With outputType: "json", the SDK returns a Transcript object:

MemberDescription
result.textFull transcript text
result.transcriptAlias for result.text
result.wordsWords with start, end, and speaker
result.utterancesSpeaker turns
result.toDeepgram()Deepgram-shaped object, for migrations
result.toDict()Normalized JSON object
result.content / result.save(path)Raw bytes / write the output to disk
ts
const dg = result.toDeepgram();
console.log(dg.results.channels[0].alternatives[0].transcript);

// save() appends the output type when the path has no extension.
// Returns the written path.
const path = await result.save("output"); // -> "output.json"

Webhooks

Pass callbackUrl to get notified when a job finishes instead of holding the call open. Use this for server and background workloads. When the job completes or fails permanently, Speech Revolutions sends a signed JSON POST to your URL:

ts
await client.transcribe("meeting.mp3", {
  callbackUrl: "https://you.example.com/hook",
});

The POST body is { job_id, status: "completed"|"failed", download_url?, step?, reason? }. It is signed with HMAC-SHA256 over the raw body, in the X-SR-Signature: sha256=<hex> header. The request also carries X-SR-Event (the status) and a unique X-SR-Delivery ID. Verify the signature against the raw body bytes, and use a constant-time comparison. Copy your signing secret from the console (API Keys → Webhook signing secret) into SPEECHREVOLUTIONS_WEBHOOK_SECRET. The webhooks guide covers the payload, retries, and secret rotation.

webhooks.ts
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifySignature(rawBody, signatureHeader, signingSecret) {
  const expected =
    "sha256=" + createHmac("sha256", signingSecret).update(rawBody).digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(signatureHeader ?? "");
  return a.length === b.length && timingSafeEqual(a, b);
}

// Express handler — capture the RAW body for verification.
// app.post(
//   "/webhooks/speechrevolutions",
//   express.raw({ type: "application/json" }),
//   (req, res) => {
//     if (!verifySignature(req.body, req.get("X-SR-Signature"), SECRET))
//       return res.status(401).send("bad signature");
//     const event = JSON.parse(req.body.toString());
//     if (event.status === "completed") {
//       // mark done; fetch event.download_url
//     } else {
//       // event.step, event.reason
//     }
//     res.json({ ok: true }); // a 2xx acks delivery (we retry on 5xx)
//   },
// );

Retrieve results later

Fetch a job by ID at any time, for example after a webhook arrives or after your service restarts. The download URL is generated on each request, so you can fetch results long after the original upload.

retrieve.ts
// Get a job's current status ("processing" | "completed" | "failed")
const status = await client.getJobStatus(jobId);
console.log(status.status);

if (status.status === "completed") {
  const result = await client.getTranscript(jobId); // downloads + parses
  console.log(result.text);
} else if (status.status === "failed") {
  console.log(status.failedStage, status.reason);
}

// List most-recent jobs (newest first), cursor-paginated
const page = await client.listJobs({ limit: 50 });
console.log(page.jobs, page.nextBefore);
for (const job of page.jobs) console.log(job.jobId, job.createdAt);

Robustness

Configure retries, backoff, and low-level request options on the client. The SDK retries 429, 5xx, and network errors automatically and honors the Retry-After header.

ts
import { ProxyAgent } from "undici";

// Any fetch option can be passed through; a dispatcher is how undici proxies.
const dispatcher = new ProxyAgent("http://proxy.internal:8080");

const client = new SpeechRevolutions({
  maxRetries: 3,
  retryBackoffMs: 500,  // exponential
  requestInit: { dispatcher },
});

Errors are typed. Each carries a .statusCode and the server's .requestId. Include the request ID when you contact support.

ts
import { RateLimitError, AuthenticationError } from "speechrevolutions";

try {
  const result = await client.transcribe("meeting.mp3");
} catch (err) {
  if (err instanceof RateLimitError) {
    console.log(err.statusCode, err.requestId); // e.g. 429 "req_..."
  }
}

Authentication

The SDK reads your API key from the environment:

bash
export SPEECHREVOLUTIONS_API_KEY=stt_...

Or pass it explicitly:

ts
const client = new SpeechRevolutions({ apiKey: "stt_..." });
// or the shorthand
const client2 = new SpeechRevolutions("stt_...");

The SDK streams the upload in chunks. It then listens on the SSE job stream and converts the completed and total counts into percent.