JavaScript / TypeScript SDK
Official JS/TS client for the Speech Revolutions STT API. Async-first (like the Deepgram / ElevenLabs JS clients) and runs on Node 18+ using native fetch.
Source on GitHub · report an issue · npm
Install
npm install speechrevolutionsQuickstart
transcribe() accepts a local path, a URL, raw bytes, or a Blob. With the default outputType: "json" the SDK parses the response into a transcript-first result object.
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(`Speaker ${u.speaker}: ${u.text}`);
}From a URL or file
transcribe() auto-detects http(s) URLs; the transcribeUrl() alias makes intent explicit.
// 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 a Deepgram-compatible alias for speakerLabels.
await client.transcribe("a.mp3", {
diarize: true, // alias for speakerLabels
outputType: "srt",
wordTimestamps: true,
customVocabulary: ["AcmeCorp"],
});| Option | Type | Default |
|---|---|---|
outputType | "txt" | "json" | "srt" | "vtt" | "docx" | "pdf" | "json" |
wordTimestamps | boolean | true |
speakerLabels | boolean | true |
diarize | boolean (alias for speakerLabels) | — |
nltk | boolean (punctuation & capitalization) | true |
tier | "standard" | "standard" |
customVocabulary | string[] | undefined |
onProgress | (event: ProgressEvent) => void | undefined |
onUploadProgress | (event: ProgressEvent) => void | undefined |
progress | boolean (render console bars) | false |
tier: standard is the only tier currently available.
Live progress
Unlike AssemblyAI and Deepgram — which expose no percentage for pre-recorded audio — you get real-time progress for both the file upload and the transcription, as a console bar, a callback, or both. They compose: the bars render and your callbacks still fire for every event.
// 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, undefined when the total is not yet known).
Building a UI? Live progress for web apps shows how to fold both callbacks into a single 0–100 bar you can serve to your frontend.
Result shape
With outputType: "json" the SDK returns a transcript-first object:
| Member | Description |
|---|---|
result.text | Full transcript (AssemblyAI / ElevenLabs style) |
result.transcript | Deepgram-style alias |
result.words | Word + start / end / speaker |
result.utterances | AssemblyAI-style speaker turns |
result.toDeepgram() | Deepgram-shaped object |
result.toDict() | Normalized JSON |
result.content / result.save(path) | Raw bytes / write to disk |
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 be notified when a job finishes instead of holding the call open — the right pattern for server and background workloads. On completion or permanent failure the platform POSTs a signed JSON body to your URL:
await client.transcribe("meeting.mp3", {
callbackUrl: "https://you.example.com/hook",
});The POST body is { job_id, status: "completed"|"failed", download_url?, step?, reason? }, signed with HMAC-SHA256 over the raw body in the X-SR-Signature: sha256=<hex> header (plus X-SR-Event with the status and a unique X-SR-Delivery id). Verify against the raw body bytes with a constant-time comparison.
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 anytime — useful after a webhook, or when rebuilding state after a restart. The download URL is regenerated on demand, so results are fetchable long after the original upload.
// 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. Transient 429/5xx/network errors are retried automatically (honoring the Retry-After header).
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 and carry a .statusCode and the server .requestId for correlating with support.
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_..."
}
}Auth
The SDK reads the key from either environment variable:
export SPEECHREVOLUTIONS_API_KEY=stt_...Or pass it explicitly:
const client = new SpeechRevolutions({ apiKey: "stt_..." });
// or the shorthand
const client2 = new SpeechRevolutions("stt_...");Under the hood
The upload is streamed in chunks with an explicit Content-Length (so presigned S3 PUTs never see Transfer-Encoding: chunked), and the SDK converts the server's SSE completed/total counts into percent for you.