---
title: "Audio API"
description: "Speech, transcription and audio input: endpoints, limits and per-character / per-minute pricing."
canonical_url: "https://minirouter.sh/docs/audio"
markdown_url: "https://minirouter.sh/docs/audio.md"
last_updated: "2026-10-07"
---

# Audio API

Turn text into speech, recorded audio into text, and send audio to language models that accept it.

## Choose your audio path

- **Speak:** Text → Audio file. Give your app a voice with a short greeting or narration. Billed per character.
- **Transcribe:** Recording → Transcript. Turn a recording into searchable text, with timestamps when available. Billed per second.
- **Understand:** Recording + prompt → Model answer. Ask for a summary, action items or answers about the recording. Billed prompt + completion tokens.

## Starter examples

[Create a key](https://minirouter.sh/key), [add credit](https://minirouter.sh/dashboard/billing), and set it in your terminal. Your key must allow the model used in the example.

```sh
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. Each run is a paid request; keep automatic retries disabled because a timeout or lost response can still incur a charge.

### App greeting

Give an onboarding screen a short spoken welcome.

Requires: Text and access to openai/tts-1.

Expected result: speech.mp3 and response.headers in your current folder.

```sh
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"
}'
```

### Narration

Turn a product tip or learning card into an audio file.

Requires: Text and access to openai/tts-1.

Expected result: speech.mp3 and response.headers in your current folder.

```sh
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": "A small habit, repeated every day, can make a lasting difference. Start with one thing you can do today.",
  "voice": "nova",
  "response_format": "mp3"
}'
```

### Plain transcript

Make voice notes searchable or paste a transcript into your app.

Requires: meeting.wav in your current folder; access to openai/whisper-1.

Expected result: The transcript as plain text in your terminal.

```sh
curl --fail-with-body --retry 0 --max-time 120 \
  https://api.minirouter.sh/v1/audio/transcriptions \
  -H "Authorization: Bearer $MINIROUTER_API_KEY" \
  -F file=@meeting.wav \
  -F model=openai/whisper-1 \
  -F response_format=text
```

### Timestamps

Request segment timestamps for a transcript reader.

Requires: meeting.wav in your current folder; access to openai/whisper-1.

Expected result: JSON with text, duration, language and segments when supplied by the provider.

```sh
curl --fail-with-body --retry 0 --max-time 120 \
  https://api.minirouter.sh/v1/audio/transcriptions \
  -H "Authorization: Bearer $MINIROUTER_API_KEY" \
  -F file=@meeting.wav \
  -F model=openai/whisper-1 \
  -F response_format=verbose_json
```

### Summary

Send audio straight to a language model with a short summarization prompt.

Requires: clip.mp3 in your current folder; access to xiaomi/mimo-v2.6-flash.

Expected result: A chat completion containing a one-sentence summary.

```sh
{
  printf '{"model":"xiaomi/mimo-v2.6-flash","max_tokens":256,"messages":[{"role":"user","content":['
  printf '%s' '{"type":"text","text":"Summarize this recording in one sentence."},'
  printf '{"type":"file","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/chat/completions \
  -H "Authorization: Bearer $MINIROUTER_API_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @body.json
```

### Action items

Ask a language model for the actions mentioned in a recording.

Requires: clip.mp3 in your current folder; access to xiaomi/mimo-v2.6-flash.

Expected result: A chat completion listing action items, or saying none were mentioned.

```sh
{
  printf '{"model":"xiaomi/mimo-v2.6-flash","max_tokens":256,"messages":[{"role":"user","content":['
  printf '%s' '{"type":"text","text":"List the action items mentioned in this recording. Include owners only when explicitly named. If there are none, say so."},'
  printf '{"type":"file","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/chat/completions \
  -H "Authorization: Bearer $MINIROUTER_API_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @body.json
```

## Availability

Speech and transcription models are listed in the [voice catalog](https://minirouter.sh/models?voiceOnly=true) 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

| 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).

## Text to speech

```sh
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": "The quick brown fox jumps over the lazy dog.",
  "voice": "alloy",
  "response_format": "mp3"
}'
```

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.

```js
// 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'));
```

```python
# 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

```sh
curl --fail-with-body --retry 0 --max-time 120 \
  https://api.minirouter.sh/v1/audio/transcriptions \
  -H "Authorization: Bearer $MINIROUTER_API_KEY" \
  -F file=@meeting.wav \
  -F model=openai/whisper-1 \
  -F response_format=verbose_json
```

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.

```sh
{
  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.json
```

`json` returns `{ "text": "…" }`. `text` returns the transcript as plain text. `verbose_json` returns:

```json
{
  "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.

```js
// 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'));
```

```python
# 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 and responses

```sh
{
  printf '{"model":"xiaomi/mimo-v2.6-flash","max_tokens":256,"messages":[{"role":"user","content":['
  printf '%s' '{"type":"text","text":"Summarize this recording in one sentence."},'
  printf '{"type":"file","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/chat/completions \
  -H "Authorization: Bearer $MINIROUTER_API_KEY" \
  -H 'Content-Type: application/json' \
  --data-binary @body.json
```

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:

```sh
{
  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.json
```

`input_audio` parts are rejected with 400.

## Limits

- Speech input: 4,096 characters per request.
- Transcription and audio input uploads: 8 MiB raw and 3,600 s per request.
- Speech and transcription: one upstream attempt per request, no streaming or realtime.
- Speech and transcription do not use Auto, Fusion or fallback model lists.
- Chat and Responses audio input requires an audio-capable model and provider; supported model fallbacks must also accept audio.

## Pricing and holds

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.

## From hold to settled charge

A transcription example, using a recording measured at **2.49 seconds**:

1. **Reserve:** round the duration up to **3 seconds** and hold 3 × the model's per-second rate.
2. **Settle:** if the provider reports **2.49 seconds**, charge 2.49 × that rate.
3. **Release:** return the unused **0.51 × the per-second rate** to your available balance.

This is an illustration, not a fixed price. Read the current rate in the [voice catalog](https://minirouter.sh/models?voiceOnly=true) and the settled charge in Request logs.

## Errors

| 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.

## References

- [Model catalog](https://minirouter.sh/models?voiceOnly=true)
- [MiniRouter authentication](https://minirouter.sh/docs/authentication)
- [MiniRouter billing](https://minirouter.sh/docs/billing)
- [Errors](https://minirouter.sh/docs/errors)
