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