Speech APIs

Use Mira TTS with an OpenAI-compatible speech client

Mira TTS answers OpenAI's POST /v1/audio/speech. Set the base URL to https://sandbox.voice.miraiminds.co/v1, the model to mira-tts and a Mirai voice, and always send response_format: the API does not do MP3, and leaving it out gives you raw 48 kHz PCM. Everything else in your OpenAI client can stay as it is.

By Mirai Minds · Updated

What changes in the client configuration?

Three values change: the base URL, the model and the voice. Keep the API key on your server and read it from your secret store or environment. Never put a secret key in browser JavaScript or a mobile app.

Setting Mirai value
Base URL for an OpenAI client https://sandbox.voice.miraiminds.co/v1
Request path /audio/speech, relative to that base URL
Model mira-tts (the older mira-tts-v51 still works as an alias)
Voice ashu, neha, shruti or sameer; neha if you leave it out
Output Always set response_format; see the table below
Input Up to 2,000 characters per request

Mira TTS is one of Mirai’s model APIs, alongside speech recognition, a language model and voice agents, and it costs ₹1.80 per 1,000 characters. Building a full phone agent instead? Agents start at ₹1 a minute at volume; see AI calling.

To get a key, open the sandbox, go to Developers and press Create key. You don’t need an access code. The first time, the console asks for your work email and sends a confirmation link. The sk_live_ key is shown once, and the same key works for TTS and the rest of the v2 API. If you would rather keep every URL under /v2, the same handler is mounted at POST /v2/tts, which also accepts text in place of input.

How do you make a first speech request?

This request generates speech and saves it to a file. It spends a few paise from your wallet. Set MIRA_API_KEY in your shell first; the example contains no credential.

curl --fail-with-body \
  https://sandbox.voice.miraiminds.co/v1/audio/speech \
  -H "Authorization: Bearer $MIRA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "mira-tts",
    "voice": "neha",
    "response_format": "wav",
    "input": "नमस्ते, आपका ऑर्डर तैयार है।"
  }' \
  --output speech.wav

On success the body is the audio. With --fail-with-body, an error response is written to speech.wav instead, so read it before you try to play the file.

Which response_format should you ask for?

response_format You get Streams?
pcm Raw signed 16-bit little-endian mono, no header Yes
mulaw (also ulaw, pcm_mulaw) Raw G.711 μ-law, one byte per sample Yes
alaw (also pcm_alaw) Raw G.711 A-law, one byte per sample Yes
wav A playable 16-bit mono file with a header No, it arrives once synthesis finishes

sample_rate takes 8000, 16000, 22050, 24000, 44100 or 48000. μ-law and A-law come at 8000 or 16000 only. The default is 48000 for pcm and wav and 8000 for μ-law and A-law. You can also put the rate in the format, as in pcm_16000 or ulaw_8000. If you do, leave sample_rate out or send the same value, because a conflicting one returns a 400.

For a phone line, ask for the line’s format and forward each chunk as it arrives. Most lines carry 8 kHz μ-law, so ulaw_8000 needs no decoding or resampling on your side.

How do you stream with the OpenAI Python SDK?

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["MIRA_API_KEY"],
    base_url="https://sandbox.voice.miraiminds.co/v1",
)

with client.audio.speech.with_streaming_response.create(
    model="mira-tts",
    voice="ashu",
    response_format="pcm",
    input="चलिए, आपका order कल deliver होगा।",
    extra_body={"sample_rate": 16000},
) as response:
    print(response.headers.get("x-sample-rate"), response.headers.get("x-audio-encoding"))
    with open("speech_16k.pcm", "wb") as out:
        for chunk in response.iter_bytes():
            out.write(chunk)  # raw s16le mono at 16 kHz; feed a player here instead

We ran this with openai 1.109.1 against the sandbox on 16 October 2026. It printed 16000 pcm_s16le and wrote 16 kHz raw PCM. sample_rate is not an OpenAI parameter, which is why it goes in extra_body.

To play streamed audio, consume chunks as they arrive. Waiting for the whole response before playback throws away the reason to stream. For a live conversation, the WebSocket endpoint is a better fit than one request per sentence: one connection per call, text sent as your model writes it, and one message to stop a reply when the caller interrupts.

What comes back in the headers?

Every 200 carries its own accounting:

Header Meaning
X-Request-Id The request’s ID (tts_…), also the row in your request log
X-Chars-Billed Characters counted for billing
X-Cost-Paise What the request costs once it completes
X-Sample-Rate The sample rate you received
X-Audio-Encoding pcm_s16le, mulaw or alaw
X-Mira-Model The build that produced the audio, such as miraiminds/mira-tts-v53

Raw audio has no header, so read X-Sample-Rate and X-Audio-Encoding before you play it. Playing μ-law as PCM, or 8 kHz audio as 48 kHz, gives you noise or audio at the wrong speed. Pin X-Mira-Model in your test results when you compare quality across releases. GET /v2/tts/requests lists every request with its status, first_byte_ms, total_ms and cost.

What can break in an existing OpenAI TTS adapter?

Adapters often apply OpenAI’s choices before any HTTP request goes out. Check each of these before you call the integration done:

  • The adapter may reject neha or shruti because it validates against OpenAI’s fixed voice list.
  • It may request MP3 unless you override the format.
  • It may label the audio with an assumed rate, such as 24 kHz, instead of the rate in X-Sample-Rate.
  • It may buffer the whole response even though the server streams.

On Pipecat, the pipecat-mirai package replaces the OpenAITTSService workaround and asks for audio at your pipeline’s rate; see the Pipecat guide. For other frameworks, follow the instructions for the version you have installed and test the audio you play.

How should you handle a failed request?

Errors use the same JSON envelope as the rest of the API and carry X-Request-Id, so quote it when you ask for help.

Status code What to do
400 invalid_request Fix the field the message names: empty or over-long input, unknown voice, unsupported format or rate
401 unauthorized Check the Authorization: Bearer sk_live_… header
402 insufficient_balance Top up; nothing was synthesised or charged
404 model_not_found Use mira-tts
429 at_capacity or rate_limited Wait for Retry-After, then send again
502 upstream_error Retry; nothing was charged
503 fleet_offline Wait for Retry-After; nothing was charged

Put an upper bound on retries in a live call. A sentence that arrives after the conversation has moved on is worse than one that never plays. Keep raw customer text out of general logs unless your data policy covers it; Mirai’s request log stores only the first 48 characters of each input. Use the latency guide to tell a slow request from a playback problem.

Common questions

What base URL and model should an OpenAI speech client use?

Base URL https://sandbox.voice.miraiminds.co/v1 and model mira-tts. Pick a voice from ashu, neha, shruti and sameer, and set response_format explicitly.

Can I keep an OpenAI client's default MP3 output?

No. mp3 returns a 400 that lists the accepted values: pcm, wav, mulaw (or ulaw) and alaw, plus forms with the rate built in such as pcm_16000 and ulaw_8000. Without response_format you get raw PCM at 48 kHz.

How do I get audio at 8 or 16 kHz through the OpenAI SDK?

sample_rate is not an OpenAI parameter, so pass it through the SDK's extra_body argument, or use a response_format with the rate in it, such as pcm_16000. Read X-Sample-Rate on the response to confirm what you got.

Will any framework with an OpenAI TTS adapter work unchanged?

Check the adapter's voice list, its default format and the sample rate it assumes. A matching request shape does not mean a framework's defaults match Mirai's audio. On Pipecat, use the pipecat-mirai package instead of OpenAITTSService.

Sources and API references

References reviewed on . Measurement dates and experimental limits are stated in the article. Check current API and runtime documentation when integrating.

Try the sandbox