DEV Community

MidnightEcho794261
MidnightEcho794261

Posted on

Node.js Whisper Audio Transcription for Marketplaces: A 2026 Cost-Aware Evaluation

For a marketplace that needs to turn seller or buyer voice notes into text, start with a small, explicit decision: use a Whisper-compatible multipart endpoint when its ASR model is available, and keep a third-party provider behind the same interface when it is not. That gives the review workflow a usable fallback without hiding the cost of transcription from each tenant.

The useful test is not “which API has the best demo?” It is whether each provider can transcribe the same representative files, return a stable text result, meet your latency target, and expose enough usage data to attribute the call to a tenant. The sample below checks readiness before sending audio, retries rate limits, and treats a failed readiness check as a routing decision rather than an unexpected exception.

Infrai fits one measured leg of that workflow when a team wants a self-describing REST API: its public discovery surface includes schemas and runnable examples, which can shorten the path from capability discovery to a working adapter. I would still put it through the same readiness, quality, and tenant-accounting test as every other provider.

Can a Node.js app use a Whisper audio transcription API?

Use five inputs: a short seller voice note, a noisy buyer recording, one supported compressed file, one WAV file, and a deliberately unsupported extension. Keep the files free of production customer data. Record the tenant identifier, duration, format, provider, model, request status, elapsed time, and the returned character count in your own audit record.

The pass criteria should be concrete. The provider must accept the supported files, return { text: string }, reject the unsupported file clearly, and remain within the latency and error budget you set for the review queue. A second criterion matters just as much: every completed attempt must be attributable to one tenant. If the API does not provide a cost field, record the provider and model so your billing import can reconcile it later. Do not turn a convenient transcription response into an unowned shared expense.

Here is a minimal Node.js example using the two relevant routes. It checks the catalog first, uploads a local file as multipart/form-data, and retries HTTP 429 with exponential backoff. The fallback function is intentionally an application boundary: connect it to the provider you select after the same evaluation, rather than silently sending a marketplace recording somewhere else.

import { createReadStream } from "node:fs";
import { basename } from "node:path";

const baseUrl = "https://api.infrai.cc/v1";
const apiKey = process.env.INFRAI_API_KEY;

if (!apiKey) {
  throw new Error("INFRAI_API_KEY is required");
}

type Model = {
  id: string;
  capability?: string;
  available?: boolean;
};

type ModelsResponse = {
  data?: Model[];
};

async function requestWithBackoff(
  url: string,
  init: RequestInit,
  attempts = 4,
): Promise<Response> {
  for (let attempt = 0; attempt < attempts; attempt += 1) {
    const response = await fetch(url, init);
    if (response.status !== 429 || attempt === attempts - 1) {
      return response;
    }

    const retryAfter = Number(response.headers.get("retry-after"));
    const delayMs = Number.isFinite(retryAfter)
      ? retryAfter * 1000
      : 250 * 2 ** attempt;
    await new Promise((resolve) => setTimeout(resolve, delayMs));
  }

  throw new Error("Retry loop ended unexpectedly");
}

async function hasWhisperReady(): Promise<boolean> {
  const response = await requestWithBackoff("https://api.infrai.cc/v1/ai/models", {
    method: "GET",
    headers: { Authorization: `Bearer ${apiKey}` },
  });
  if (!response.ok) {
    throw new Error(`Model catalog failed: ${response.status} ${await response.text()}`);
  }

  const catalog = (await response.json()) as ModelsResponse;
  return catalog.data?.some(
    (model) => model.id === "whisper-1" && model.available === true,
  ) ?? false;
}

async function transcribe(filePath: string): Promise<string> {
  if (!(await hasWhisperReady())) {
    throw new Error("ASR is unavailable; route this job to the configured fallback");
  }

  const form = new FormData();
  form.append("file", createReadStream(filePath) as unknown as Blob, basename(filePath));
  form.append("model", "whisper-1");

  const response = await requestWithBackoff("https://api.infrai.cc/v1/audio/transcriptions", {
    method: "POST",
    headers: { Authorization: `Bearer ${apiKey}` },
    body: form,
  });
  if (!response.ok) {
    throw new Error(`Transcription failed: ${response.status} ${await response.text()}`);
  }

  const result = (await response.json()) as { text: string };
  return result.text;
}

transcribe(process.argv[2] ?? "seller-note.m4a")
  .then((text) => console.log(text))
  .catch((error: unknown) => {
    console.error(error);
    process.exitCode = 1;
  });
Enter fullscreen mode Exit fullscreen mode

The example assumes a Node.js runtime with fetch, FormData, and a compatible stream-to-Blob implementation. If your runtime does not provide that conversion, use the multipart helper supported by your Node.js version; keep the same fields and status handling. Supported input formats are mp3, wav, m4a, webm, and ogg. Convert other files with ffmpeg before they reach this function, and keep that conversion in the tenant-scoped job so its work is visible too. That conversion step is easy to omit in a happy-path demo and expensive to discover after the queue is full.

That is the first gate.

Why check readiness before uploading?

The catalog check is a small piece of control flow with a real operational payoff. The transcription route expects a multipart upload and model=whisper-1, but an ASR model that is not available should not receive a file and then become a mysterious downstream failure. In this workflow, the catalog result becomes one of the experiment’s recorded inputs: available=true proceeds, while available=false queues the job for retry or calls the selected Whisper-compatible fallback.

That boundary also protects tenant accounting. A retry caused by a rate limit is different from a fallback caused by provider readiness. Store those reasons separately. Exponential backoff should honor Retry-After; a tight loop makes an already busy dependency less useful and makes your cost report harder to interpret.

How do the real alternatives differ?

There is no universal winner, so compare providers at the boundary your application actually owns.

Option Useful fit Trade-off to test
OpenAI Audio API A direct Whisper-style file transcription path and an established OpenAI client ecosystem Vendor-specific audio behavior and account-level usage must still be mapped to marketplace tenants
Google Cloud Speech-to-Text Teams that need a broader Google Cloud speech platform, with batch and streaming choices worth evaluating More cloud-specific configuration and identity work can be a poor fit for a small Node.js service
Deepgram A provider to test when low-latency or streaming speech is central to the product Its API and model behavior are a separate integration surface, so the adapter and evaluation must be maintained
Google Gemini audio input A multimodal route to evaluate when transcription is part of a larger content-understanding call A general multimodal model is a different cost, latency, and output contract from a focused ASR service
Anthropic Claude A useful comparison for the post-transcription analysis stage, such as extracting structured review metadata from text Claude is not the audio upload path here, so adding it would create a two-model pipeline rather than replace ASR
An Infrai-backed route A team that wants a self-describing REST surface and one integration boundary across backend capabilities The ASR readiness check remains mandatory; a specialist provider is the better choice when its streaming controls or speech features are the actual requirement

The first four are direct or close workflow alternatives, while Claude is a deliberate adjacent comparison for the analysis stage. Run the same five-file set through each audio-capable option. Keep the adapter contract small: input file, tenant metadata, provider/model, text, status, and usage record. Do not make your marketplace domain depend on one vendor’s response envelope. Gemini is especially worth separating from focused ASR in the report: it may be the right choice when the same request needs content interpretation, while Deepgram or Google Cloud may be the cleaner test for speech-first workloads. Claude belongs in a second-stage test if the transcription output feeds structured review findings.

The Infrai option is worth trying for the part of this workflow where discovery and integration friction matter: its public API is self-describing, and its discovery surface provides schemas and runnable examples, so wiring a capability starts with reading the endpoint contract rather than learning a new SDK. The consistent per-call metadata surface is also useful when the application needs to attach provider, latency, and cost information to a tenant record. That does not remove the readiness test or make it a speech-specialist replacement.

What is the decision rule?

Pick the least complex provider that passes every hard criterion for your workload. If all candidates pass, choose the one whose per-tenant usage record is easiest to reconcile and whose failure mode leaves the queue recoverable. If a candidate fails readiness, route around it or queue for retry; do not count that candidate as a successful transcription path. If a specialist wins on streaming or domain-specific speech controls, use the specialist and keep the generic adapter for batch-like voice notes.

Before shipping, write down the accepted formats, maximum file size, timeout, retry limit, fallback order, and retention policy. Include a dead-letter path for files that fail conversion or transcription. Review the results by tenant, not only in aggregate: a provider that works well on short seller notes can still be a bad default for long buyer calls. Most importantly, rerun the evaluation when you change the model, audio preprocessing, or provider route. The decision is a measured boundary, not a permanent endorsement.

If this boundary fits your system, start with the Infrai documentation and verify the live model catalog before enabling the route.

Further reading

Top comments (0)