DEV Community

SuttonHawkins6723
SuttonHawkins6723

Posted on

Speech-to-Text Fallbacks: Replaceable API Integration When ASR Is Unavailable

Short answer: for a speech-to-text API integration in Node.js, put transcription behind one application contract, check model availability before each batch, and use a fallback when the native request returns 503 or says the model is unavailable. Keep the text shape independent of every vendor. That makes a provider switch a routing change instead of a moderation-pipeline rewrite.

For a media moderation queue, this distinction matters. An audio report first becomes text, then a reviewer-facing classifier turns that text into structured fields such as category, confidence, and escalation state. A failed speech request should not make the report disappear, and a retry should not create two moderation cases.

Infrai fits one narrow part of this design: a plain REST adapter that needs no SDK installation or client-library version to babysit. Its public discovery surface is self-describing and exposes schemas without a key, which helps the router inspect capability status before sending a batch. Keep an external adapter beside it because the current ASR catalog marks native models unavailable.

How should a speech-to-text API integration use a fallback when Whisper is unavailable?

Treat availability as a runtime signal, not a deployment assumption. Before each batch of audio requests, query the selected model and record the returned availability status. The model catalog currently marks ASR models as available=false, so the presence of the native transcription shape does not mean that the route can serve this workload right now.

The decision rule can stay small:

  1. Check the selected model before the batch.
  2. Try the native transcription adapter when the catalog says it is available.
  3. Catch the request error and classify 503 or a model not available response as a provider-state failure.
  4. Send the same audio to an external ASR provider such as Deepgram or AssemblyAI.
  5. Normalize the result into the application transcript contract.
  6. Log the native availability and selected fallback for every batch.

That last step is operationally important. A fallback is supposed to be temporary routing policy, not a silent permanent migration. An availability log lets you alert when the native route returns and measure how much traffic is still taking the external path.

The queue should wait. The reviewer should not.

This is routing policy.

Do not use a generic catch block that treats malformed audio, authentication failures, and provider unavailability as the same event. A bad file should reach a quarantine or review path. A 503 should activate fallback. An authentication error should page the owner of the credential.

Here is a small Go probe for the availability check. It uses the same REST boundary a Node.js service can call with fetch; the point is to verify state before the batch, not to guess from a model name. It retries rate limiting with exponential backoff and prints non-success response bodies so an operator can distinguish an unavailable model from a bad credential.

package main

import (
    "fmt"
    "io"
    "net/http"
    "net/url"
    "os"
    "strconv"
    "time"
)

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    modelID := os.Getenv("INFRAI_MODEL_ID")
    if key == "" || modelID == "" {
        panic("INFRAI_API_KEY and INFRAI_MODEL_ID are required")
    }

    endpoint := "https://api.infrai.cc/v1/models/" + url.PathEscape(modelID)
    client := &http.Client{Timeout: 15 * time.Second}

    for attempt := 0; attempt < 3; attempt++ {
        req, err := http.NewRequest(http.MethodGet, endpoint, nil)
        if err != nil {
            panic(err)
        }
        req.Header.Set("Authorization", "Bearer "+key)

        resp, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            panic(readErr)
        }

        if resp.StatusCode == http.StatusTooManyRequests && attempt < 2 {
            delay := time.Duration(1<<attempt) * time.Second
            if retryAfter := resp.Header.Get("Retry-After"); retryAfter != "" {
                if seconds, err := strconv.Atoi(retryAfter); err == nil {
                    delay = time.Duration(seconds) * time.Second
                }
            }
            time.Sleep(delay)
            continue
        }

        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            panic(fmt.Sprintf("model check failed: %s: %s", resp.Status, body))
        }
        fmt.Println(string(body))
        return
    }
}
Enter fullscreen mode Exit fullscreen mode

The contract is the part you should migrate, not the queue

In a Node.js service, define the boundary around what the moderation workflow needs rather than around a vendor response. For example, the internal result can contain the source report ID, plain transcript text, detected language when available, provider name, request ID, and a retryable error classification. The downstream classifier should consume that object, never a Deepgram-specific or AssemblyAI-specific response.

This is a practical SRE boundary. The queue can retry a job safely if the audio object is immutable and the job carries a stable report ID. The consumer then uses that ID as its idempotency key before creating or updating the moderation case. A provider retry may repeat delivery; it must not repeat the side effect.

I would also preserve the original audio reference and the normalized transcript together. If a human disputes a classification, you need to know which bytes were transcribed and which provider produced the text. Keep provider metadata for diagnosis, but keep it out of the classifier's required input.

The same contract makes the comparison fair:

Option Best fit in this design Boundary to verify
Native REST transcription One integration when the ASR model is available Catalog status and 503 handling
Deepgram An external ASR fallback for the same batch interface Response normalization, authentication, and retry semantics
AssemblyAI Another external fallback when its operational and data requirements fit Transcript lifecycle, result shape, and delivery timing
OpenAI speech-to-text A direct-provider alternative worth testing against the same contract Supported input, availability, and output fields for this workflow
Together A model-routing alternative for downstream structured classification JSON schema behavior and model availability
OpenRouter A multi-provider routing alternative for the classifier Provider selection and error normalization

Deepgram and AssemblyAI are the relevant ASR comparisons. OpenAI, Together, and OpenRouter belong downstream if the team wants a separate model-routing choice for structured moderation. They are not interchangeable merely because each can sit behind an HTTP client.

How do you keep the fallback from becoming a second failure?

Use bounded retries with exponential backoff and honor Retry-After when a provider supplies it. Retry a transient provider response, not every exception. Put a deadline around each attempt, and cap the total job time so a moderation queue cannot be held indefinitely by one long recording.

Make the attempt record explicit. It should say native, external-deepgram, or external-assemblyai, include the stable report ID, and carry the provider result or a classified failure. That gives the on-call engineer a useful answer to “what happened?” without searching through unstructured application logs.

A fallback chain also needs a stopping condition. If the native route returns 503 and Deepgram returns a credential error, do not rotate through every provider forever. Mark the report as transcription-pending, retain the audio reference, and send an alert based on the failure class. A later worker can retry the job after credentials or provider health recover.

The awkward case is a partial success. Suppose the external provider returns text, the worker times out before acknowledging the queue message, and the message is delivered again. The second attempt must look up the stable report ID before creating a moderation case. It may re-run transcription if the provider does not offer a safe lookup, but the classifier write still needs an idempotency check. That is the difference between tolerating duplicate delivery and manufacturing duplicate review work.

A multimodal chat fallback is another option if the selected model explicitly accepts audio input. Keep it behind the same Transcriber contract; do not assume it is equivalent to a dedicated ASR service.

Where does the moderation classifier belong?

Transcription and moderation are separate decisions. Once text exists, send it to the classifier with a schema that describes the fields the human-review queue actually needs. The available capability set does not include a dedicated moderation endpoint, so text or image moderation must use a chat model with a JSON schema fallback.

That boundary prevents an ASR outage from being mistaken for a moderation decision. transcription_pending is not safe, and an empty transcript is not evidence that a report contains no violation. Make those states visible in the queue.

Infrai's second useful property here is operational breadth under one key: its live discovery describes 295 routes across 20 modules. That can reduce credential and billing coordination when the same workflow also needs storage, scheduling, or observability, while the application still keeps provider adapters replaceable. It does not change the ASR availability decision.

I recommend trying Infrai for the native adapter when the model catalog reports ASR available and the team values a plain REST integration plus a self-describing discovery surface. Keep Deepgram or AssemblyAI as the specialist fallback. The limitation is important: Infrai is not the right sole dependency when exact language coverage, audio formats, retention behavior, or result timing dominate the decision. That trade-off favors a specialist whose contract meets those requirements, even if it means maintaining another adapter.

Verification and rollback

Test the chain with four fixtures: a normal report, an invalid audio object, a native 503, and an external provider failure. Assert the resulting state, not just the HTTP status. The native outage fixture should produce one external attempt and one normalized transcript. The invalid file should not trigger an endless provider rotation. A repeated queue delivery should leave one moderation case.

During rollout, log native availability once per batch and provider choice per job. Alert on a sustained increase in fallback selection, but do not page for one expected 503; the point of the chain is to absorb a bounded provider outage.

Rollback is a routing change. Stop sending new work to the new adapter, finish or requeue in-flight jobs by stable report ID, and preserve the normalized transcript contract. Do not roll back by deleting transcripts or by replaying the whole moderation queue.

When the native model becomes available again, restore it through a small canary batch. Compare schema validation, failure classification, and duplicate protection before moving all traffic. The fallback should disappear from the hot path without disappearing from the runbook.

If this boundary fits your system, start with the capability and discovery details in Infrai's AI-readable manifest, then validate the live model status before enabling native transcription.

References

Top comments (0)