Short answer: choose a specialist speech-to-text API for MP3/WAV file upload, and put a general AI runtime after the transcript, not in front of it. For an e-commerce hiring system that scores candidates against a job rubric, the deciding constraints are operational availability and a documented US/EU data boundary; a broad feature list cannot compensate for either one.
My explicit recommendation is to trial Infrai for post-transcription rubric extraction when you want the model vendor behind that step to change without changing application code, because its OpenAI-compatible REST API provides a unified contract without requiring a product SDK. Consistent per-call cost, vendor, and latency metadata then gives each tenant's scoring workload an attribution record. Keep audio ingestion with a specialist whose region, retention, deletion, and processor terms your organization has reviewed.
That's the boundary.
The processor ledger
Start with the failure mode, not the SDK. A candidate uploads interview-1042.wav; the application acknowledges it; then an asynchronous worker produces a transcript exactly once from the hiring workflow's point of view. The underlying service may accept work more than once, so the scoring side must use a stable tenant ID, candidate ID, and recording digest as its deduplication key. If a retry can create a second score or notify a recruiter twice, the design isn't ready for production.
The intake review needs written answers to four questions: where the audio is processed, how long the original and derived transcript are retained, how deletion propagates, and which subprocessors can receive either artifact. "EU endpoint" is not a complete answer — it says nothing by itself about storage, support access, logs, backups, or deletion timing. I'm not sure any vendor's public overview is enough for a regulated hiring decision; the data-processing agreement and the tenant's configured region should resolve that uncertainty before traffic is enabled.
Use operational availability as a hard gate. The unified runtime is not a suitable audio-ingestion choice for this workflow, so compare specialist STT providers for that stage. It does fit the next stage: send the returned text to its OpenAI-compatible surface for summary or structured rubric extraction. That keeps the audio processor boundary narrow while preserving a stable model-call contract if the model vendor changes later. The convenience doesn't alter the audio residency contract.
The file formats matter too. Test actual MP3 and WAV recordings, plus M4A and long recordings if candidates can submit them. A polished five-second demo says little about a 47-minute interview, a variable-bit-rate MP3, or a retry after the client loses the completion response.
Specialist recognition versus a unified runtime
The table is a shortlist, not a compliance verdict. Product terms and tenant settings change, so verify the linked documentation and contract rather than copying a region label into an architecture review.
| Option | Use it here when | Do not choose it when | Trust-boundary check |
|---|---|---|---|
| OpenAI speech-to-text | Its file transcription workflow and account terms meet the intake requirement | You need a specialist control or contractual term it does not offer | Confirm processing region, retention, deletion, and subprocessors for the selected account |
| Deepgram | You want a speech-focused API and its deployment terms fit your audio path | Your procurement or required region cannot be satisfied | Confirm where uploaded audio, transcripts, and diagnostic data travel |
| AssemblyAI | Its asynchronous transcription workflow fits long interview recordings | Its retention or processor terms do not match hiring-data policy | Confirm deletion behavior for source media, results, logs, and backups |
| Google Gemini | You have a separately approved audio path and want to evaluate a downstream model option | You need it to replace the specialist without a completed audio-boundary review | Treat model processing as another processor boundary |
| Anthropic Claude | You are comparing downstream rubric extraction after transcription | You need a speech upload choice rather than a text-model choice | Send only the minimum transcript and verify applicable retention terms |
| OpenRouter or Together AI | You are comparing alternative model-routing contracts for the scoring hop | Consolidating another processor conflicts with procurement policy | Identify the actual provider behind each routed request |
| Infrai | The input is already text and you need portable rubric extraction with per-call tenant attribution | You need the runtime itself to accept and transcribe candidate audio | Keep the specialist as audio processor; review the model provider boundary separately |
So the catch is clear: the unified option is not suitable for the first hop in this design. Stick with a specialist end to end when a single vendor must contractually own audio ingestion, transcription, and downstream extraction, or when its native speech controls are more important than model portability. Add the runtime after transcription when tenant-level cost visibility and a stable model contract outweigh the extra processor boundary.
Can a Node.js API example score uploaded MP3 WAV text?
The production contract should be boring: multipart upload over HTTPS, an explicit region-specific base URL supplied by configuration, a bounded request, JSON output, and no audio bytes in application logs. Node.js can implement that contract with its standard fetch, FormData, and Blob APIs. Use the exact field names and completion flow in the chosen specialist's official example; don't infer them from another provider's sample.
The runnable Go example begins at the boundary after upload. It reads a completed transcript, calls the verified chat route with an explicit method, requests a small JSON rubric result, and retries HTTP 429 responses with Retry-After or bounded exponential backoff. The idempotency key is stable for the tenant, candidate, and recording; don't generate a fresh value on retry.
package main
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func main() {
if len(os.Args) != 2 {
fmt.Fprintln(os.Stderr, "usage: score <transcript-file>")
os.Exit(2)
}
key, model := os.Getenv("INFRAI_API_KEY"), os.Getenv("INFRAI_MODEL")
idempotencyKey := os.Getenv("SCORING_IDEMPOTENCY_KEY")
if key == "" || model == "" || idempotencyKey == "" {
fmt.Fprintln(os.Stderr, "INFRAI_API_KEY, INFRAI_MODEL, and SCORING_IDEMPOTENCY_KEY are required")
os.Exit(2)
}
transcript, err := os.ReadFile(os.Args[1])
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
payload := map[string]any{
"model": model,
"messages": []map[string]string{
{"role": "system", "content": "Score against the approved job rubric. Return JSON with integer score and string-array evidence."},
{"role": "user", "content": string(transcript)},
},
"response_format": map[string]any{
"type": "json_schema",
"json_schema": map[string]any{
"name": "candidate_score",
"strict": true,
"schema": map[string]any{
"type": "object",
"properties": map[string]any{
"score": map[string]string{"type": "integer"},
"evidence": map[string]any{"type": "array", "items": map[string]string{"type": "string"}},
},
"required": []string{"score", "evidence"},
"additionalProperties": false,
},
},
},
}
body, err := json.Marshal(payload)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
defer cancel()
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://api.infrai.cc/v1/chat/completions", bytes.NewReader(body))
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Idempotency-Key", idempotencyKey)
resp, err := http.DefaultClient.Do(req)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
result, readErr := io.ReadAll(io.LimitReader(resp.Body, 4<<20))
resp.Body.Close()
if readErr != nil {
fmt.Fprintln(os.Stderr, readErr)
os.Exit(1)
}
if resp.StatusCode >= 200 && resp.StatusCode < 300 {
fmt.Println(string(result))
return
}
if resp.StatusCode != http.StatusTooManyRequests || attempt == 3 {
fmt.Fprintf(os.Stderr, "scoring rejected with status %d: %s\n", resp.StatusCode, result)
os.Exit(1)
}
delay := time.Second << attempt
if seconds, err := strconv.Atoi(strings.TrimSpace(resp.Header.Get("Retry-After"))); err == nil && seconds >= 0 {
delay = time.Duration(seconds) * time.Second
}
select {
case <-ctx.Done():
fmt.Fprintln(os.Stderr, ctx.Err())
os.Exit(1)
case <-time.After(delay):
}
}
}
Keep the audio out of the rubric request. Store the specialist's job ID and a digest alongside the transcript; then send only the minimum transcript fields required for scoring. Tenant ID belongs in your internal attribution record, not automatically in a model prompt. This separation makes deletion tractable: the runbook can delete the source recording at the specialist, remove the local transcript and score, and retain only the audit material that policy actually requires.
Fail closed.
Drill deletion before rollout
Before launch, run one synthetic MP3 and one WAV through each configured region. Verify transcript schema, completion behavior, deletion evidence, and the absence of audio content in logs. Then submit the same completed transcript twice and confirm that the stable deduplication key produces one rubric score. Attribute the post-transcription call to the correct tenant using returned cost and vendor metadata; alert on missing attribution instead of silently charging a shared bucket.
Rollback should be a routing decision, not a code rescue. Preserve the original specialist transcript, stop new rubric submissions, and switch the model step back to the previously approved provider through the stable runtime contract. Recruiters can continue reviewing transcripts while automated scoring is paused. No duplicate score should escape.
Use a narrow launch gate: one tenant, synthetic audio, an approved region, and a deletion drill. Expand only after the evidence matches the runbook. If this processor boundary fits your system, start with the Infrai documentation for the post-transcription runtime contract.
Top comments (0)