breeze tts generates audio from text. In an interactive terminal, omitting --voice picks a random visible voice for the request, and the default path streams playback with a compact live sound wave as soon as audio arrives.
breeze tts consumes account credits each time it generates audio. Check your balance with breeze balance.
Interactive use
--text, a file with --input, or --stdin:
Formats and files
Streaming text to speech usespcm by default to reduce time to first audio. Pass --format wav or --format mp3 when you want the stream response in that format explicitly; when saving with -o, the CLI also infers the format from the file extension.
Pass --no-stream to use the non-streaming endpoint, and --no-play to skip local playback.
When local stream playback is not used, the command keeps stdout reserved for the saved audio path and prints client-observed timing to stderr:
Delivery instructions
--instructions steers delivery — tone, pace, emotion — and --guidance-scale controls how strongly the model follows it. breeze tts enhance rewrites a rough instruction into one the model follows well:
Async generation
--async creates a generation job instead of waiting for audio, which suits long texts and batch scripts. Track it with breeze jobs:
--async cannot be combined with --play or --no-stream. See History and jobs.
Realtime conversation
Usebreeze tts realtime for live conversation turns. The CLI treats --stdin as one logical conversation while keeping each physical WebSocket bounded. Realtime text to speech always receives binary pcm_s16le, 24000 Hz, mono, 16-bit audio from the API, so this command does not provide --format.
Run this non-playing smoke test first to verify authentication, voice access, realtime routing, WAV output, and per-turn TTFA:
"type": "summary" and includes ok, output_file, ttfa_ms, and per-turn status. If your installed version is older, run breeze update --check --agent, then breeze update --yes --agent before retrying.
--stdin-format text, each non-empty input line is one turn. Lines that begin with /, contain JSON, or otherwise look like commands remain ordinary text. The API allows one active turn per session, so the CLI sends turns sequentially, starting the next turn only after the previous one completes.
Change instructions between turns
Use--stdin-format jsonl when a managed conversation needs different delivery instructions for later turns. This is a CLI input format, not a raw WebSocket event stream. It accepts two record types:
breeze-tts-2 when --model is omitted; passing --model explicitly always wins. The CLI waits for the previous turn to reach turn.done or turn.cancelled before sending an update. It then waits for an exact session.updated acknowledgement before processing the next record, so a buffered input file cannot start a turn under an unconfirmed value. In JSON output, the acknowledgement appears on stdout as the server sent it:
--instructions sets the initial value during session creation; use session.update records only for later changes.
Malformed JSON, unknown record types or fields, invalid instructions, rejected updates, and acknowledgement mismatches stop the managed run before any later record is sent. If the connection fails before the update write begins, the CLI can reconnect and send that still-unsent record once. After the write begins, an unacknowledged update is never replayed because its application state is uncertain.
While waiting for input, the CLI sends application-level ping events at a safe interval derived from session.ready.inactivity_timeout_seconds. A successful write is not treated as proof that the WebSocket is healthy: the CLI waits up to five seconds for a new inbound server frame. The normal idle acknowledgement is pong; audio or another server event also proves that the server-to-client path remains active. If the first wait expires, the CLI sends one more ping and waits for a second, independent window of up to five seconds. Two consecutive missing acknowledgements mark the physical connection unhealthy.
An open stdin represents an active realtime call: the CLI keeps the logical conversation alive and continues heartbeat and physical-session rotation even when no line is arriving. Close stdin or press Ctrl-C when the call ends so the CLI releases its server-side WebSocket slot.
Physical realtime sessions have a finite lifetime. At 600 seconds of physical-epoch age, or at an earlier safe threshold derived from session.ready.max_session_seconds and session.ready.expires_at, the managed --stdin connection marks a planned rotation. The CLI performs the handoff only at an idle turn boundary; it does not interrupt an active turn solely because the 600-second threshold elapsed, so that physical WebSocket can remain open longer. The server-advertised 1800-second hard maximum remains the final upper bound. Before the current session expires, the server can also send session.expiring. At the idle boundary, the CLI obtains a fresh one-time session URL, establishes the replacement WebSocket, waits for any pending usage.committed notifications on the old WebSocket within a bounded settlement window, closes the old WebSocket, and sends the next input line on the replacement. A single positional-text invocation exits as soon as its one turn completes, so it does not stay open merely to rotate.
Long text is handled automatically: the CLI splits each turn into multiple text.append events on UTF-8 character boundaries, honoring the per-event byte limit the server advertises in session.ready (max_append_bytes), so a turn can be far longer than a single append.
The --file flag accepts .pcm for raw PCM or .wav for a WAV container. Use --no-play to capture audio without local playback. When --file is omitted, the audio is saved to the local Breeze cache — the same behavior as breeze tts — so --no-play and agent runs never discard generated audio; the summary reports the saved path.
In an interactive terminal, the CLI reports the time to first audio (TTFA) for each turn as soon as the first audio frame arrives, and after the run prints a breeze history play <history_item_id> hint for replay.
Use --events when another process owns the raw WebSocket protocol. The command reads JSONL client events from stdin (positional text is rejected with --events or --stdin) and writes server events when --output json is enabled. Event lines, including session.update, are forwarded verbatim — no turn serialization, instruction carry-over, or text splitting — and may be up to 4MB each. The input owner must wait for session.updated before sending the next turn.start. Binary audio frames are still written to --file or played locally.
JSON output
With--output json or --agent, stdout is JSON Lines: each server event, including session.updated, is echoed as one line, followed by a final summary line marked with "type": "summary". A long --stdin conversation can contain more than one session.ready line because its physical WebSocket rotated; the final summary aggregates all completed turns in the logical CLI run. The summary does not repeat the instruction text; use the ordered session.updated event as the confirmation record.
ttfa_ms is the first turn’s time to first audio in milliseconds; turns lists every completed turn with its own turn_id, history_item_id, status, and ttfa_ms. status is done for a turn that delivered audio, or cancelled for a turn the server settled without completing it (a turn.cancel, a turn that ended without producing audio, or a protocol error that cancelled the turn). cached is true when the audio path lives in the local Breeze cache because no --file was given.
The usage_* fields report whether the CLI observed the best-effort usage.committed notification for every completed turn before closing its final physical WebSocket. The CLI waits for these events for at most five seconds in total; a missing notification produces a warning and usage_drain_complete: false, but does not turn otherwise successful audio generation into a failed command. Durable billing truth remains available from history and usage APIs.
A run that completes with some audio still succeeds (ok: true, exit 0) even if individual turns were cancelled — check each turn’s status. If every turn settles as cancelled and no audio was received at all, the command fails: it exits non-zero with ok: false and does not keep an empty audio file.
Interruptions
If an idle--stdin WebSocket is interrupted by a network change, service deployment, upstream realtime worker restart, or missing heartbeat acknowledgements, the CLI makes a bounded number of reconnect attempts. Every attempt requests fresh one-time session credentials; the CLI prefers the query-free direct WebSocket transport when the response provides it and otherwise uses the compatible public URL. An expired client_secret or WebSocket URL is never reused. Once a replacement reaches session.ready, it sends an immediate heartbeat before resuming the normal cadence. That interruption is then considered recovered, so a later independent idle interruption receives a fresh bounded reconnect budget. If the idle failure is published before the CLI begins writing turn.start, the unsent stdin line waits for that bounded replacement and is sent once. Once the first turn.start write begins, the turn is active and is never moved or replayed.
If the connection drops or the heartbeat ACK watchdog fails while a turn is active, the command exits with an explicit, non-retryable TURN_INTERRUPTED error and network exit code 6. It never replays the turn automatically, because the server may already have generated audio or charged credits even when the terminal event did not reach the client. Do not automatically rerun the command or submit the same turn; reconcile the application conversation state first, then decide whether to submit a new turn.
Turn-scoped validation, bad-request, plan-concurrency, and shared generation-capacity errors keep the physical WebSocket open. Capacity and active-turn validation errors settle through the matching turn.cancelled event. A plan-concurrency rejection can happen before the server creates a turn, so the CLI settles that input from the error itself. Before reading the next stdin line, the CLI honors the bounded retry_after_seconds hint. It never replays rejected text. JSON mode preserves every event actually sent by the server; a pre-turn concurrency rejection therefore has an error event without a synthetic turn.cancelled event.
Managed instruction updates fail closed. If the server rejects an update, returns an acknowledgement with a different value, or the connection ends after the write without confirming it, the CLI preserves any audio already received, emits a failed summary, and stops before sending later turns. Start a new command after deciding which instruction state your application should use; do not automatically replay an update whose acknowledgement was lost.
--events remains a raw, connection-scoped protocol mode: the CLI requires a timely inbound server frame after each keepalive and stops promptly if the socket closes or becomes half-open while stdin is still open, but it does not move, replay, or retain caller-authored events onto another WebSocket. The process that owns the JSONL protocol must create a new command/session at a safe turn boundary and carry its last confirmed instructions into that session.
Audio already received is never discarded: on Ctrl-C, on server errors, and when the connection closes before every turn completes, the CLI finalizes the output file (including the WAV header), emits the summary, and exits with the matching error code — credits and quota failures exit 4, timeouts exit 5, and connection and upstream failures exit 6. See Scripting and agents for the full exit-code table.
For raw WebSocket events, SDK connections, browser-safe client_secret sessions, and PCM playback, continue with the Realtime text-to-speech guide.
Speech language
--language controls the speech language and pronunciation rules sent as the TTS language_code. It does not change the CLI interface language or output message language.
Omitting --language defaults to en. To use another language, run breeze models list --agent and pass an ISO 639-1 two-letter code listed for the selected model. The Developer API rejects a language that the model does not support.
Scripts and agents
In scripts, JSON mode, or any non-interactive run,breeze tts requires an explicit voice: pass --voice, or pass --random-voice to let Breeze pick one.
breeze tts JSON includes client-observed timing fields for latency measurement:
response_header_ms measures from the CLI starting the stream request to receiving response headers. ttfa_ms and first_audio_byte_ms measure from the same start point to the first audio byte read from the response body. These fields describe CLI-observed network and service latency; they do not include sound-card playback buffering.
See Scripting and agents for the JSON output contract and exit codes.
