Build
Build with audio.
Give your app a voice. Turn recordings into text. Ask questions about what was said.
Three ways to work with sound
Transcribe
Turn a recording into searchable text, with timestamps when available.
Understand
Ask for a summary, action items or answers about the recording.
File-based requests, through one OpenAI-compatible API. Speech and transcription return complete files or text, without streaming or realtime.
Start with something useful
Create a key, add credit, and set it in your terminal. Your key must allow the model used in the example.
export MINIROUTER_API_KEY="paste-your-api-key-here"Choose a recipe, then edit its text or supply your own recording. These are editable code examples.
Welcome someone in
Give an onboarding screen a short spoken welcome.
- You supply
- Text and access to openai/tts-1.
- You get
- speech.mp3 and response.headers in your current folder.
curl --fail-with-body --retry 0 --max-time 120 \
https://api.minirouter.sh/v1/audio/speech \
-H "Authorization: Bearer $MINIROUTER_API_KEY" \
-H 'Content-Type: application/json' \
-D response.headers --output speech.mp3 \
--data '{
"model": "openai/tts-1",
"input": "Welcome back. Your workspace is ready. Let us get started.",
"voice": "alloy",
"response_format": "mp3"
}'Copy a recipe into your terminal. Selecting one here makes no API call.
Keep the input small
- Speech input
- 4,096 characters
- Audio upload
- 8 MiB
- Audio duration
- 3,600 s
- Speech & transcription
- 1 attempt
Upload WAV, FLAC, Ogg, MP4/M4A or MP3. WebM is not supported; Microsoft transcription also excludes MP4/M4A. Speech and transcription do not use Auto, Fusion or fallback model lists.
From hold to settled charge
Credit is reserved before the request. After the provider reports usage, the charge settles and unused credit returns to your balance.
If the provider reports the same duration, only that usage is charged.
- Settled charge
- 2.49 s × rate
- Released credit
- 0.51 s × rate
- ReserveSpeech uses the character count. Transcription rounds the file duration up to a second. Audio adds 32 prompt tokens per second, alongside other input and reserved output tokens.
- SettleActual provider usage determines the final charge. The chart illustrates transcription at a per-second rate.
- Check Request logsKeep
x-minirouter-request-idto find the settled charge. Unused reserved credit is released.
Use the voice catalog for current rates, including the MiniRouter fee.
Pricing fields and receipts
Catalog units, credit holds and Request logs exports.
Speech models price per character (pricing.speech_character in the catalog, shown per 1M characters). Transcription models price per second (pricing.transcription_second, shown per minute). Both list prompt and completion as 0. Prices include the MiniRouter fee.
Speech holds are exact: characters × the per-character rate. Transcription holds read the duration from the uploaded file and reserve it, rounded up to a second, × the per-second rate. Audio contributes 32 prompt tokens per second to the hold, alongside other input and reserved output tokens. Each hold settles to the provider's reported usage and the rest is released. Request logs rows show speech_characters or transcription_seconds with zero tokens; the CSV export carries both columns.
Text to speech
Send text and a voice to /v1/audio/speech. The response is an audio file, ready to save and play.
Speech body, voices and SDKs
6 output formats, provider voices, Node.js and Python.
Body: model, input (up to 4,096 characters), optional voice, response_format (mp3, wav, opus, pcm, aac, flac; mp3 by default), speed and instructions. The response is the audio file with that format's Content-Type. No streaming.
Voices are per provider: OpenAI alloy, ash, coral, echo, fable, nova, onyx, sage, shimmer; Microsoft en-US-Harper, en-US-Nova, en-US-Davis, en-US-Jenny. An unknown voice returns 400.
// npm install openai@7.4.0
import OpenAI from 'openai';
import { writeFile } from 'node:fs/promises';
const client = new OpenAI({
baseURL: 'https://api.minirouter.sh/v1',
apiKey: process.env.MINIROUTER_API_KEY,
maxRetries: 0,
});
const response = await client.audio.speech.create({
model: 'openai/tts-1',
input: 'The quick brown fox jumps over the lazy dog.',
voice: 'alloy',
response_format: 'mp3',
});
await writeFile('speech.mp3', Buffer.from(await response.arrayBuffer()));
console.log(response.headers.get('x-minirouter-request-id'));# pip install openai==2.53.0
import os
from openai import OpenAI
client = OpenAI(
base_url='https://api.minirouter.sh/v1',
api_key=os.environ['MINIROUTER_API_KEY'],
max_retries=0,
)
raw = client.audio.speech.with_raw_response.create(
model='openai/tts-1',
input='The quick brown fox jumps over the lazy dog.',
voice='alloy',
response_format='mp3',
)
with open('speech.mp3', 'wb') as output:
output.write(raw.content)
print(raw.headers.get('x-minirouter-request-id'))Speech to text
Upload a recording to /v1/audio/transcriptions. Choose plain text, JSON or metadata with timestamps when available.
Uploads, response formats and SDKs
Multipart or base64, accepted containers, Node.js and Python.
Multipart: file, model, optional language (accepted but currently ignored upstream) and response_format (json default, text, verbose_json). Uploads are capped at 8 MiB.
JSON body: model, audio (base64), media_type, optional language and response_format. The same 8 MiB cap applies to the decoded audio, and a larger body is refused with 413. Write the body to a file so large recordings do not hit the shell's argument limit.
{
printf '{"model":"openai/whisper-1","media_type":"audio/wav","response_format":"json","audio":"'
base64 < meeting.wav | tr -d '\n'
printf '"}'
} > body.json
curl --fail-with-body --retry 0 --max-time 120 \
https://api.minirouter.sh/v1/audio/transcriptions \
-H "Authorization: Bearer $MINIROUTER_API_KEY" \
-H 'Content-Type: application/json' \
--data-binary @body.jsonjson returns { "text": "…" }. text returns the transcript as plain text. verbose_json returns:
{
"text": "The quick brown fox jumps over the lazy dog.",
"language": "en",
"duration": 2.49,
"segments": [{ "start": 0, "end": 2.49, "text": "The quick brown fox jumps over the lazy dog." }]
}language and duration can be null when the provider does not report them. segments can be empty; segment timestamps are included only when available.
Accepted containers: WAV, FLAC, OGG, MP4/M4A, MP3, up to 3,600 s; WebM is rejected. Microsoft transcription also rejects MP4/M4A.
// npm install openai@7.4.0
import OpenAI from 'openai';
import { createReadStream } from 'node:fs';
const client = new OpenAI({
baseURL: 'https://api.minirouter.sh/v1',
apiKey: process.env.MINIROUTER_API_KEY,
maxRetries: 0,
});
const result = await client.audio.transcriptions.create({
model: 'openai/whisper-1',
file: createReadStream('meeting.wav'),
response_format: 'verbose_json',
}).withResponse();
console.log(result.data.text, result.data.duration);
console.log(result.response.headers.get('x-minirouter-request-id'));# pip install openai==2.53.0
import os
from openai import OpenAI
client = OpenAI(
base_url='https://api.minirouter.sh/v1',
api_key=os.environ['MINIROUTER_API_KEY'],
max_retries=0,
)
with open('meeting.wav', 'rb') as audio:
raw = client.audio.transcriptions.with_raw_response.create(
model='openai/whisper-1',
file=audio,
response_format='verbose_json',
)
print(raw.parse().text)
print(raw.headers.get('x-minirouter-request-id'))Audio input on chat
Attach a recording as a file part with an audio/* data URL. Ask a model that supports audio to summarize it or answer questions.
Responses API and eligible models
input_file on /v1/responses; input_audio is rejected.
Send a recording to a language model as a file content part whose file_data is an audio/* data URL. Only models whose catalog input_modalities include audio accept it, for example google/gemini-3.6-flash and xiaomi/mimo-v2.6-flash. Containers: WAV, FLAC, OGG, MP4/M4A, MP3, up to 3,600 s per request. Audio is billed as ordinary prompt tokens at the model's input rate.
On /v1/responses use an input_file part:
{
printf '{"model":"xiaomi/mimo-v2.6-flash","max_output_tokens":256,"input":[{"role":"user","content":['
printf '{"type":"input_text","text":"Summarize this recording in one sentence."},'
printf '{"type":"input_file","filename":"clip.mp3","file_data":"data:audio/mpeg;base64,'
base64 < clip.mp3 | tr -d '\n'
printf '"}]}]}'
} > body.json
curl --fail-with-body --retry 0 --max-time 120 \
https://api.minirouter.sh/v1/responses \
-H "Authorization: Bearer $MINIROUTER_API_KEY" \
-H 'Content-Type: application/json' \
--data-binary @body.jsoninput_audio parts are rejected with 400.
Model availability
Find an eligible model and check your key permissions.
Speech and transcription models are listed in the voice catalog with type speech or transcription. Language models that accept audio list audio in architecture.input_modalities. Your API key must permit the model. Unavailable routes return HTTP 503 before a credit hold or provider call.
Endpoints
Four paths, one bearer key, supported request shapes.
| Method | Path | Format |
|---|---|---|
| POST | /v1/audio/speech | OpenAI speech body; raw audio bytes back |
| POST | /v1/audio/transcriptions | OpenAI multipart or a JSON body; text back |
| POST | /v1/chat/completions | file content part with an audio/* data URL |
| POST | /v1/responses | input_file part with an audio/* data URL |
Use a MiniRouter API key as a bearer token. These examples name a concrete model that accepts the input.
Speech and transcription return complete files or text. These two endpoints do not support streaming, realtime, saved presets, Auto, Fusion or fallback model lists.
Chat and Responses accept inline audio files on eligible models. Saved presets and supported model fallbacks follow the normal routing rules; every selected route must accept audio. input_audio parts, the chat audio field and audio output in modalities are rejected. Audio is not supported with provider keys (BYOK).
Errors
400, 402, 404 and 413, and what each means.
| Status | Cause |
|---|---|
| 400 | Invalid body, unknown voice or format, input over 4,096 characters, unsupported container, audio over 3,600 s, input_audio part, a model that does not accept audio |
| 402 | Insufficient credit for the hold |
| 404 | Unknown model |
| 413 | Upload over 8 MiB |
Disable automatic retries: a lost response may still incur cost. Keep x-minirouter-request-id and read the settled charge in Request logs.