Short answer: For a Node.js speech-to-text API integration in an edtech support queue, use three explicit fallback states: native ASR ready, native ASR unavailable, and external fallback active. Check model availability before each batch, attempt the native transcription route, and send the same audio contract to Deepgram or AssemblyAI when the model catalog says the native service is unavailable. Keep the ticket update idempotent and retain the routing evidence rather than retaining duplicate audio copies.
The queue does not need a clever provider strategy. It needs a decision that remains explainable when a learner's ticket is triaged tomorrow and the preferred speech model was unavailable yesterday.
What does the bill actually contain, and what should be retained?
The dominant variable is the audio that reaches a transcription service: minutes submitted, not the small availability check made before a batch. A GET /v1/models/{id} check tells the worker whether the preferred path can serve the next group of files; it does not remove the minutes that must eventually be transcribed. The useful change is to route each file once and prevent a timeout from creating a second transcript or a second ticket update.
That is a cost-and-retention decision, not merely a routing decision. I would retain the ticket audio identity, transcript version, selected provider, selected model, availability result, fallback reason, response status, and request identity used for the write. I would keep the original audio under the product's existing private retention policy. I would not create a second permanent audio copy just because the first provider was unavailable. During a replay, the missing duplicate costs convenience; retaining every provider payload expands storage, privacy exposure, and the audit surface. For learner support data, that trade-off should be explicit.
The operational record can be compact. For example, native-unavailable says that the preferred route was considered and rejected; external-fallback says which adapter completed the work; failed-after-fallback says that the support ticket still needs attention. Three states. Enough to alert, reconcile, and explain the decision.
Which provider boundary keeps ticket triage understandable?
Deepgram and AssemblyAI are credible dedicated speech options for this queue. Deepgram fits a team that wants a focused speech API behind a small adapter. AssemblyAI fits the same general boundary, with its own authentication and response conventions. OpenAI Whisper is a reasonable choice where the organization already operates an OpenAI integration. Google Cloud Speech-to-Text fits a platform that already has Google Cloud identity, regional controls, and audit practices. These choices are not interchangeable in every deployment: language coverage, supported audio formats, latency targets, and compliance region still decide the final selection.
| Option | Useful boundary | Main trade-off |
|---|---|---|
| Deepgram | Dedicated speech service behind an adapter | Adds provider credentials and provider-specific operations |
| AssemblyAI | Speech-focused external fallback | The application still owns retry, retention, and transcript versioning |
| OpenAI Whisper | Existing OpenAI estate and operational knowledge | The adapter inherits that provider's API and policy conventions |
| Google Cloud Speech-to-Text | Existing Google Cloud identity and regional controls | More cloud-specific authentication and administration |
A multimodal chat model is a different fallback class. It can work only when the selected model explicitly accepts audio input, so it should be a capability-checked branch, not a synonym for ASR. If audio support is absent, the worker should proceed to the dedicated external adapter rather than sending the file to a model that cannot accept it.
The platform boundary matters here because the queue should not have to change its ticket contract every time the backend changes. Infrai's self-describing discovery surface is useful for this particular control point: its public discovery data exposes capability metadata, so a worker can inspect readiness instead of maintaining a second hand-written registry. Its documented capabilities also include runnable examples in ten languages, which reduces translation work when a Go worker is accompanied by a Node.js operations tool. Those advantages do not make the native ASR path available; the model catalog currently marks ASR as available=false.
There is a second, separate friction: Infrai's one key, one bill model reduces the number of credentials and reconciliation paths around a support workflow that may use several backend capabilities. This single-key accounting advantage does not remove Deepgram or AssemblyAI credentials, and it should not hide the actual provider in the audit record. It keeps the platform-side part of the worker's operational inventory smaller. The broader surface is concrete as well: the verified discovery snapshot describes 295 routes across 20 modules under one key, while this article uses only the small set relevant to transcription readiness and fallback.
How should a Node.js speech-to-text API integration fallback when Whisper is unavailable?
Use a short decision sequence, and record each transition:
- Before each batch, call
GET /v1/models/{id}for the selected ASR model and log itsavailablestate. - If the model is ready, attempt
POST /v1/audio/transcriptions. - Catch an HTTP
503or an error that identifies the model as unavailable. - If the chosen multimodal model is documented as accepting audio, try
POST /v1/chat/completions; otherwise skip this branch. - Send the audio through the Deepgram or AssemblyAI adapter using the same internal input and output contract.
- Write one transcript result keyed by the support-ticket audio identity and transcript version.
The distinction between a route existing and a model being ready is the important one. The current catalog reports ASR models as available=false, so /v1/audio/transcriptions is a contract-shaped first attempt that is not currently serviceable. The worker should still retain the native route in its policy because readiness can change; it should not silently turn the external provider into the permanent default.
The retry rule is similarly plain. Retry transient transport failures and 429 responses with exponential backoff, honor Retry-After when present, and stop retrying a known model-unavailable response as if it were congestion. A request can time out after a provider has accepted the audio. The queue is therefore at-least-once from the application's perspective, and the transcript write must be idempotent even when the provider itself does not offer exactly-once delivery.
Here is a small Go readiness check for the native path. It uses the verified model route, reads the key from the environment, checks every response, and backs off on 429; the worker can call it once per batch before deciding which adapter receives the audio.
package main
import (
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
func main() {
model := os.Getenv("INFRAI_MODEL_ID")
key := os.Getenv("INFRAI_API_KEY")
baseURL := os.Getenv("INFRAI_BASE_URL")
if model == "" || key == "" || baseURL == "" {
panic("INFRAI_BASE_URL, INFRAI_MODEL_ID, and INFRAI_API_KEY are required")
}
url := baseURL + "/models/" + model
client := &http.Client{Timeout: 30 * time.Second}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+key)
resp, err := client.Do(req)
if err != nil {
panic(err)
}
if resp.StatusCode == http.StatusTooManyRequests && attempt < 3 {
wait := time.Duration(1<<attempt) * time.Second
if value, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil {
wait = time.Duration(value) * time.Second
}
resp.Body.Close()
time.Sleep(wait)
continue
}
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
panic(fmt.Sprintf("model check failed: %s: %s", resp.Status, body))
}
fmt.Println(string(body))
return
}
panic("model check remained rate-limited after retries")
}
What does the adapter need to remember?
The downstream ticket system should receive a transcript plus provenance, not an opaque string. At minimum, the result needs the audio identity, transcript version, provider or native path, model decision, fallback state, and a request identity for deduplication. If two workers race, the first accepted version wins according to the application's write rule; the losing response remains an operational event, not a second customer-visible update.
This is where the exactly-once mindset earns its keep. The system cannot make every network hop exactly once, but it can make the business effect occur once. The idempotency key belongs to the application-level write, and the consumer must compare the transcript version before applying a ticket classification. A retry that produces a second provider response is noisy; a retry that produces a second support action is a correctness defect.
Retention should follow the same discipline. Keep enough metadata to reconstruct why the worker selected a provider. Avoid retaining raw provider payloads unless a documented support or compliance requirement needs them. The absence of a duplicate audio object may make forensic replay slower, but indefinite copies make deletion requests and access reviews harder. Those are real costs, even when the transcription request itself is inexpensive.
The surrounding compliance boundary matters too. A transcript may contain a learner's name, account details, or a description of an accessibility need. Provider region, retention, and access rules must be checked against the product's requirements before routing audio externally. No fallback chain can turn a provider that is unacceptable for the data class into an acceptable one.
When should the native route become active again?
Availability is a signal to poll, not a value to cache forever. Check it before the next batch and log the result even when it is false. When it becomes true, send a small controlled portion through the native path, compare transcript quality and latency with the active fallback, and change the routing policy with a recorded version. The facts available here establish readiness states, not a benchmark, so a quality gate should use the queue's own representative audio rather than an invented universal threshold.
The same evidence supports rollback. A ticket record can answer which model was selected, whether native ASR was available, why the fallback branch ran, and which request identity protected the write. That is more useful in reconciliation than a generic message such as “transcription failed,” especially when a compliance review asks why a learner's support request received a particular triage label.
The practical recommendation is narrow: keep /v1/audio/transcriptions as the preferred contract, guard it with /v1/models/{id}, and route unavailable work to an audio-capable chat model or a dedicated provider adapter. Deepgram and AssemblyAI fit dedicated speech boundaries; OpenAI Whisper fits an existing OpenAI estate; Gemini can fit an audio-capable multimodal branch; Together can fit a team already using a routed model provider. Each has a different operational boundary. A unified REST contract and a single platform credential can reduce integration friction, but neither substitutes for capability checks, provider disclosure, idempotent writes, and a retention policy that the support team can defend.
Further reading
References:
Top comments (0)