POST /v1/audio/speech
The exact contract for Flux text to speech: JSON request fields, the rules for each speech model, the streamed audio response and its content types, receipt headers and every error code.
Turns text into spoken audio, streamed back as it is made. For a guided introduction, see Text to speech.
| Endpoint | POST https://api.fluxrouter.ai/v1/audio/speech |
| Request | application/json |
| Response | Audio bytes, streamed (chunked) |
| Auth | Authorization: Bearer <your Flux key> |
| Plan | Paid |
| Status | Rolling out |
The same route also answers at /audio/speech, without the /v1. Until speech is switched on for your account, it answers 404.
Request fields
| Field | Required | Type | Notes |
|---|---|---|---|
model | no | string | flux-voice-speak (the default) or flux-voice-speak-hd. |
input | yes | string | The text to say. Not empty, up to 4,096 characters. |
voice | yes | string | A voice id that belongs to model. Read them from GET /v1/audio/voices. |
response_format | no | string | mp3 (the default), opus, aac, flac, wav or pcm. |
speed | no | number | From 0.25 to 4. 1 is normal speed. |
instructions | no | string | Direction for tone and delivery. flux-voice-speak only. Up to 4,096 characters. |
stream_format | no | string | Leave it out, or send audio. sse is refused. |
Rules for each model
flux-voice-speak | flux-voice-speak-hd | |
|---|---|---|
| Voices | Every voice listed for it | alloy, ash, coral, echo, fable, nova, onyx, sage, shimmer |
response_format | All six | All six |
speed | Full range | Full range |
instructions | Yes | Refused with 400 if not empty |
| Billed characters | Input length; divided by speed when speed is below 1, rounded up | Input length |
The voice list can also carry voices on other speech models. Those models are not covered here yet.
Response
A 200 streams the audio as it is made, in chunks, with no Content-Length. The headers arrive with the first chunk.
response_format | Content-Type | Notes |
|---|---|---|
mp3 | audio/mpeg | |
opus | audio/ogg | Opus in an Ogg container. |
aac | audio/aac | |
flac | audio/flac | |
wav | audio/wav | |
pcm | audio/pcm | Raw samples, no header: 24 kHz, 16-bit, signed little-endian, mono. |
If audio stops arriving for more than 30 seconds partway through, the stream ends early. The status and headers have already been sent by then, so check that the clip is complete if that matters to you.
Response headers
| Header | Meaning |
|---|---|
x-flux-routed-model | The model id that served |
x-flux-billed-chars | The characters billed; see the table above |
x-flux-cost-usd | What this request cost, in US dollars |
cache-control | no-store |
Billing happens when the first byte of audio arrives. Anything that fails before that is not billed; once audio has started, the charge stands even if the client disconnects.
Limits
| Limit | Value |
|---|---|
input | 4,096 characters |
instructions | 4,096 characters |
| Wait for the first audio | 30 seconds, then 504 |
| Gap between chunks | 30 seconds |
| Requests | 60 requests a minute per account |
| Audio | 36,000 seconds of audio an hour per account, shared with transcription; speech counts an estimate from its length |
Errors
Errors are JSON, never audio, in this envelope:
{
"error": {
"message": "voice for flux-voice-speak-hd must be one of [...]",
"type": "invalid_request_error",
"param": "voice"
}
}
code and param are present only when they apply.
| Status | code | Meaning | Billed |
|---|---|---|---|
400 | none, param set | A field is wrong: model, input, voice, response_format, speed, instructions, stream_format or body. param names it and the message says what is allowed. | No |
400 | invalid_request_error | The speech service refused the request after it passed our checks. Change the text or voice and retry. | No |
401 | none | The key was missing or not accepted, or the model is not on the key's model list. | No |
402 | premium_locked | The key's plan does not include speech. | No |
402 | credit_unresolved | Your credit balance could not be checked. Retry shortly. | No |
402 | another code | A spend cap or prepaid balance stopped the request. See Spend caps. | No |
404 | none | Speech is not switched on for your account yet. | No |
429 | rate_limit_error | Over the per-account rate or hourly audio budget, or the speech service is busy. Wait, then retry. | No |
502 | capability_unavailable | Speech failed, or no audio came back. Retry. | No |
504 | capability_unavailable | No audio arrived in time. Retry. | No |
Fixes for each are on Text to speech problems.
Example requests
curl https://api.fluxrouter.ai/v1/audio/speech \
-H "Authorization: Bearer sk-YOUR-FLUX-KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "flux-voice-speak",
"input": "Your build finished. All tests passed.",
"voice": "nova",
"response_format": "mp3"
}' \
--output done.mp3
from openai import OpenAI
client = OpenAI(
api_key="sk-YOUR-FLUX-KEY",
base_url="https://api.fluxrouter.ai/v1",
)
# Stream the audio to a file as it arrives, rather than holding it in memory.
with client.audio.speech.with_streaming_response.create(
model="flux-voice-speak",
input="Your build finished. All tests passed.",
voice="nova",
response_format="mp3",
) as response:
print(response.headers.get("x-flux-cost-usd"))
response.stream_to_file("done.mp3")
import fs from "node:fs";
import OpenAI from "openai";
const client = new OpenAI({
apiKey: "sk-YOUR-FLUX-KEY",
baseURL: "https://api.fluxrouter.ai/v1",
});
const response = await client.audio.speech.create({
model: "flux-voice-speak",
input: "Your build finished. All tests passed.",
voice: "nova",
response_format: "mp3",
});
fs.writeFileSync("done.mp3", Buffer.from(await response.arrayBuffer()));
Low-latency pcm, with the receipt headers printed and the audio saved:
curl -sS -D - https://api.fluxrouter.ai/v1/audio/speech \
-H "Authorization: Bearer $FLUX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "flux-voice-speak",
"input": "Three new messages. The first is from Priya.",
"voice": "coral",
"response_format": "pcm",
"speed": 1.1,
"instructions": "Friendly and quick, like a helpful assistant."
}' \
--output reply.pcm
With TypeScript's fetch, streaming to a file:
import fs from "node:fs";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
const res = await fetch("https://api.fluxrouter.ai/v1/audio/speech", {
method: "POST",
headers: {
Authorization: "Bearer sk-YOUR-FLUX-KEY",
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "flux-voice-speak",
input: "Your report is ready.",
voice: "nova",
response_format: "opus",
}),
});
if (!res.ok) throw new Error(JSON.stringify(await res.json()));
console.log(res.headers.get("x-flux-cost-usd"));
await pipeline(Readable.fromWeb(res.body!), fs.createWriteStream("report.ogg"));