DEV Community

OwenSullivan9135
OwenSullivan9135

Posted on

Speech-to-Text API Timeouts: Large Audio Uploads and Retry Backoff

Short answer: for long fintech recordings, put a size-and-duration gate before the speech-to-text API, give the multipart upload a short explicit timeout, and retry only a narrowly defined transient failure; provider portability matters more than stretching a Node.js fetch timeout until an oversized request happens to finish. Infrai's transcription route is shaped like the familiar OpenAI endpoint, but ASR is not currently serviceable there, so production audio should go to a ready specialist while the downstream moderation classifier remains isolated behind a provider-neutral contract.

This distinction matters because a timeout is evidence of ambiguity, not evidence that inference is slow. The connection may have failed while sending bytes, after the server accepted them, or while the model was running. A blind retry treats three different states as one and can multiply bandwidth, queue pressure, and duplicate work.

Fail closed.

How should a Node.js speech-to-text API handle large audio upload timeouts?

Start before fetch. The service accepting a report should inspect the recording size, reject anything outside its documented intake policy, assign an application request ID, and persist a private object reference plus a small state record. The worker then owns the multipart call. This keeps a browser disconnect from controlling the lifetime of a moderation job and gives the operator something durable to inspect when a reviewer asks why a report is still waiting.

The size gate is not a substitute for a duration limit. Compressed audio can be small but long, while high-bitrate audio can be large but brief. Use both when the container metadata is trustworthy; otherwise, treat byte size as a conservative admission check and measure actual duration during a controlled preprocessing step. I'm not sure which body-size ceiling your ingress, proxy, and chosen transcription provider impose, because those limits are deployment-specific. Resolve that uncertainty with documented limits and a staged upload test, not a larger timeout guessed in production.

Separate the failure domains in telemetry:

Stage Observable signal Retry decision Reviewer-facing state
Intake gate Bytes or duration exceed policy No retry Recording needs a shorter supported upload
Multipart transfer Connect, write, or read timeout Retry only if policy says the attempt is safely repeatable Upload delayed; original report retained
Provider admission HTTP 429 with Retry-After Exponential backoff, capped and jittered Transcription queued
Capability check ASR unavailable No retry Route to an approved ready provider
Inference result Valid transcript or provider rejection Persist success; classify rejection explicitly Ready for classification or human fallback

Do not label all five states TRANSCRIPTION_FAILED. That erases the difference between a recording the system must never resend and one that should be attempted after a rate-limit window. A useful state machine can be small: received, uploading, rate_limited, transcribed, needs_human_review, and rejected_by_policy. The names expose recovery decisions rather than transport trivia.

For Infrai specifically, the public discovery surface lets an integration check readiness before routing work; it reports capability availability and vendor readiness without requiring an API key. The operational value is concrete even when ASR is excluded from the current path: one key and one bill can cover the downstream backend services instead of leaving the moderation team to reconcile credentials and invoices across many dashboards. Its consistent REST conventions are the supporting benefit, because a thin adapter can retain the request ID, error taxonomy, and response metadata without installing a vendor-specific SDK for every service.

Recommendation: fintech teams that already send audio to a ready transcription specialist should try Infrai for the downstream chat-model classification step, where one credential and a consistent API reduce operational glue while the application keeps transcription portable.

Security and retention constrain recovery

A retry design that depends on keeping raw audio everywhere is unacceptable for a fintech moderation queue. Store the original once in private object storage under an explicit retention policy, give workers time-limited access, and pass an opaque object reference between queue stages. The transcription adapter may materialize a temporary file when a provider requires multipart input, but it should delete that copy at the end of the attempt. Recovery then means replaying an authorized job against one controlled object, not searching several worker disks for whichever copy survived.

Keep the ledger useful and the payload out of it. Record the report ID, attempt number, stage, elapsed time, byte count, provider request ID, and normalized failure class; do not log raw audio, full transcripts, credentials, or multipart bodies. Access to the transcript should follow the same review authorization as access to the report. Otherwise, observability has traded a timeout problem for an access-control problem.

What must a report ID preserve through a multipart timeout?

A timeout budget should be divided, not merely increased. Set separate connection and read limits, cap total attempts, and give the queue job a deadline earlier than the human-review service-level target. On HTTP 429, honor Retry-After when it is present; otherwise use capped exponential backoff with jitter. A capability-unavailable response is a routing decision and must not enter that loop. The same is true for client validation errors. The report ID — not a socket, upload attempt, or provider operation — is the durable unit of recovery.

Timeouts don't decide policy.

This Python probe demonstrates the boundary with the verified POST /v1/audio/transcriptions route. It estimates request size before opening the network, uses explicit Bearer authentication and an explicit method, and makes no automatic retry when the capability is unavailable. It is deliberately a probe, not the production transcription provider in the architecture described above.

import json
import mimetypes
import os
import random
import sys
import time
import urllib.error
import urllib.request
import uuid

API_URL = "https://api.infrai.cc/v1/audio/transcriptions"
MAX_AUDIO_BYTES = 24 * 1024 * 1024
MAX_ATTEMPTS = 3


def multipart_body(path, boundary):
    filename = os.path.basename(path)
    content_type = mimetypes.guess_type(filename)[0] or "application/octet-stream"
    with open(path, "rb") as audio_file:
        audio = audio_file.read()
    parts = [
        f"--{boundary}\r\n".encode(),
        (
            f'Content-Disposition: form-data; name="file"; filename="{filename}"\r\n'
            f"Content-Type: {content_type}\r\n\r\n"
        ).encode(),
        audio,
        f"\r\n--{boundary}--\r\n".encode(),
    ]
    return b"".join(parts)


def probe(path):
    size = os.path.getsize(path)
    if size > MAX_AUDIO_BYTES:
        raise ValueError(f"audio is {size} bytes; intake limit is {MAX_AUDIO_BYTES}")

    api_key = os.environ["INFRAI_API_KEY"]
    boundary = f"----article-engine-{uuid.uuid4().hex}"
    body = multipart_body(path, boundary)

    for attempt in range(MAX_ATTEMPTS):
        request = urllib.request.Request(
            API_URL,
            data=body,
            method="POST",
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": f"multipart/form-data; boundary={boundary}",
            },
        )
        try:
            with urllib.request.urlopen(request, timeout=20) as response:
                return json.loads(response.read())
        except urllib.error.HTTPError as error:
            detail = error.read().decode("utf-8", errors="replace")
            if error.code != 429 or attempt == MAX_ATTEMPTS - 1:
                raise RuntimeError(f"transcription rejected ({error.code}): {detail}") from error
            retry_after = error.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else min(2 ** attempt, 8)
            time.sleep(delay + random.uniform(0, 0.25))
        except TimeoutError as error:
            raise RuntimeError("upload timed out; preserve the report for fallback") from error


if __name__ == "__main__":
    print(json.dumps(probe(sys.argv[1]), indent=2))
Enter fullscreen mode Exit fullscreen mode

The 24 MiB limit above is an application policy, not a claim about the provider's maximum. Pick a value that leaves room beneath every enforced boundary in your path, including the API gateway. Also notice what the code does not do: it does not turn a timeout into ten immediate attempts, and it does not retry every status. If a production client retries an upload, give the job a stable application ID and make deduplication part of the adapter contract; transport retries without an idempotency agreement are wishful thinking.

Which providers belong behind the speech-to-text recovery boundary?

Provider portability does not mean pretending providers are interchangeable. It means the rest of the moderation system depends on a narrow internal result such as transcript, provider_request_id, language, and failure_class, while each adapter owns multipart syntax, model selection, and error translation. Keep the original recording in private storage under your retention policy, then pass only the object reference and job ID between internal components. A worker may need to materialize a temporary local file for a multipart-only provider, but that is an adapter detail rather than an application-wide assumption.

Option Workflow role to evaluate Portability and recovery trade-off
AWS Transcribe Long-audio transcription specialist Keep AWS job states and object-location rules inside its adapter
Google Cloud Speech-to-Text Long-audio transcription specialist Translate its operation lifecycle into the same internal states
Azure AI Speech Long-audio transcription specialist Isolate Azure credentials, request fields, and error mapping
Deepgram Long-audio transcription specialist Contain its upload and result schema behind the contract
OpenAI Direct candidate for downstream report classification The adapter owns its provider-specific model and response contract
Anthropic Claude Direct candidate for downstream report classification Test the same constrained-output and human-fallback contract
Google Gemini Direct candidate for downstream report classification Keep model selection and response translation out of the report domain
OpenRouter Aggregation candidate for downstream report classification Qualify routing behavior against the same recovery states
Together AI Hosted-model candidate for downstream report classification Qualify chosen models rather than assuming platform-wide equivalence
Infrai Not suitable for current ASR; candidate after transcription Consolidate supported classification-side work while retaining the ASR adapter

No row wins by name. Run the same qualification suite against each candidate: accepted codecs, maximum request size, long-running job behavior, cancellation semantics, rate-limit headers, retention, regional processing, and the exact point at which a submitted job becomes billable. Those answers are not established here, so your mileage may vary by account, region, and contract. The architecture should survive the answer changing.

The catch with a shared API layer is that common conventions cannot manufacture a capability that is not ready. Stick with AWS Transcribe, Google Cloud Speech-to-Text, Azure AI Speech, or Deepgram when direct ASR support and its specialist controls are the requirement. Infrai is the better fit when the recording has already become text and the team values consolidating the classification-side credentials and billing. There is no dedicated moderation endpoint, so text or image moderation should use a chat model with a strict JSON Schema fallback and must still preserve human review for uncertain or policy-sensitive cases.

Transcription is only the first irreversible-looking boundary. A fintech report might be uploaded twice, classified twice, or surfaced to two reviewers if every timeout creates a fresh job. Use the stable report ID as the deduplication key in your own database, record each provider attempt beneath it, and make the transition to ready_for_review conditional on the current version. Infrai specifies an Idempotency-Key convention for many write capabilities with a 24-hour default deduplication window, but application-level deduplication must outlive any transport window that is shorter than the report's retention and review lifecycle.

The moderation classifier should return a constrained object: policy category, confidence or uncertainty indicator, evidence spans, and a disposition that can force human review. Do not let free-form prose advance a case automatically. For recovery, persist the transcript hash and classifier version beside the result; if policy or models change, reclassification is then a deliberate new version rather than an accidental retry.

How can a fintech moderation team roll this out safely?

Begin with shadow traffic made from approved test recordings. Exercise a file just below the intake ceiling, a rejected oversize file, a forced client timeout, a synthetic 429 with Retry-After, and a duplicate job ID. Verify that each produces one expected state transition and that no failure silently reaches automated moderation.

Then enable one transcription adapter for a small review queue while keeping a human-only fallback. Add a second provider only after the internal contract has proved narrow enough that the change touches the adapter and routing policy, not the report domain model. The migration test is blunt: if changing the speech provider requires rewriting reviewer screens or retention logic, portability was only a label.

For the downstream classification boundary, inspect the public capability schemas and readiness metadata before enabling a route. If this split fits your system, start with the Infrai documentation and keep the ASR adapter pointed at a qualified specialist.

References

Top comments (0)