Skip to content

DocsModalities

Audio & speech

Text-to-speech with POST /v1/audio/speech and speech-to-text with POST /v1/audio/transcriptions: voices, formats, timestamps, and billing.


INFRO is in private early access. Running these examples requires an early-access account, an active trial or subscription entitlement, and your own provider connection. Self-serve purchasing is not generally open. See Early access.

Two endpoints cover audio. /v1/audio/speech turns text into spoken audio, and /v1/audio/transcriptions turns an uploaded recording into text. Both are synchronous — there is no job to poll, unlike video.

The shapes differ from the rest of the API in one way each: speech returns raw audio bytes rather than JSON, and transcription takes a multipart/form-data upload rather than a JSON body. Both are served by connections whose provider kind speaks OpenAI's audio API — OpenAI, Azure OpenAI, Groq, and OpenAI-compatible endpoints.

Endpoints
POST https://api.infro.io/v1/audio/speech
POST https://api.infro.io/v1/audio/transcriptions

Text to speech

modelstringrequired
A speech model, e.g. openai/tts-1-hd, or elevenlabs/eleven-v3, cartesia/sonic-3, or minimax/speech-2.6 where one of your connections serves them through an OpenAI-compatible speech API. Latency, voice range, and language coverage differ sharply — compare them in the catalog.
inputstringrequired
The text to speak, up to 1,000,000 characters (the request body is limited to 1 MB; your provider may allow far less). Punctuation drives prosody, so keep sentence marks intact.
voicestringrequired
Voice ID from the model's voice list, up to 128 characters. IDs are vendor-specific and are not portable, which is why fallbacks across TTS vendors rarely works — a voice that exists on one vendor does not exist on another.
speednumber
0.5–2.0. Forwarded when set; the provider's default applies otherwise. Models without a rate control ignore it or refuse it.
formatstring
Container and codec of the audio: mp3 (default), wav, opus, flac, or pcm. Sent to the provider as its response_format; the exact sample rate and encoding of pcm are the provider's.
response_formatstring
audio (default) returns the audio bytes as the response body. url stores the audio and returns JSON with a signed link that stops working 24 hours after the request, like generated images.
curl https://api.infro.io/v1/audio/speech \
  -H "Authorization: Bearer $INFRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "elevenlabs/eleven-v3",
    "input": "Your deployment finished. Two of forty checks were skipped.",
    "voice": "aria",
    "format": "mp3"
  }' \
  --output speech.mp3

With "response_format": "url" the gateway reads the whole file from your provider, stores it, and answers with JSON. audio.url is signed and stops working at audio.expires_at. Usage is billed the same way in both modes, and the X-INFRO-* headers are sent on this response too.

json
{
  "id": "req_7d2e4b90",
  "created": 1755950000,
  "model": "openai/tts-1-hd",
  "route": "primary",
  "route_name": null,
  "provider": "openai",
  "connection": "conn_4f10a2c7",
  "audio": {
    "url": "https://api.infro.io/media/aud/2026-09-24/org_.../req_7d2e4b90.mp3?exp=1756036400&sig=...",
    "expires_at": "2026-09-25T12:00:00.000Z",
    "mime_type": "audio/mpeg"
  },
  "usage": {
    "characters": 58,
    "cost": 0.00174,
    "price_source": "list"
  }
}

Streaming playback

INFRO passes the provider's audio body through as it arrives, with the matching Content-Type. There is no separate streaming parameter — read the body as a stream instead of buffering it, as the Python tab above does, and playback can start as soon as your provider sends the first bytes.

Because the body is audio, the INFRO fields travel as response headers: X-INFRO-Cost (the estimated provider cost in USD, empty when the model is unpriced), X-INFRO-Route, X-INFRO-Provider, and X-INFRO-Request-Id.

mp3 and pcm are the practical choices for play-while-downloading. wav writes a length header some players wait for, and flac has to be decoded in full before it is useful, so both are better suited to files you save rather than play live.

Speech to text

filefilerequired
The recording, sent as multipart/form-data. mp3, wav, m4a, flac, ogg, and webm are accepted, up to 25 MB; a larger file is refused with 400 invalid_request_error, and a request body over 26 MB with 413 request_too_large before it is read.
modelstringrequired
A transcription model, e.g. openai/whisper-v3-turbo, or deepgram/nova-4 or assemblyai/universal-3 where one of your connections serves them through an OpenAI-compatible transcription API — see the catalog.
languagestring
ISO 639-1 hint, e.g. en or de, forwarded to the provider. Omit to let the provider detect it.
timestampsboolean
When true, INFRO asks the provider for segment timestamps (verbose_json with segment granularity) and the response includes a segments array with start and end offsets in seconds.
diarizeboolean
Implies timestamps. INFRO does not send a diarization option to the provider; a segment carries a speaker label only if the provider's response includes one.
curl https://api.infro.io/v1/audio/transcriptions \
  -H "Authorization: Bearer $INFRO_API_KEY" \
  -F file=@standup.m4a \
  -F model="deepgram/nova-4" \
  -F language="en" \
  -F timestamps=true \
  -F diarize=true

Transcription response

json
{
  "text": "Deploys are green. Two checks were skipped on the mobile build.",
  "language": "en",
  "model": "deepgram/nova-4",
  "route": "primary",
  "route_name": null,
  "provider": "openai-compatible",
  "connection": "conn_4f0a9d17",
  "segments": [
    {
      "id": 0,
      "start": 0.0,
      "end": 4.8,
      "speaker": "speaker_0",
      "text": "Deploys are green."
    },
    {
      "id": 1,
      "start": 4.8,
      "end": 12.4,
      "speaker": "speaker_1",
      "text": "Two checks were skipped on the mobile build."
    }
  ],
  "usage": {
    "seconds": 12.4,
    "cost": 0.0014,
    "price_source": "custom"
  }
}

text is always present and is the full transcript. language is what the provider reports, or null. segments appears only when timestamps (or diarize) is true, and speaker only when the provider labels speakers. route, provider, and connection name where the request ran; usage.cost is null when the model is unpriced.

INFRO does not store synthesized audio, input text, uploaded recordings, or transcripts from these two endpoints; it records request metadata (model, provider, status, duration or characters, estimated cost). See Privacy & data.

Billing

EndpointUnitWhere cost is reported
/v1/audio/speechPer 1M characters of inputThe X-INFRO-Cost response header, and the request log
/v1/audio/transcriptionsPer hour of audio, metered by the secondusage.seconds and usage.cost in the response

Providers typically bill speech per character, and INFRO's estimate counts every character of input, including whitespace and punctuation. Transcription is estimated on the duration the provider reports, so silence and music in a recording count the same as speech — trim dead air before uploading if you are processing hours of it. Vendor list rates are in the catalog.

Sound effects and music

POST /v1/audio/sound-effects and POST /v1/audio/music take { model, prompt, duration_seconds?, response_format? }; music also takes instrumental and lyrics where the model does (MiniMax sings the lyrics it is given and needs them; Lyria makes instrumental clips of a fixed length). Each model's lengths are checked before the call — ElevenLabs sound effects 0.5–30 s, Stable Audio 2.5 1–190 s — and a model that sets its own length refuses duration_seconds. The default answer is JSON with a signed link to the file, kept 24 hours; response_format: "audio" returns the file itself with its cost in X-INFRO-Cost. Priced per second, or per track. Music licences are the provider's own and narrower than "commercial use" on some plans; read them before you publish.

The text of a speech request, and the prompt of a sound or music request, passes your organization's input guardrails like a chat message: a blocking rule refuses it before any call, and a redacting rule changes what the provider receives.

Long recordings

Transcription holds the connection open for the whole job, and a provider that does not answer within your connection's timeout counts as a failed attempt. Uploads are also capped at 25 MB. Split anything past about an hour into chunks on silence boundaries, transcribe them in parallel, and concatenate the text — offsetting each chunk's segments by its start time keeps timestamps continuous. Status codes and retry guidance are in Errors, and per-key throughput in Rate & spend limits.

↑ ↓ navigate↵ open/ anywhere in docs