Developer Dashboard

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 ​

ModelBest fitLanguages
gemini-3.8-flash-ttsCreative narration, audiobooks, expressive acting, regional accents, complex dialogue, and stable voice identity across long-form turns130, including Persian
gemini-3.8-flash-lite-ttsHigh-throughput production, voice-agent cascades, read-aloud features, and everyday single-speaker speech101, 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 ​

ModelInputCached inputAudio 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 ​

ModelInputCached inputAudio 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 ​

  1. Replace gemini-3.1-flash-tts-preview with Lite for throughput or Flash for creative work. Apply the same choice when migrating from gemini-2.5-flash-tts, gemini-2.5-pro-tts, gemini-2.5-flash-preview-tts, or gemini-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.
  2. Treat text as a verbatim transcript. Do not put delivery instructions such as Say cheerfully: or speaker names such as Speaker 1: in spoken text; they may be read aloud. On native requests, attach camelCase speechMetadata with speaker and style to each text part. Every multi-speaker part must name a configured speaker. Put sustained delivery instructions in style; reserve inline tags such as <laugh>, <sigh>, and <short pause> for momentary vocal events.
  3. Native unary 3.8 responses default to WAV (audio/wav) with a RIFF header, unlike the legacy headerless PCM default. Inspect inlineData.mimeType: write WAV bytes directly and wrap only raw PCM. Never add a second WAV header or guess the format from a filename.
  4. Chat Completions uses audio.format; Speech uses response_format. The Chat example explicitly requests pcm16 and decodes only choices[0].message.audio.data. Do not use message.content as an audio fallback or strip arbitrary prefixes from Base64.
  5. gemini-3.1-flash-tts-preview supports PCM16 only. For legacy Speech calls, request response_format: "pcm", save raw PCM, and transcode with ffmpeg -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 ​

bash
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.mp3

The 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 ​

bash
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.json

Inspect 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:

json
{
  "candidates": [
    {
      "content": {
        "role": "model",
        "parts": [
          {
            "inlineData": {
              "mimeType": "audio/wav",
              "data": "[BASE64_AUDIO]"
            }
          }
        ]
      }
    }
  ]
}

Decode the actual saved response, not the illustrative placeholder:

python
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.

bash
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.wav

Inspect 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:

json
{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "audio": {
          "data": "[BASE64_AUDIO]"
        }
      }
    }
  ]
}