Gemini TTS API: From Text to Playable Audio in Python

The Gemini TTS API returns audio inside a response object, not necessarily a ready-to-save WAV file. For Tokenhot’s documented gemini-2.5-flash-preview-tts route, build a generateContent request, select the voice, keep the response, and decode the audio according to its actual MIME type. Renaming the entire JSON response to speech.wav will not make it playable.
This tutorial stays with Tokenhot’s Gemini 2.5 Flash TTS reference. Its example uses Zephyr and labels the returned audio audio/mp3. That is a documentation example, not a recording generated for this article. The Python companion has offline checks; no live synthesis or listening test was performed.
Version check before starting: as checked on October 11, 2026, Google’s deprecation table lists November 17, 2026 as the earliest possible shutdown date for its Gemini 2.5 TTS previews. This is not a confirmed Tokenhot cutoff or proof of an equivalent replacement route. Confirm the gateway model remains available before committing a new workflow to it.
1. Keep the model, protocol, and voice fields together
This route uses:
POST https://api.tokenhot.ai/v1beta/models/gemini-2.5-flash-preview-tts:generateContent
Authorization: Bearer YOUR_TOKENHOT_API_KEY
Content-Type: application/json
Do not substitute /v1/chat/completions merely because another Tokenhot audio model uses it. Likewise, do not combine this request with a newer Gemini model’s voice-field nesting or output-format options.
Here is an authored single-voice request using the documented structure:
{
"contents": [
{
"parts": [
{"text": "Say clearly and warmly: Your report is ready. Open the dashboard to review it."}
]
}
],
"generationConfig": {
"responseModalities": ["AUDIO"],
"speechConfig": {
"voiceConfig": {
"prebuiltVoiceConfig": {"voiceName": "Zephyr"}
}
}
}
}
The style instruction and spoken copy are in the text prompt here. Inspect the eventual recording to confirm which words were spoken and how. The example does not establish exact pronunciation, timing, or that every instruction will be followed.
The companion deliberately fixes the voice to the documented Zephyr example. To use a different voice, first confirm it for this model and route, then change the preparation function and its validation together. A voice listed for a different API/version is not enough evidence.
2. Prepare the request locally
Download the complete Python companion and save it as media_workflows.py in your working directory. It uses the Python standard library; the author’s checks used Python 3.13.5. There is no package installation or embedded key.
Create a UTF-8 file named speech.txt containing the prompt above, then run:
python media_workflows.py tts-request --text-file speech.txt --out speech-request.json
Preparation writes a new JSON file and stops without opening a connection. Inspect the file before sending it. Existing files are not overwritten; use a new filename for an intentional revision.
Use Tokenhot’s Quick Start to locate your key, then set TOKENHOT_API_KEY in your shell or secret manager. For example, in PowerShell:
$env:TOKENHOT_API_KEY = "YOUR_TOKENHOT_API_KEY"
Replace the placeholder privately. Do not put the actual key in the request file, article, source control, or shared terminal output.
3. Submit once and retain the complete response
This command sends an authenticated request and may incur usage charges:
python media_workflows.py submit --kind tts --request speech-request.json --run runs/tts-001 --execute
The explicit --execute flag is required. The helper reserves a new run directory, stores the request without authorization headers, and makes one POST. A received body goes into runs/tts-001/response.json before response parsing. It also records the HTTP status separately.
The run-directory guard prevents an accidental rerun with the same local directory. It is not server-side idempotency: using another directory can send another request. If the connection ends without a usable response, reconcile that attempt before deciding to synthesize again.
Local responses can contain private text and generated audio. Keep them out of Git and redact them before asking for help. A response-size limit in this example is a memory guard, not a provider quota.
4. Save the returned format rather than guessing one
The documented audio location is candidates[0].content.parts[].inlineData. The relevant fields are mimeType and Base64 data; the example’s candidate ends with finishReason: "STOP". The string Base64_String_Here in that example is a placeholder, not a usable test recording.
Run the local decoder:
python media_workflows.py audio --response runs/tts-001/response.json --output-stem output/voiceover
Use an output stem without an extension. The helper selects .mp3 or .wav from the returned type and prints the actual destination.
| Actual response | Local handling |
|---|---|
audio/mp3 or audio/mpeg |
Decode Base64, check for a recognizable MP3 signature, and save .mp3 unchanged. |
audio/wav, audio/x-wav, or audio/wave |
Check the RIFF/WAVE container and PCM frame data, then preserve the existing WAV bytes. |
Raw audio/L16 or audio/pcm |
Stop by default. Obtain the actual sample format, byte order, rate, and channel count before wrapping it. |
| Missing audio, an unfinished candidate, or an unsupported type | Keep the response for diagnosis rather than writing a misleading audio file. |
These are the companion’s handling branches, not a claim that Tokenhot has been observed returning every format in the table. Its published sample only labels MP3. The MP3 signature check is also not a full codec decode or a listening test.
The script selects candidate zero and requires one inline audio part. It does not concatenate alternative candidates or multiple encoded containers. Such responses need separately verified framing, not a blind byte join.
Raw PCM requires an explicit, confirmed contract
Raw samples need metadata that a WAV container normally carries. Only when your provider confirms signed 16-bit little-endian PCM with the applicable rate and channels should you use the explicit override. For example, if separately confirmed as 24,000 Hz, mono:
python media_workflows.py audio --response runs/tts-001/response.json --output-stem output/voiceover --pcm-s16le 24000 1
Those numbers illustrate an explicit conversion configuration, not a verified Tokenhot default. An audio/L16 label by itself does not confirm the little-endian assumption. Do not use this override to make an unknown payload appear to work.
Python 3.13’s wave module handles supported uncompressed PCM WAV data, not MP3 transcoding. Adding a WAV header to MP3 or adding a second header to an existing WAV corrupts the intended interpretation instead of converting the codec.
5. Recover the file before repeating synthesis
The audio command has no network path and needs no API key. When the complete response already exists, it can decode again to a fresh destination without another synthesis request.
If Base64 decoding fails, inspect the saved fields. If the MIME type and bytes disagree, ask for the exact route output contract. If the file already exists, inspect it or choose another name. If the POST timed out and no complete response exists, a local decoder cannot recover audio it never received.
The example refuses HTTP redirects and uses hard-link file installation to avoid overwriting an existing destination. A filesystem that does not support that operation will fail safely; move the working directory to a supported local filesystem rather than rerunning synthesis as a workaround.
Finally, open the saved file in an audio player. Check completeness, product-name pronunciation, numbers, unwanted spoken instructions, and pacing. File validation cannot establish those qualities. Keep the exact request beside each deliberately generated version so later comparisons are meaningful.
Does this also cover live speech or voice cloning?
No. This guide demonstrates one non-streaming, preset-voice request on the documented Tokenhot route. It does not establish support for live sessions, cloning, multi-speaker generation, or newer Gemini TTS settings. Those require their own model-specific request and response evidence.
Use Tokenhot’s documented Gemini 2.5 TTS route, select a voice, decode the returned audio format, and recover saved responses without another synthesis.


