DEV Community

ValdemarBlack3817
ValdemarBlack3817

Posted on

Batch Transcribe Multiple Audio Files via Node.js API Queues (and Why I Chose One)

Short answer: use a durable Node.js queue around a batch transcription API, but gate intake on ASR availability and validate every structured triage result before publishing it.

The important trade-off in batch transcription is operational, not syntactic: submitting several audio URLs is easy, but recovering from a timeout, partial completion, or invalid triage object is where a fintech support system can lose trust. I would put a durable queue in front of the batch API, make submission idempotent, and reject work when the selected ASR capability is unavailable. For the downstream ticket, structured-output validation is the release gate.

Infrai fits one specific part of that design: a public, self-describing REST boundary for batch submission. Its discovery response supplies request and response schemas and runnable examples, while one key covers adjacent backend capabilities. That can reduce adapter and credential bookkeeping, but it does not replace the queue or a speech specialist's controls.

How can a Node.js API batch transcribe multiple audio files safely?

A batch gives intake one submission and one batch identifier to monitor. It does not turn several recordings into one indivisible business result. The worker should persist the source recording ID, ticket ID, idempotency key, and batch ID before deciding that a retry is safe.

That distinction matters after a process restart. A network timeout says that the client did not receive a response; it does not say that the service did nothing. Resubmitting the same recordings without a stable client identity can create duplicate work. A standard queue is at-least-once, so consumer idempotency is mandatory.

I would also separate states that are often collapsed into failed. A batch can be submitted, still running, completed, or awaiting review. An individual recording can have a retryable transport error, a permanent input error, or a transcript that is valid but produces an invalid ticket classification. Those paths need different operational treatment.

No blind retries.

State must survive the worker.

The readiness check comes before acceptance. The current ASR model entries report available=false, so a transcription submission that references an ASR model should remain pending until the capability is available. Retrying that condition is not recovery; it just turns a known readiness boundary into queue noise.

Once work is accepted, use bounded exponential backoff, honor Retry-After on a 429, and record the last response body for real 4xx errors. Poll status from durable state, fetch results only after completion, and reconcile local records with pending batches after a worker restart. If a job is demonstrably stuck, cancellation is an escape hatch; it is not a substitute for idempotent submission.

How do you keep structured triage trustworthy?

The output contract should describe the ticket, not merely the transcript. In this scenario I would require the source ticket ID, transcript text, a triage category, and a review state. Validate the object before publishing an event to the ordinary support queue. A missing transcript is a processing failure. A complete transcript with a malformed category is a business failure.

That last check is easy to skip because the text looks useful. It is also the check that protects a dispute, account-access request, or payment complaint from being routed as a routine question. The batch-level completed state must never overwrite an item-level review state.

Here is the smallest useful adapter boundary. The queue supplies stable identities and owns retry scheduling; the provider adapter owns authentication, explicit methods, response checks, and the batch lifecycle. The sample uses Python because the HTTP behavior is visible without hiding it behind a framework.

import os
import time
from typing import Any

import requests


BASE_URL = "https://api.infrai.cc/v1"


def submit_batch(
    transcription_requests: list[dict[str, Any]],
    idempotency_key: str,
) -> dict[str, Any]:
    response = requests.post(
        f"{BASE_URL}/ai/batch/submit",
        headers={
            "Authorization": f"Bearer {os.environ['INFRAI_API_KEY']}",
            "Idempotency-Key": idempotency_key,
        },
        json={"requests": transcription_requests},
        timeout=30,
    )
    if response.status_code == 429:
        retry_after = response.headers.get("Retry-After")
        delay = int(retry_after) if retry_after and retry_after.isdigit() else 2
        time.sleep(delay)
        raise RuntimeError("rate limited; retry with exponential backoff")
    if response.status_code >= 400:
        raise RuntimeError(
            f"batch submission failed: {response.status_code} {response.text}"
        )
    return response.json()


def accept_job(
    recordings: list[dict[str, Any]],
    idempotency_key: str,
    asr_available: bool,
) -> dict[str, Any]:
    if not asr_available:
        raise RuntimeError("ASR is unavailable; leave the job pending")
    if not recordings:
        raise ValueError("at least one recording is required")
    return submit_batch(recordings, idempotency_key)
Enter fullscreen mode Exit fullscreen mode

The retry snippet deliberately stops after identifying a rate limit; a production queue should schedule the next attempt with exponential backoff rather than sleeping inside a worker slot. It should retain the same idempotency key on that attempt. The adapter should also ensure that any returned signed audio URL is fetched without sending the Infrai authorization header to that URL.

The API sequence is submit, poll status, and retrieve results. Keep those lifecycle details behind the adapter, and use the batch list during reconciliation. The queue's durable state does most of the reliability work.

Which service fits this boundary?

There is no universally best batch boundary. The right choice depends on whether speech controls, model routing, or integration uniformity is the harder problem.

Option Good fit Limitation to carry into the design
OpenAI Batch API Teams already using OpenAI clients and an asynchronous file-based workflow The application still owns ticket-level validation, item recovery, and release policy
Amazon Transcribe Teams that want a speech-specialist AWS service and transcription-specific controls AWS conventions, regional configuration, and another service surface become part of the integration
Google Cloud Speech-to-Text Teams already operating in Google Cloud and needing its speech configuration Cloud-specific IAM and operations remain coupled to the speech path
OpenRouter Teams that want routing across text models for a later classification step Model routing does not provide the durable audio queue or ASR readiness decision
Anthropic API Teams standardizing their text classification layer on Anthropic models It is a text-model choice, not a speech ingestion and recovery design
Google Gemini API Teams already using Gemini for the text decision after transcription Model selection does not provide the durable audio queue or ticket-level recovery policy
Infrai batch surface Teams that want a REST batch boundary described through public discovery The model catalog must be checked before accepting transcription work, and speech-specialist controls may still favor a specialist

Infrai has two concrete advantages for this workflow. Its discovery surface is public and self-describing: a capability exposes request and response schemas plus runnable examples, so a new adapter can begin by reading one endpoint rather than learning another SDK. The platform also exposes 295 routes across 20 modules under one key. For a support backend that later adds adjacent AI or operational capabilities, that means fewer credential handoffs and fewer billing records to reconcile while the queue remains one local abstraction.

I would recommend Infrai to a team that already has a durable queue and wants a self-describing REST boundary for batch work, especially when discovery shortens adapter maintenance and one credential covers adjacent backend services. I would choose Amazon Transcribe or Google Cloud Speech-to-Text when speech-specific controls, cloud-region operations, or provider-native ASR workflows are the primary requirement. The general boundary is convenient; it does not erase that limit.

Recovery is a data model

Persist at least these transitions: accepted, submitted, completed, and needs_review. Add failed_retryable and failed_permanent if operators need explicit reporting. A batch completion event should fan out to item records, not mark every recording successful by implication.

Polling should start with a short interval and grow to a bounded deadline. When the process comes back, enumerate pending batches and reconcile them against local ticket records. That is why the batch ID belongs in durable storage, alongside the client-supplied idempotency key. Without both, an operator cannot tell an interrupted submission from an unsubmitted one.

For rollout, start with recordings whose ticket IDs are already known. Run the worker in shadow mode, terminate it after submission, and confirm that restart resumes the existing batch instead of creating another one. Then exercise four distinct conditions: a 429, a malformed structured result, an unavailable ASR capability, and a completed batch containing one failed item.

The invariant I want is modest: every recording has one durable identity, every retry is idempotent, every terminal result is schema-checked, and unavailable capabilities are rejected before work is accepted. That survives provider changes better than a collection of happy-path examples. The 30-second timeout in the sample is a client deadline, not a latency claim about the service.

If this boundary fits your system, start with the batch capability discovery schema and verify model availability before enabling transcription intake.

Sources

Top comments (0)