Fish Audio TTS
Fish Audio is a TTS provider with strong voice cloning, low cost (~70% cheaper than ElevenLabs), and competitive quality (ranked #1 on TTS-Arena 2026). Used for cloned voiceovers, ad scripts, and post-call audio outputs.
Auth
Authorization: Bearer ${FISH_AUDIO_API_KEY}
Base URL: https://api.fish.audio
Quick curl (with JSON file to avoid shell-escape issues)
cat > /tmp/fish-tts.json << 'ENDJSON'
{
"text": "Hello from the Merlino voice clone.",
"reference_id": "3782f7b36e5d411fb9d70d1809b134a0",
"format": "mp3",
"mp3_bitrate": 192,
"temperature": 0.9,
"latency": "normal",
"chunk_length": 300,
"prosody": { "speed": 0.9 }
}
ENDJSON
curl -X POST https://api.fish.audio/v1/tts \
-H "Authorization: Bearer $FISH_AUDIO_API_KEY" \
-H "Content-Type: application/json" \
-H "model: s1" \
-d @/tmp/fish-tts.json \
--output ./output.mp3
Always use a temp JSON file — text with em dashes, exclamation marks, or smart quotes breaks naive shell quoting.
TTS endpoint — full reference
POST https://api.fish.audio/v1/tts
Headers
| Header | Value |
|---|---|
| Authorization | Bearer YOUR_KEY |
| Content-Type | application/json |
| model | s1 (or speech-1.6, speech-1.5) |
Request body parameters
| Param | Type | Default | Range | Notes |
|---|---|---|---|---|
| text | string | required | — | Text to speak |
| reference_id | string | null | — | Voice model ID |
| format | string | mp3 | wav, pcm, mp3, opus | Output format |
| temperature | float | 0.7 | 0-1 | Higher = more expressive |
| top_p | float | 0.7 | 0-1 | Nucleus sampling |
| latency | string | normal | low, balanced, normal | normal = best quality |
| chunk_length | int | 300 | 100-300 | Text segment size |
| mp3_bitrate | int | 128 | 64, 128, 192 | kbps (MP3 only) |
| opus_bitrate | int | -1000 | -1000, 24, 32, 48, 64 | bps (opus only) |
| sample_rate | int | 44100 | 8k-48k | Hz |
| normalize | bool | true | — | Normalize numbers in text |
| max_new_tokens | int | 1024 | — | Max audio tokens per chunk |
| repetition_penalty | float | 1.2 | — | >1.0 reduces repetition |
| min_chunk_length | int | 50 | 0-100 | Min chars before chunking |
| prosody.speed | float | 1.0 | 0.5-2.0 | Speaking rate |
| prosody.volume | float | 0 | — | dB adjustment |
Response
- 200 — chunked audio (
Transfer-Encoding: chunked) - 401 — bad auth
- 402 — billing issue
- 422 — validation error
Merlino voice (Mike's clone) — defaults
{
"reference_id": "3782f7b36e5d411fb9d70d1809b134a0",
"temperature": 0.9,
"prosody": { "speed": 0.9 },
"mp3_bitrate": 192,
"latency": "normal",
"chunk_length": 300
}
Built-in voice presets
| Voice | Reference ID | Vibe |
|---|---|---|
| Adam (popular) | 722523118ee94709b661502c76b016b7 | Young, energetic, friendly |
| TikTok Adam | b8d3d639d6414cc39c964955f77fce82 | Deep, authoritative, dramatic |
| E-girl | 8ef4a238714b45718ce04243307c57a7 | Female, young, casual |
| Energetic Male | 802e3bc2b27e49c2995d23ef70e6ac89 | Male, high energy |
| Sarah | 933563129e564b19a115bedd57b7406a | Female, clear |
| Adrian | bf322df2096a46f18c579d0baa36f41d | Male, professional |
| Selene | b347db033a6549378b48d00acb0d06cd | Female, smooth |
| Ethan | 536d3a5e000945adb7038665781a4aca | Male, neutral |
Speed/energy guide
| Use | Speed | Temperature | |---|---|---| | Chill narration | 0.8 | 0.7 | | Merlino default | 0.9 | 0.9 | | High energy | 1.0 | 0.9 | | Fast-paced (reels) | 1.2 | 0.8 |
Voice cloning — create a new voice
POST https://api.fish.audio/model
Content-Type: multipart/form-data
| Field | Required | Notes |
|---|---|---|
| type | Yes | tts |
| title | Yes | Model name |
| train_mode | Yes | fast (instant) |
| voices | Yes | Audio file(s), 10-30s clear speech recommended |
| visibility | No | private, public, unlist |
| texts | No | Transcript of the audio (auto via ASR if omitted) |
| enhance_audio_quality | No | Boolean |
Returns a model ID — use it as reference_id in TTS calls.
Pricing
| Service | Cost |
|---|---|
| TTS | $15 per 1M UTF-8 bytes (~180k words / ~12 hours speech) |
| ASR | $0.36 per audio hour |
| Voice model | Pay-as-you-go, no subscription |
Rate limits: 5 concurrent (starter) → 15 concurrent (after $100+ spent) → custom (enterprise).
SDKs
pip install fishaudio
npm install fish-audio
Both SDKs support WebSocket streaming for low-latency real-time TTS.
Fish Audio vs ElevenLabs
| | Fish Audio | ElevenLabs | |---|---|---| | Cost/char | $0.00004 | $0.00014 | | Cost/min | ~$0.05 | ~$0.18 | | Price diff | 70% cheaper | — | | Quality rank | #1 TTS-Arena (2026) | #2-3 | | Clone min audio | 10 seconds | Few minutes | | Languages | 30+ | 70+ | | Smoothness | Good, some chunk artifacts at boundaries | Buttery smooth | | Streaming latency | Sub-500ms | Not specified | | Extras | Open-source model | Dubbing, SFX, voice isolator |
Rule of thumb: Fish Audio when cost + speed dominate. ElevenLabs when output smoothness matters more.
Using inside SignalWire flows
SignalWire's native TTS catalog (Rime, ElevenLabs, Deepgram, Cartesia) handles in-call TTS. Fish Audio is generally used for pre-rendered audio — pre-generate MP3s, host them, then play via SWML play:
- play:
url: "https://your.cdn/clips/intro.mp3"
For dynamic per-call cloned-voice output, render to a temp URL with Fish, hand the URL to play. Adds a generation step per call.
Anti-patterns
- Inline curl with shell-escaped JSON — punctuation breaks everywhere. Always use a temp file.
- Using
latency: lowfor narration — sacrifices quality. Reserve for real-time. - Cloning from noisy or compressed audio — Fish's
enhance_audio_qualityhelps a little, but garbage in = garbage out. - Treating Fish-rendered MP3s as a substitute for in-call dynamic TTS — adds latency. For real-time conversational AI, use a SignalWire-native voice.
See also
- SWML AI verb — in-call TTS voices
- Call Intelligence
- AssemblyAI transcription