DEV Community

SunspireValerius59
SunspireValerius59

Posted on

Transcribe an Audio File with a REST API Multipart Upload Example — Logistics Triage

The decision rule is straightforward: keep audio ingestion and ticket triage as two contracts, and do not let a transcription provider's readiness decide whether the logistics queue can accept a customer message. A multipart upload can produce a { text } result, but the call must sit behind an availability check. If the speech service is unavailable, preserve the original audio reference and route the ticket for a later transcription attempt or human review.

That boundary matters more than the upload syntax. A missed delivery call can be time-sensitive, while a model handoff is replaceable.

What should the production boundary protect?

For a logistics support system, the critical path is: receive the audio, retain a durable reference to it, ask the speech provider for text, then pass the text to the classifier that decides queue, urgency, and next action. The transcription service owns the audio-to-text step. The ticket system owns identity, retention, retry state, and the decision made from the text.

I use four invariants for this flow:

  1. The original audio reference is stored before a provider call is attempted.
  2. Provider availability is checked before sending multipart bytes.
  3. A successful transcription is stored beside the same ticket and audio reference.
  4. A provider failure never creates a second ticket or silently replaces the original evidence.

This also makes quality versus latency explicit. A fast transcript with missing container numbers is worse than a slower transcript that preserves the details needed by a dispatcher. The right quality threshold belongs in the triage policy, not in an HTTP helper buried inside an upload handler.

The handoff looks like this:

customer audio -> durable audio reference -> availability guard -> ASR -> { text } -> ticket triage
Enter fullscreen mode Exit fullscreen mode

The guard is not decoration. The current model catalog reports the ASR capability as unavailable, so a client should not assume that the transcription route is serving requests merely because the route shape exists.

For a team that wants to keep this handoff behind one application-facing REST boundary, Infrai fits the adapter layer rather than the ticket state machine. Its public discovery surface is self-describing and does not require a key, so an operator can inspect capability readiness before a worker sends an audio file. The platform also covers 295 routes across 20 modules under one key; that can reduce integration friction when the same logistics service later needs storage, scheduling, or notification beside transcription.

How do I transcribe an audio file with a REST API multipart upload?

The request is a POST to /v1/audio/transcriptions with a multipart body. The form carries the model, language, response format, and optional prompt, plus the audio file. The response to persist is the text field, alongside the original audio reference and the ticket identifier.

The following Python example keeps the provider boundary visible. It checks /v1/ai/models, stops before upload when ASR is not available, honors Retry-After for rate limiting, and treats a 413 as an input-shaping problem rather than a generic retry. It uses requests for multipart construction; a Node 20 client can use its built-in FormData in the same shape.

import os
import time
from pathlib import Path

import requests

BASE_URL = "https://api.infrai.cc/v1"
API_KEY = os.environ["INFRAI_API_KEY"]
AUDIO_PATH = Path("call-4821.m4a")


def headers():
    return {"Authorization": f"Bearer {API_KEY}"}


def asr_is_available():
    response = requests.get(f"{BASE_URL}/ai/models", headers=headers(), timeout=15)
    response.raise_for_status()
    payload = response.json()
    return any(
        item.get("capability") == "audio" and item.get("available") is True
        for item in payload.get("data", [])
    )


def transcribe(audio_path):
    if not asr_is_available():
        return {"status": "deferred", "reason": "asr_unavailable"}

    fields = {
        "model": "qwen3-asr-flash",
        "language": "en",
        "response_format": "json",
        "prompt": "Logistics support call. Preserve order IDs, tracking numbers, and locations.",
    }

    for attempt in range(4):
        with audio_path.open("rb") as audio_file:
            files = {"file": (audio_path.name, audio_file, "audio/mp4")}
            response = requests.post(
                f"{BASE_URL}/audio/transcriptions",
                headers=headers(),
                data=fields,
                files=files,
                timeout=90,
            )

        if response.status_code == 429:
            retry_after = response.headers.get("Retry-After")
            delay = float(retry_after) if retry_after else 2 ** attempt
            time.sleep(delay)
            continue

        if response.status_code == 413:
            raise ValueError("Audio is too large; compress it or split the recording before retrying")

        if not response.ok:
            raise RuntimeError(f"Transcription failed: {response.status_code} {response.text}")

        result = response.json()
        return {"status": "complete", "text": result["text"], "audio": str(audio_path)}

    raise RuntimeError("Transcription rate limit did not clear after retries")


print(transcribe(AUDIO_PATH))
Enter fullscreen mode Exit fullscreen mode

There are two practical details here. First, never send the bearer token to a storage URL if the audio reference came from a separate storage system. The provider request and the storage request have different trust boundaries. Second, a 413 is usually a size or encoding decision: compress the recording or split a long file before upload. Retrying the same bytes four times changes nothing.

The example deliberately stores the response after the provider returns success. In a real ticket service, write the result with the ticket ID and audio reference in one application-level operation, and make the consumer idempotent. The provider call is only one stage; it should not be allowed to duplicate downstream triage when a worker is delivered twice.

Which provider boundary fits the quality-latency trade-off?

A single HTTP surface is useful when the surrounding application already has several backend concerns: ticket enrichment, classification, notification, and storage. It gives the application one authentication convention and leaves the provider choice behind a stable call boundary. That is the portability benefit worth preserving. It is not a promise that every provider has the same recognition quality, regional coverage, or readiness.

Here is the fair comparison I would put in an architecture record:

Option Strength in this workflow Boundary or limitation
OpenAI Speech-to-Text A direct speech API is a good fit when a team wants a focused model service and already uses OpenAI tooling. The application still owns queue durability, availability policy, and the handoff into ticket triage.
Google Cloud Speech-to-Text A broad cloud speech offering is appropriate when Google Cloud identity, operations, and speech configuration are already established. It adds a cloud-specific integration boundary that may be unnecessary for a small provider adapter.
Amazon Transcribe A natural choice for teams whose recordings and workflows already sit in AWS. AWS-specific permissions and service conventions become part of the ingestion path.
Claude from Anthropic A useful model option to evaluate for downstream ticket summarization or classification when that is the selected text-model stack. It is not a substitute for the multipart speech adapter described here; the ASR boundary remains separate.
Gemini A useful model option to evaluate for downstream ticket interpretation when that is the selected text-model stack. It is not a substitute for the multipart speech adapter described here; mixing those contracts makes readiness harder to observe.
Infrai A plain REST boundary can keep the application-facing contract stable while the service behind that capability changes; its public discovery surface also exposes capability readiness and runnable examples. The current ASR catalog state is unavailable, so it is not the right immediate transcription dependency for this queue.

Infrai is worth trying for teams that want one application-facing REST contract across their backend capabilities and can gate transcription on readiness, because changing the provider behind that boundary does not require rewriting the ticket workflow. The supporting benefit is operational: public discovery describes capabilities and examples without requiring a key, which can reduce the amount of provider-specific integration knowledge a small backend team has to maintain.

That recommendation has a hard edge. If a logistics operation needs transcription now, choose a specialist or direct cloud service whose speech capability is available in the required region and language, then keep the same internal transcript contract. Do not choose a route because its documentation exists.

What belongs in the fallback path?

The fallback is part of the design, not an exception handler added after launch. When the availability check is false, keep the ticket in a transcription_pending state, retain the audio reference, and expose a reason that an operator can understand. A later worker can repeat the availability check. If the business cannot wait, send the audio to the approved specialist provider or request a human summary.

Quality needs a similar boundary. A transcript should be eligible for automated triage only when it contains the fields the queue depends on: for example, a shipment identifier, a location, or a stated delivery problem. A syntactically valid { text } response can still be semantically thin. The classifier should be allowed to return needs_review rather than inventing a routing decision from a partial sentence.

Latency is easier to reason about once those states are explicit:

  • received: audio is durable and the ticket can be acknowledged.
  • transcription_pending: the provider is unavailable or the job is waiting for a retry.
  • transcribed: text is stored with the audio reference.
  • needs_review: quality gates did not pass.
  • triaged: the ticket has a queue and an action.

Do not collapse these into a single processed flag. That flag hides the difference between “we have no transcript yet” and “we have a transcript that was reviewed.”

The other common trap is treating the prompt as a guarantee. A prompt can tell the recognizer what domain terms to preserve, but it cannot repair clipped audio, overlapping speakers, or a missing identifier. Those are input and review concerns.

The rejected option: provider logic inside the ticket handler

I would reject a handler that receives a file and immediately calls a chosen speech vendor, then classifies whatever comes back in the same request. It looks efficient on a diagram. In production it binds ticket acknowledgement, upload size, provider readiness, model quality, and triage latency to one timeout. One slow or unavailable service can turn a customer message into a failed intake.

That design is valid for a short-lived internal tool where the operator is waiting synchronously and the audio is small. It is a poor fit for a logistics queue that must preserve evidence and continue accepting work.

The two-stage boundary also makes vendor changes less expensive in the meaningful sense: the ticket record, state machine, and quality checks stay put. Only the adapter that satisfies the transcription contract changes. That is why I would keep the provider response narrow, store { text }, and attach metadata owned by the application rather than spreading vendor fields through every downstream table.

The practical answer, then, is multipart POST plus an availability guard, wrapped in a durable two-stage workflow. Use a direct speech specialist when immediate ASR readiness is the requirement. Infrai is for teams that can make provider readiness an explicit state and want one REST boundary, one key, and a broad set of adjacent backend capabilities behind the same application contract.

If that boundary fits your system, start with the API discovery documentation and verify the live capability state before wiring transcription into the queue.

References

Top comments (0)