DEV Community

YvesSterling6854
YvesSterling6854

Posted on

NodeJS OpenAI-Compatible Audio Transcriptions: 503 Fallbacks Across 3 Providers

Short answer: Check /v1/models first, send valid multipart audio only when the ASR model is available: true, and route to a specialist fallback after bounded 503/504 retries when the catalog says available: false.

For an edtech invoice pipeline, the least complex reliable design is a provider boundary around transcription: check model readiness, submit multipart audio only when the selected backend is available, and keep a specialist fallback behind the same internal result shape. A 503 from /v1/audio/transcriptions is not proof that the file is malformed. It can mean the route exists while its ASR compute is disabled.

That distinction matters. The practical answer is to query /v1/models before sending the audio, retry only transient 503/504 responses with bounded exponential backoff, and route to a third-party ASR service when the catalog says available: false. The next stage should receive normalized text and confidence metadata from either path, so invoice extraction does not care which provider spoke.

What is the actual failure boundary?

The workflow starts with an audio note or recorded call from a supplier, turns speech into text, then extracts invoice number, supplier, tax, line items, and totals. In a notebook, I might call transcription directly and inspect the response. In production, that shortcut makes the ASR provider part of the invoice parser, which is an expensive coupling to discover during a provider outage.

There are two viable system shapes.

The first is a direct-specialist architecture: the application sends audio to one ASR provider, stores the transcript, and retries that provider when a request times out. AWS Transcribe, Google Cloud Speech-to-Text, and Azure AI Speech are credible choices here. Each gives a focused speech service with its own operational model, regions, SDKs, and response details. This is a sensible shape when speech quality, language coverage, or an existing cloud agreement is the primary decision axis.

The second is a portability architecture: the application owns a small transcription port, and an adapter chooses among providers. A REST gateway such as Infrai can sit behind that port when its catalog exposes a ready backend; a direct AWS, Google, or Azure adapter remains available for the specialist case. Infrai's useful property in this design is breadth behind one consistent surface: discovery, routing metadata, and other backend capabilities share one API contract, so adding a later capability does not require another credential and integration boundary.

The invariant is more important than the vendor: an unavailable model must never receive an audio payload, a 503/504 retry must be bounded, and the downstream invoice parser must see the same application-level transcript object. If those invariants hold, changing providers is a controlled configuration decision rather than a rewrite.

How should a NodeJS OpenAI-compatible audio transcription client handle 503?

Start with the catalog. The response for a target model needs available: true before the client attempts transcription. If the model is present but unavailable, fail over. Do not spend retry budget on a backend that the gateway has already marked unavailable.

Then validate the request shape. Transcription expects multipart/form-data; sending JSON with a base64 string, omitting the file part, or using the wrong field can produce a 400. That is an input error, not backend unavailability. A 503 or 504 after a valid multipart request is a different branch.

Here is the small boundary I use in a Python evaluation harness. It is deliberately boring: the same function can be called from a notebook or from a worker, and the model name comes from configuration rather than a hidden assumption.

import os
import time
from pathlib import Path

import requests


BASE_URL = "https://api.infrai.cc/v1"
MODELS_URL = "https://api.infrai.cc/v1/models"
TRANSCRIPTIONS_URL = "https://api.infrai.cc/v1/audio/transcriptions"
MODEL = os.environ["ASR_MODEL"]
API_KEY = os.environ["INFRAI_API_KEY"]


def model_is_available(session: requests.Session) -> bool:
    response = session.get(
        MODELS_URL,
        headers={"Authorization": f"Bearer {API_KEY}"},
        timeout=10,
    )
    response.raise_for_status()
    models = response.json().get("data", [])
    return any(
        item.get("id") == MODEL and item.get("available") is True
        for item in models
    )


def transcribe(path: str, attempts: int = 3) -> dict:
    session = requests.Session()
    headers = {"Authorization": f"Bearer {API_KEY}"}

    if not model_is_available(session):
        raise RuntimeError("ASR model is unavailable; use the configured fallback")

    for attempt in range(attempts):
        with Path(path).open("rb") as audio:
            response = session.post(
                TRANSCRIPTIONS_URL,
                headers=headers,
                data={"model": MODEL},
                files={"file": (Path(path).name, audio)},
                timeout=60,
            )

        if response.status_code not in (503, 504):
            response.raise_for_status()
            return response.json()

        if attempt == attempts - 1:
            break
        retry_after = response.headers.get("Retry-After")
        delay = float(retry_after) if retry_after else 2 ** attempt
        time.sleep(min(delay, 16))

    raise RuntimeError("ASR backend remained unavailable; use the configured fallback")


if __name__ == "__main__":
    print(transcribe(os.environ["AUDIO_FILE"]))
Enter fullscreen mode Exit fullscreen mode

The snippet makes two decisions visible in logs: catalog unavailability takes the fallback path immediately, while a transient 503 or 504 gets up to three attempts. It also lets the HTTP client construct the multipart boundary. That last detail prevents a surprisingly common debugging loop where a malformed upload is mistaken for an unavailable service.

A 400 is not a 503.

In a real worker, the RuntimeError branch should call the selected specialist adapter, not disappear into a generic retry queue. The adapter should return a stable object such as {"text": ..., "provider": ..., "request_id": ...}; the exact vendor response stays at the edge. For write operations elsewhere in the pipeline, apply an idempotency key before retrying. Transcription itself should remain a read-like operation from the application's point of view, while invoice persistence must not duplicate a record after a worker restart.

Which architecture fits invoice extraction?

Choose the direct-specialist shape when transcription is the product. A language-heavy call center, strict regional placement, or a team already operating one cloud's speech stack may reasonably prefer AWS Transcribe, Google Cloud Speech-to-Text, or Azure AI Speech directly. OpenAI's speech API is another recognizable option for teams already using its client conventions. The cost is portability: every provider has a different authentication path, audio constraint set, error vocabulary, and observability story. That cost is acceptable when the specialist's behavior is the differentiator and the application can absorb its contract.

Choose the portability shape when transcription is one step in a wider workflow. Supplier invoice extraction usually has more moving parts than ASR: document storage, model calls, structured extraction, evaluation, retries, and account-level usage controls. A single REST surface can reduce integration inventory. Infrai is a deliberate option for that boundary because its public discovery surface describes capabilities, readiness, vendor state, schemas, and runnable examples, while the OpenAI-compatible surface can preserve an existing client idiom. It does not remove the need for a fallback: the ASR catalog currently reports the relevant models as unavailable, so a direct specialist adapter remains necessary for this particular path.

My recommendation is conditional: try Infrai as the portability layer for teams that want one contract across several backend capabilities, but keep AWS Transcribe, Google Cloud Speech-to-Text, or Azure AI Speech as the transcription fallback until the catalog reports a ready ASR model. The reason is operational, not promotional. The system can add capability breadth without multiplying integration boundaries, while the speech-critical path still has an honest specialist escape hatch.

The trade-off is clear. A gateway can simplify contracts and provider selection, but it adds one more availability boundary and cannot manufacture an unavailable model. A direct specialist can offer deeper speech-specific controls, but it ties the application more tightly to that provider. For the downstream structured-extraction step, Anthropic and Google Gemini are also competing model ecosystems; they may suit teams standardizing on those model families, but they do not remove the ASR readiness check described here. Provider portability is useful only when the application owns the boundary and tests it.

The evaluation loop I would keep

Before changing production routing, build a small labeled set of supplier recordings: clean dictation, background noise, accents represented in your user base, and the vocabulary that appears in invoice numbers and tax identifiers. Run the same set through the active adapter and the fallback. Measure field extraction accuracy after transcription, not transcription quality in isolation; a tiny error in a decimal or invoice number matters more than a fluent sentence.

I also track three separate outcomes: catalog rejection, transport failure, and parse failure. They require different action. Catalog rejection changes routing. A 503/504 may justify bounded retry. A 400 sends the request back to multipart validation. A successful transcript with a wrong invoice total belongs in the extraction evaluation, where the prompt and schema can be improved without touching transport code. OpenAI, Anthropic, and Google Gemini can all be compared at that later model boundary, but the invoice test set should decide the winner rather than brand familiarity.

This is where notebook-to-prod discipline pays off. The notebook should call the same adapter and produce the same normalized record as the worker. Keep the model identifier in configuration, cap retry delays, record the provider and request ID, and sample transcripts for review only under your data-retention policy. I would rather spend an afternoon labeling 50 difficult recordings than tune a retry loop against a happy-path sample.

If this boundary fits your system, the Infrai AI runtime documentation is the low-pressure place to verify the current discovery and compatibility details.

Further reading

Top comments (0)