Two endpoints in the format of the OpenAI Audio API: one turns text into speech, the other turns a recording into text. The audio you send is processed in memory and never written to our servers. Moon Blur does not apply to the text read aloud or to the audio (X-Moon-Blur: ).
Read a text aloudLink to the Read a text aloud section
POST https://api.realmoon.ai/v1/audio/speech- Authentication: an
sk-moon-…key with theinferencescope (see Authentication). - Body: JSON, 256 KB at most.
- Response: the audio file itself.
ParametersLink to the Parameters section
| Parameter | Type | Required | Description |
|---|---|---|---|
model | string | yes | A speech model from GET /. |
input | string | yes | The text to read, 4,096 characters at most. Split a longer text into several requests. |
voice | string | no | The ID of one of the model’s voices: letters, digits, spaces and _ , 128 characters at most. Empty: the model’s default voice (default_voice in /v1/models). |
response_format | string | no | mp3 (default). Gemini voices also accept wav and pcm (response_formats in /v1/models). |
instructions | string | no | Reading style (tone, pace), 500 characters at most. Gemini voices only. |
speed | number | no | Only 1 is accepted. |
language | string | no | Accepted, with no effect. |
Any other parameter is rejected with 400 .
ExampleLink to the Example section
curl https://api.realmoon.ai/v1/audio/speech \
-H "Authorization: Bearer $MOONAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "mistralai/voxtral-mini-tts-2603", "voice": "fr_marie_neutral", "input": "Hello and welcome."}' \
-o hello.mp3import os
from openai import OpenAI
client = OpenAI(base_url="https://api.realmoon.ai/v1", api_key=os.environ["MOONAI_API_KEY"])
with client.audio.speech.with_streaming_response.create(
model="mistralai/voxtral-mini-tts-2603",
voice="fr_marie_neutral",
input="Hello and welcome.",
) as response:
print("Credits:", response.headers.get("x-moon-credits"))
response.stream_to_file("hello.mp3")import fs from 'node:fs';
import OpenAI from 'openai';
const client = new OpenAI({ baseURL: 'https://api.realmoon.ai/v1', apiKey: process.env.MOONAI_API_KEY });
const speech = await client.audio.speech.create({
model: 'mistralai/voxtral-mini-tts-2603',
voice: 'fr_marie_neutral',
input: 'Hello and welcome.',
});
fs.writeFileSync('hello.mp3', Buffer.from(await speech.arrayBuffer()));ResponseLink to the Response section
The response body is the audio file:
response_format | Content-Type | Content |
|---|---|---|
mp3 | audio/mpeg | MP3 |
wav | audio/wav | WAV, 16-bit, 24 kHz, mono |
pcm | audio/pcm | Raw PCM, 16-bit, 24 kHz, mono |
| Header | Value |
|---|---|
X-Moon-Credits | Credits charged for the request. |
X-Moon-Request-Id | The request ID, for /. |
X-Moon-Prompt-Storage | none. |
X-Moon-Blur | not_applied. |
Speech modelsLink to the Speech models section
List as of November 5, 2026; the current list, with prices, comes from GET /.
| Model | Default voice | Formats |
|---|---|---|
mistralai/ | fr_marie_neutral | mp3 |
mistralai/ | none | mp3 |
microsoft/ | fr-FR-Soleil:MAI-Voice-2.1-Flash | mp3 |
microsoft/ | none | mp3 |
elevenlabs/, eleven-multilingual-v2, eleven-v3 | none | mp3 |
fish-audio/, deepgram/, qwen/, hexgrad/ | none | mp3 |
google/, google/ | Sulafat | mp3, wav, pcm |
CostsLink to the Costs section
Most voices are billed per character of text read (billing: ). Gemini voices are billed on the text sent and on the length of the audio actually produced. The request holds the price of the text before the call; a request that fails or is cut off by our timeout costs nothing.
ErrorsLink to the Errors section
| HTTP | code | Meaning |
|---|---|---|
| 400 | missing_required_parameter | model or input is missing. |
| 400 | unknown_parameter | Unknown parameter. |
| 400 | unsupported_value | A response_format the model does not offer. |
| 400 | unsupported_parameter | speed other than 1, instructions on a non-Gemini voice, or a voice, language or format the model rejects (with a hint). |
| 400 | string_above_max_length | Text longer than 4,096 characters. |
| 400 | null | Malformed voice, instructions longer than 500 characters, invalid JSON body. |
| 404 | model_not_found | Unknown model, or one that does not serve this endpoint. |
| 413 | request_too_large | Body larger than 256 KB. |
| 429 | too_many_concurrent | Already 8 speech requests in flight for this workspace. |
| 429 | upstream_rate_limited | The model is overloaded: retry after Retry-After (10 seconds). |
| 502, 504 | upstream_error, upstream_timeout | The model failed, produced no audio, or did not respond in time. |
Transcribe a recordingLink to the Transcribe a recording section
POST https://api.realmoon.ai/v1/audio/transcriptions- Authentication: an
sk-moon-…key with theinferencescope. - Body:
multipart/only, with theform-data Content-Lengthheader. The file goes in thefilefield. - Response: the transcription, as JSON or plain text.
ParametersLink to the Parameters section
| Field | Type | Required | Description |
|---|---|---|---|
file | file | yes | The recording: wav, mp3, flac, m4a (or mp4), ogg, webm or aac. 25 MB and 20 minutes at most. |
model | string | yes | A transcription model from GET /. |
language | string | no | ISO 639-1 language code (fr, en…). Improves the transcription. |
prompt | string | no | Context or expected vocabulary (proper names, acronyms), 1,000 characters at most. |
response_format | string | no | json (default), text or verbose_json. |
temperature, timestamp_granularities[] | no | Accepted, with no effect. |
Any other field is rejected with 400 . A recording longer than 20 minutes is rejected (400 ): split it into pieces. For a compressed file, the duration counted is at least what its size implies.
ExampleLink to the Example section
curl https://api.realmoon.ai/v1/audio/transcriptions \
-H "Authorization: Bearer $MOONAI_API_KEY" \
-F model=mistralai/voxtral-mini-transcribe \
-F language=en \
-F file=@meeting.m4awith open("meeting.m4a", "rb") as audio:
transcript = client.audio.transcriptions.create(
model="mistralai/voxtral-mini-transcribe",
file=audio,
language="en",
)
print(transcript.text)const transcript = await client.audio.transcriptions.create({
model: 'mistralai/voxtral-mini-transcribe',
file: fs.createReadStream('meeting.m4a'),
language: 'en',
});
console.log(transcript.text);ResponseLink to the Response section
{
"text": "Hi everyone, we will start with the budget.",
"usage": { "type": "duration", "seconds": 312.4, "credits": 0 }
}The credits value is made up.
| Field | Type | Description |
|---|---|---|
text | string | The transcribed text. |
usage.type | string | Always duration. |
usage.seconds | number | The audio duration counted, in seconds. |
usage.credits | integer | Credits charged. |
task | string | With verbose_json: transcribe. |
language | string or null | With verbose_json: the detected or requested language. |
duration | number or null | With verbose_json: the duration in seconds. |
With response_format: , the response is the text alone, as text/plain. In every case, the X-Moon-Credits header gives the credits charged, and X-Moon-Request-Id the request ID.
Transcription modelsLink to the Transcription models section
List as of November 5, 2026; the current list, with prices per minute, comes from GET /.
mistralai/,voxtral-mini-transcribe mistralai/,voxtral-mini-transcribe-3-0 mistralai/voxtral-small-transcribe-3-0 openai/,whisper-large-v3-turbo openai/,whisper-large-v3 openai/,gpt-4o-mini-transcribe openai/gpt-4o-transcribe elevenlabs/,scribe-v2 deepgram/,nova-3 assemblyai/universal-3-6-pro nvidia/,parakeet-tdt-0.6b-v3 qwen/qwen3-asr-1.7b
CostsLink to the Costs section
Transcription is billed at the actual cost reported by the provider, based on the audio duration (or, failing that, the list price). Before the call, the request holds at least the price of one minute, with a margin. A transcription that fails costs nothing.
ErrorsLink to the Errors section
| HTTP | code | Meaning |
|---|---|---|
| 400 | invalid_content_type | Request sent as something other than multipart/. |
| 400 | missing_required_parameter | model or file is missing. |
| 400 | unknown_parameter | Unknown field. |
| 400 | unsupported_value | Unknown response_format. |
| 400 | unsupported_format | Audio format not recognized. |
| 400 | audio_too_long | Recording longer than 20 minutes. |
| 400 | unsupported_parameter | A language or format the model rejects (with a hint). |
| 400 | null | Malformed language, prompt longer than 1,000 characters, empty file, unexpected file field. |
| 404 | model_not_found | Unknown model, or one that does not serve this endpoint. |
| 411 | length_required | Upload without a Content-Length header. |
| 413 | file_too_large, request_too_large | File larger than 25 MB, or body too large. |
| 429 | too_many_concurrent | Already 4 transcriptions in flight for this workspace. |
| 429 | upstream_rate_limited | The model is overloaded: retry after Retry-After (10 seconds). |
| 502, 504 | upstream_error, upstream_timeout | The model failed or did not respond in time. |
Key, balance and spend cap errors, common to both endpoints, are in Errors.
LimitsLink to the Limits section
| Limit | Speech | Transcription |
|---|---|---|
| Size | 4,096 characters, 256 KB body | 25 MB file, 20 minutes of audio |
| Requests in flight per workspace | 8 | 4 |
| Wait for the model’s response | 85 seconds at most | 85 seconds at most |