Gemini 3.8 Flash and Flash-Lite TTS Now Available
Date: 2026-09-30 / (1405-07-08)
Summary
Gemini 3.8 Flash TTS and Flash-Lite TTS are now available on AvalAI. Choose Flash for creative narration or Lite for high-throughput speech; both support Persian. Promotional pricing applies through December 31, 2026, with structured prompting and audio-format migration guidance below.
Details
Choose the right Google TTS model
| Model | Best fit | Languages |
|---|---|---|
gemini-3.8-flash-tts | Creative narration, audiobooks, expressive acting, regional accents, complex dialogue, and stable voice identity across long-form turns | 130, including Persian |
gemini-3.8-flash-lite-tts | High-throughput production, voice-agent cascades, read-aloud features, and everyday single-speaker speech | 101, including Persian |
Both models take text input and produce audio output, with the same native structured prompting schema. Lite's throughput and latency positioning is provider guidance, not an observed AvalAI performance guarantee. In a voice-agent cascade, a separate model prepares the answer and TTS speaks the final text; these are not audio-input conversation or reasoning models.
AvalAI endpoint availability
Both models support only native /v1beta/models methods, /v1/chat/completions, and /v1/audio/speech on AvalAI. The native example below uses :generateContent.
Do not send Gemini 3.8 TTS to Responses, Messages, legacy Vertex /v1/text:synthesize, Live, or Interactions. Upstream extended-voice listing, voice design, and voice replication endpoints are not exposed for these models on AvalAI; upstream Batch, Flex, or Priority documentation does not establish AvalAI availability either. The examples use prebuilt voices only.
Google lists 8,192 input tokens and 16,384 output tokens as Gemini API serving limits, not an AvalAI context-window or account-quota guarantee. Use the AvalAI model catalog for authoritative routing, published limits, availability, and account-tier throughput; do not replace its data with upstream limits.
Pricing
These are AvalAI prices in USD per 1 million tokens. Input and cached input refer to text tokens; output refers to generated audio tokens, not seconds or a flat per-file fee.
Promotional rates: September 30–December 31, 2026, inclusive
| Model | Input | Cached input | Audio output |
|---|---|---|---|
gemini-3.8-flash-tts | $0.50 | $0.125 | $9.00 |
gemini-3.8-flash-lite-tts | $0.50 | $0.125 | $6.00 |
Standard rates: from January 1, 2027
| Model | Input | Cached input | Audio output |
|---|---|---|---|
gemini-3.8-flash-tts | $1.00 | $0.25 | $18.00 |
gemini-3.8-flash-lite-tts | $1.00 | $0.25 | $12.00 |
See the pricing catalog for current model rates. These prices do not imply a cache lifetime, a special account tier, or a throughput quota.
Migration checklist
- Replace
gemini-3.1-flash-tts-previewwith Lite for throughput or Flash for creative work. Apply the same choice when migrating fromgemini-2.5-flash-tts,gemini-2.5-pro-tts,gemini-2.5-flash-preview-tts, orgemini-2.5-pro-preview-tts. For legacy Vertex integrations, change the endpoint and request schema, not just the model ID; this announcement does not automatically reroute existing requests. - Treat text as a verbatim transcript. Do not put delivery instructions such as
Say cheerfully:or speaker names such asSpeaker 1:in spoken text; they may be read aloud. On native requests, attach camelCasespeechMetadatawithspeakerandstyleto each text part. Every multi-speaker part must name a configured speaker. Put sustained delivery instructions instyle; reserve inline tags such as<laugh>,<sigh>, and<short pause>for momentary vocal events. - Native unary 3.8 responses default to WAV (
audio/wav) with a RIFF header, unlike the legacy headerless PCM default. InspectinlineData.mimeType: write WAV bytes directly and wrap only raw PCM. Never add a second WAV header or guess the format from a filename. - Chat Completions uses
audio.format; Speech usesresponse_format. The Chat example explicitly requestspcm16and decodes onlychoices[0].message.audio.data. Do not usemessage.contentas an audio fallback or strip arbitrary prefixes from Base64. gemini-3.1-flash-tts-previewsupports PCM16 only. For legacy Speech calls, requestresponse_format: "pcm", save raw PCM, and transcode withffmpeg -f s16le -ar 24000 -ac 1 -i legacy-speech.pcm legacy-speech.mp3. Saving raw PCM with an MP3 extension does not encode MP3.
API request and response examples
Set AVALAI_API_KEY in your environment. The requests below have been checked offline, not executed against the API: no live credentials or API responses were available. All displayed responses are ILLUSTRATIVE minimal shapes, not complete observed responses; no token counts, costs, or latency measurements are invented.
Speech: save an explicitly requested MP3
curl --fail-with-body -sS https://api.avalai.ir/v1/audio/speech \
-H "Authorization: Bearer $AVALAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3.8-flash-tts",
"voice": {"name": "Zephyr", "languageCode": "en-US"},
"input": "Welcome to AvalAI. This voice was generated by AI.",
"response_format": "mp3"
}' \
--dump-header speech-headers.txt \
--output speech.mp3The English input matches languageCode: "en-US". Change only the model ID to gemini-3.8-flash-lite-tts for Lite. Inspect the actual HTTP status and Content-Type in the saved headers before playback; a failed request body is not audio.
ILLUSTRATIVE response: a binary MP3 body with an audio Content-Type, not a JSON Base64 wrapper. Inspect actual headers for any billing metadata; do not assume a binary response contains JSON usage or estimated_cost.
Native: per-part speaker and style metadata
curl --fail-with-body -sS \
https://api.avalai.ir/v1beta/models/gemini-3.8-flash-tts:generateContent \
-H "x-goog-api-key: $AVALAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"contents": [{
"role": "user",
"parts": [
{"text": "Welcome to AvalAI.", "speechMetadata": {"speaker": "Host", "style": "Warm and welcoming."}},
{"text": "Have a wonderful day!", "speechMetadata": {"speaker": "Guest", "style": "Cheerful and relaxed."}}
]
}],
"generationConfig": {
"responseModalities": ["AUDIO"],
"speechConfig": {
"multiSpeakerVoiceConfig": {
"speakerVoiceConfigs": [
{"speaker": "Host", "voiceConfig": {"prebuiltVoiceConfig": {"voiceName": "Zephyr"}}},
{"speaker": "Guest", "voiceConfig": {"prebuiltVoiceConfig": {"voiceName": "Puck"}}}
]
}
}
}
}' \
--output native-speech.json
jq 'walk(if type == "object" and has("inlineData") then .inlineData |= (if has("data") then del(.data) else . end) else . end)' native-speech.jsonInspect the real full response, including every candidate, usageMetadata or usage, and estimated_cost when present. This display command removes only Base64 audio data from native parts and preserves all other fields; the saved JSON remains intact.
ILLUSTRATIVE minimal response shape — audio data abbreviated, billing fields omitted rather than fabricated:
{
"candidates": [
{
"content": {
"role": "model",
"parts": [
{
"inlineData": {
"mimeType": "audio/wav",
"data": "[BASE64_AUDIO]"
}
}
]
}
}
]
}Decode the actual saved response, not the illustrative placeholder:
import base64
import json
import wave
from pathlib import Path
response = json.loads(Path("native-speech.json").read_text())
parts = response["candidates"][0]["content"]["parts"]
audio_parts = [part["inlineData"] for part in parts if "inlineData" in part]
if not audio_parts:
raise ValueError("No audio returned; inspect the full response")
for index, audio in enumerate(audio_parts):
data = base64.b64decode(audio["data"], validate=True)
mime_type = audio["mimeType"].split(";", 1)[0].strip().lower()
filename = f"speech-{index}.wav"
if mime_type == "audio/wav":
# WAV already has a RIFF header; do not wrap it again.
Path(filename).write_bytes(data)
elif mime_type == "audio/l16":
# Gemini raw PCM16: 24000 Hz, mono, two bytes per sample.
with wave.open(filename, "wb") as output:
output.setnchannels(1)
output.setsampwidth(2)
output.setframerate(24000)
output.writeframes(data)
else:
raise ValueError(f"Unsupported audio MIME type: {audio['mimeType']}")The decoder rejects unknown MIME types rather than guessing. If the response specifies different PCM parameters, adapt the decoder to those parameters instead of forcing this example's 24 kHz mono settings.
Chat Completions: explicit PCM16 and raw-audio conversion
For detailed native per-turn delivery control, use the native request above; this Chat request keeps the transcript plain.
curl --fail-with-body -sS https://api.avalai.ir/v1/chat/completions \
-H "Authorization: Bearer $AVALAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemini-3.8-flash-lite-tts",
"messages": [{"role": "user", "content": "Welcome to AvalAI. This voice was generated by AI."}],
"modalities": ["text", "audio"],
"audio": {"voice": "Zephyr", "format": "pcm16"}
}' \
--output chat-speech.json
jq 'walk(if type == "object" and has("audio") and (.audio | type) == "object" then .audio |= (if has("data") then del(.data) else . end) else . end)' chat-speech.json
python3 - <<'PY'
import base64
import json
from pathlib import Path
response = json.loads(Path("chat-speech.json").read_text())
data = response["choices"][0]["message"]["audio"]["data"]
Path("chat-speech.pcm").write_bytes(base64.b64decode(data, validate=True))
PY
ffmpeg -f s16le -ar 24000 -ac 1 -i chat-speech.pcm chat-speech.wavInspect the real full JSON, including usage and estimated_cost when returned. The display command removes only audio Base64 data, leaving billing and other response fields intact. The conversion explicitly treats the requested PCM16 as raw signed 16-bit little-endian, 24 kHz, mono; a filename alone cannot tell a decoder those parameters.
ILLUSTRATIVE minimal response shape — not an observed response:
{
"choices": [
{
"message": {
"role": "assistant",
"audio": {
"data": "[BASE64_AUDIO]"
}
}
}
]
}