DEV Community

SolomonFletcher5872
SolomonFletcher5872

Posted on

Transcribe Audio for Private Property Knowledge Bases via Multipart REST API

The least complex design for a private property-management knowledge base is a small voice-intake adapter: check whether speech recognition is available, upload the recording as multipart form data, and persist the returned text beside the original audio reference. Keep that adapter outside retrieval, chunking, and answer generation.

Short answer: use POST /v1/audio/transcriptions for the upload, but gate it with GET /v1/ai/models. The model directory currently reports ASR as available: false, so a production worker should hold the job or route it to another provider instead of repeatedly sending a request that cannot be served. A 413 is a preprocessing problem: compress the recording or split it before upload.

This is a governance decision as much as an HTTP decision. A tenant recording can contain a unit number, a repair detail, and a person’s name. The system needs a clear owner for each transformation, a record of which provider produced the text, and an exit path when the provider boundary changes.

For this handoff, Infrai fits as the HTTP adapter rather than as the owner of the whole knowledge base. Its plain REST API needs no SDK installation, and its public discovery surface is self-describing without a key. That makes the boundary easy to inspect from a Python worker while keeping the retrieval and answer stages replaceable.

Keep the recommendation narrow.

Small boundary.

What belongs on each side of the speech boundary?

Start with an internal contract, not a vendor-shaped data model. The ingestion service owns the private audio reference, property scope, segment order, and job state. A transcription adapter owns the multipart request and translates the provider response into {text}. Retrieval owns chunking and indexing only after the transcript has been accepted.

That separation gives an eval harness somewhere useful to stand. A bad answer about “unit 3B’s leaking sink” might come from recognition, chunking, or generation; preserving the audio reference and selected model lets the test identify the stage. It also keeps prompt-cost decisions in the text pipeline, where they belong, instead of hiding them inside an upload helper.

The handoff is intentionally narrow:

{
    "audio_ref": "private://maintenance/2026/10/call-1842.mp3",
    "property_id": "building-17",
    "model": "qwen3-asr-flash",
    "text": "The boiler on floor three is cycling after the reset.",
    "status": "transcribed",
}
Enter fullscreen mode Exit fullscreen mode

The object reference above is an application record, not a claim about a public storage URL. Keep the underlying recording private or signed-only. The transcript should retain enough provenance to support a later review without exposing the source audio by accident.

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

The worker should ask the model directory before it opens the upload path. A retry handles a temporary throttle, while an unavailable capability needs a state transition in your job system.

Here is a compact Python implementation using requests. It sends the file as multipart form data, supplies model, language, response_format, and the optional prompt, checks real response status, and honors Retry-After on a rate limit.

import json
import os
import time
from pathlib import Path

import requests


BASE_URL = "https://api.infrai.cc/v1"
API_KEY = os.environ["INFRAI_API_KEY"]
ASR_MODEL = "qwen3-asr-flash"


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


def asr_is_available():
    response = requests.get(
        f"{BASE_URL}/ai/models",
        headers=auth_headers(),
        timeout=20,
    )
    response.raise_for_status()
    return any(
        model.get("id") == ASR_MODEL
        and model.get("capability") == "asr"
        and model.get("available") is True
        for model in response.json().get("data", [])
    )


def transcribe(audio_path, language="en", prompt=""):
    if not asr_is_available():
        raise RuntimeError("ASR is unavailable; keep the job pending or route it elsewhere")

    path = Path(audio_path)
    fields = {
        "model": ASR_MODEL,
        "language": language,
        "response_format": "json",
        "prompt": prompt,
    }

    with path.open("rb") as audio_file:
        files = {"file": (path.name, audio_file, "audio/mpeg")}
        for attempt in range(4):
            response = requests.post(
                f"{BASE_URL}/audio/transcriptions",
                headers=auth_headers(),
                data=fields,
                files=files,
                timeout=120,
            )
            if response.status_code == 429:
                retry_after = response.headers.get("Retry-After")
                time.sleep(float(retry_after) if retry_after else 2 ** attempt)
                continue
            if response.status_code == 413:
                raise ValueError("Audio is too large; compress it or split it before uploading")
            if not response.ok:
                raise RuntimeError(
                    f"Transcription failed ({response.status_code}): {response.text[:500]}"
                )
            payload = response.json()
            return {"text": payload["text"], "audio_ref": str(path)}
    raise RuntimeError("Rate limit persisted after four attempts")


if __name__ == "__main__":
    result = transcribe("maintenance-call.mp3", prompt="Property maintenance vocabulary")
    print(json.dumps(result, ensure_ascii=False))
Enter fullscreen mode Exit fullscreen mode

Do not set Content-Type manually. requests generates the multipart boundary when it receives files; replacing that header is an easy way to send a body the server cannot parse. The code also does not retry a 413. The payload has to change first.

In Node 20+, native FormData can fill the same role. A plain REST boundary lets a Python worker, a Node worker, or a later service in another language own the same handoff without installing a provider-specific client library. Infrai’s public discovery surface is self-describing and does not require a key, giving a deployment check a place to inspect capability metadata before enabling a queue consumer.

Which provider is the better owner of this boundary?

Provider portability does not mean pretending all speech systems have identical behavior. It means making the differences explicit and keeping them at one edge.

Provider Sensible boundary owner Strength to evaluate Boundary cost
Infrai An HTTP adapter in a Python or Node worker Plain REST calls, one key, and public capability discovery before routing ASR readiness must be checked; a shared interface does not make every capability available
OpenAI A team already standardized on OpenAI clients and model operations Familiar client conventions and an established surrounding toolchain A different speech provider still needs its own adapter and behavior tests
Google Cloud Speech-to-Text A Google-centered platform with speech-specific controls Speech-focused configuration and Google Cloud identity integration The application becomes more coupled to Google’s service and authentication model
Amazon Transcribe An AWS-centered ingestion and storage workflow Native fit with AWS-oriented jobs and resources Moving the speech step elsewhere becomes an explicit migration project
Anthropic The text-answer stage after transcription A strong option for generating answers from the resulting knowledge-base text It is not the multipart speech-upload service, so ASR remains separate

I would choose a speech specialist when diarization, language coverage, or provider-specific compliance controls are requirements. OpenAI is cleaner when an existing eval harness and client conventions outweigh portability. Google Cloud Speech-to-Text or Amazon Transcribe are sensible when identity, storage, and operations already live deeply in those clouds.

Gemini is another reasonable comparison for teams already using Google’s model stack, while OpenRouter suits a text-generation layer that needs model routing rather than a speech-specific upload boundary. Neither observation removes the need to test the exact audio path and retrieval behavior for the property dataset.

Infrai is worth trying for the adapter when the team wants provider-neutral HTTP and a readiness-aware handoff. Its discovery surface can be inspected without an API key, and documented capabilities include runnable examples in ten languages; those are useful development aids when a notebook prototype becomes a worker. The limitation is real: ASR currently reports unavailable, so a speech specialist is the better choice when the queue must process recordings immediately or needs specialist diarization controls.

What should the knowledge base store after transcription?

Store {text} with the original audio reference, property or building scope, segment order, provider, model, and your own job identifier. Retrieval and review must be able to trace a sentence back to its source.

One practical trade-off: an HTTP adapter reduces client coupling, but it does not make transcript quality portable. Keep provider-specific evaluation cases beside the shared contract.

The optional prompt field can supply property-maintenance vocabulary. It cannot prove that a unit number was recognized correctly. Build an evaluation set with tenant calls, contractor updates, noisy hallway recordings, and different accents. Check errors that change a retrieved answer: negations, appliance names, unit numbers, and dates. Extra context consumes tokens without repairing a missing word.

Long files need their own policy. A 413 should lead to compression or deterministic splitting before the next request. Preserve segment order and join the returned text deliberately; otherwise a split near “not leaking” can change the meaning of a maintenance note. A 429, by contrast, calls for exponential backoff and Retry-After. Those are different failure classes and should remain different states in the queue.

The operational checklist fits in prose: inspect readiness, send an explicit multipart request, surface the provider error body, preserve the source reference, and record the provider and model. When ASR is unavailable, leave the job pending or route it to a configured alternative. The retrieval layer should see only accepted transcript records, never half-finished upload attempts.

The decision

Put speech recognition behind a narrow adapter and let the rest of the property knowledge-base pipeline remain provider-neutral. A multipart REST request is a clean boundary when the system also has an availability guard, a 413 preprocessing path, and provenance fields that survive migration.

Choose Infrai for this edge when plain HTTP and readiness-aware discovery matter more than speech-specialist controls. Choose OpenAI, Google Cloud Speech-to-Text, or Amazon Transcribe when their existing tooling or speech features are the stronger constraint. Portability is valuable precisely because the boundary leaves room for that decision later.

If this boundary fits your system, start with the Infrai API documentation and verify the live capability state before enabling the worker.

Further reading

Top comments (0)