fish-audio-sdk
Turns text into speech and speech into text using Fish Audio in Python or JavaScript code.
Installation
Paste this into Claude Code, Cursor, or any agent that can run commands.
SKILL.mdShow the author's original SKILL.md
---
name: fish-audio-sdk
description: Write code with the official Fish Audio SDKs, Python (`fishaudio`, PyPI `fish-audio-sdk`) and JavaScript/TypeScript (`fish-audio`). Use when the user wants text-to-speech, speech-to-text, voice cloning / voice-model management, or realtime WebSocket TTS through the installed SDK rather than raw HTTP. Covers install and auth, sync + async Python, the TypeScript client, exact method signatures and defaults, model selection (including the S2.1 typing caveat), the real exception types, and the Python↔JavaScript naming differences. For raw REST/WebSocket calls without an SDK (curl, unsupported languages, edge runtimes), use the `fish-audio-api` skill instead.
---
# Fish Audio SDK Skill
Use this skill to generate correct, runnable code with the **official Fish Audio SDKs**:
- **Python**: package `fish-audio-sdk` on PyPI, imported as `fishaudio`. (The same wheel still ships a separate legacy `fish_audio_sdk` package. Do **not** mix them; everything here is the modern `fishaudio` package.)
- **JavaScript / TypeScript**: package `fish-audio` on npm, imported as `FishAudioClient`.
If the user wants raw `curl` / HTTP / WebSocket without installing an SDK, use the **`fish-audio-api`** skill instead.
> This file is the index. Deeper, task-specific rules and full examples live in [`references/`](references/). Read the reference for the task you're doing before writing code.
## Global facts
- **Auth:** both SDKs read the API key from the `FISH_API_KEY` environment variable automatically. Get keys at `https://fish.audio/app/api-keys`. Never hardcode a key.
- **Base URL:** `https://api.fish.audio` (override with `base_url=` in Python / `baseUrl:` in JS).
- **TTS models:** the API supports `s1` (deprecated), `s2-pro`, `s2.1-pro` (recommended for production), and `s2.1-pro-free` (free tier). Do not choose `s1` for new code: S1 is deprecated and will be retired on December 31, 2026. After that date, requests that specify `s1` are served by `s2.1-pro` (migration: `https://docs.fish.audio/developer-guide/models-pricing/migrate-from-s1`). However, the Python SDK types `model` as only `s1` and `s2-pro` (plus the deprecated `speech-1.x`), default `s2-pro`. The JS package on npm (`fish-audio` 0.1.0) types `backend` without `s2-pro` or `s2.1-pro` and defaults it to `"s1"` at runtime (its README says `"s2-pro"`), so in JS always pass `backend` explicitly. Both SDKs forward the model value without runtime validation, so `"s2.1-pro"` works over the wire. Static type checkers will flag it, so add `# type: ignore` (Python) / an `as` cast (TS), or use the `fish-audio-api` skill for raw calls. `speech-1.5` / `speech-1.6` are **deprecated**. In Python pass `model="s2-pro"` (keyword); in JS pass the **positional** `backend` argument.
- **ASR models:** use `transcribe-1-pro` (recommended: speaker turns, long recordings, emotion cues). Neither SDK has an ASR `model` argument: send the `model: transcribe-1-pro` HTTP header on every request (Python `RequestOptions(additional_headers=...)`, JS `requestOptions.headers`). A request without it is served and billed as `transcribe-1`, the model for short recordings. See [references/speech-to-text.md](references/speech-to-text.md).
- **Audio formats:** `mp3` (default), `wav`, `pcm`, `opus`.
- **Playback in examples:** `play()` shells out to a system audio tool: Python uses **ffmpeg/ffplay** (or `mpv`), JS uses **ffplay**. It is for local/desktop use; in a server, `save()` to a file or stream the bytes instead. See [references/installation.md](references/installation.md).
## Quick start: Python
```python
from fishaudio import FishAudio
from fishaudio.utils import play, save
client = FishAudio() # reads FISH_API_KEY
# Generate speech (returns the full audio as bytes)
audio = client.tts.convert(text="Hello from Fish Audio!")
save(audio, "output.mp3") # write to a file
# play(audio) # or play locally (needs ffmpeg)
```
Async: identical resource tree on `AsyncFishAudio`, used as a context manager:
```python
import asyncio
from fishaudio import AsyncFishAudio
from fishaudio.utils import save
async def main():
async with AsyncFishAudio() as client:
audio = await client.tts.convert(text="Hello from Fish Audio!")
save(audio, "output.mp3")
asyncio.run(main())
```
## Quick start: JavaScript / TypeScript
```ts
import { FishAudioClient, play } from "fish-audio";
const client = new FishAudioClient({ apiKey: process.env.FISH_API_KEY });
// convert() returns audio you can play or pipe to a file
const audio = await client.textToSpeech.convert({
text: "Hello from Fish Audio!",
}); // npm 0.1.0 defaults to the deprecated "s1"; pass a model as shown below
await play(audio); // local playback (needs ffplay)
```
To pick a model in JS, pass `backend` as the **positional** argument (not a named option):
```ts
import type { Backends } from "fish-audio";
// The cast is needed because the npm 0.1.0 `Backends` type omits "s2.1-pro".
const audio = await client.textToSpeech.convert(
{ text: "Hi" },
"s2.1-pro" as Backends
);
```
## Capabilities → references
| Task | Reference |
| ---------------------------------------------------------------- | ------------------------------------------------------------ |
| Install, auth, playback deps, verify a key | [references/installation.md](references/installation.md) |
| Text-to-Speech (convert, stream, formats, prosody, model select) | [references/text-to-speech.md](references/text-to-speech.md) |
| Voice cloning (instant references + persistent voice models) | [references/voice-cloning.md](references/voice-cloning.md) |
| Speech-to-Text (models, timestamps, fields the SDK drops) | [references/speech-to-text.md](references/speech-to-text.md) |
| Realtime WebSocket TTS (stream text → audio) | [references/websocket.md](references/websocket.md) |
| Errors, retries, and timeouts (the **real** exception types) | [references/errors.md](references/errors.md) |
## Python ↔ JavaScript name map
The two SDKs do **not** use the same names. Use this map when porting code between them.
| Concept | Python (`fishaudio`) | JavaScript (`fish-audio`) |
| --------------------- | ------------------------------------------------- | ---------------------------------------------------------- |
| Client | `FishAudio()` / `AsyncFishAudio()` | `new FishAudioClient({ apiKey })` |
| Text-to-Speech | `client.tts.convert(text=...)` → `bytes` | `client.textToSpeech.convert({ text })` |
| TTS HTTP stream | `client.tts.stream(...)` → `AudioStream` | (use `convert`; realtime streaming is `convertRealtime`) |
| Realtime WebSocket | `client.tts.stream_websocket(text_stream)` | `client.textToSpeech.convertRealtime(request, textStream)` |
| Speech-to-Text | `client.asr.transcribe(audio=...)` | `client.speechToText.convert({ audio })` |
| List voice models | `client.voices.list()` | `client.voices.search()` |
| Get voice model | `client.voices.get(id)` | `client.voices.get(id)` |
| Create voice (clone) | `client.voices.create(title=..., voices=[bytes])` | `client.voices.ivc.create({ title, voices: [File] })` |
| Update / delete voice | `client.voices.update(id, ...)` / `delete(id)` | `client.voices.update(id, ...)` / `delete(id)` |
| Credit balance | `client.account.get_credits()` | `client.user.get_api_credit()` |
| Subscription package | `client.account.get_package()` | `client.user.get_package()` |
| Choose model | `model="s2-pro"` keyword arg | positional `backend` arg, e.g. `convert(req, "s2-pro")` |
| ASR model (header) | `RequestOptions(additional_headers=...)` | `convert(req, { headers: { model: "transcribe-1-pro" } })` |
## Decision shortcuts
- **Audio from text** → `tts.convert` (Python) / `textToSpeech.convert` (JS).
- **Reuse a saved voice** → pass `reference_id` (the voice model `id`).
- **Clone a voice instantly from a clip** → pass `references=[ReferenceAudio(audio=..., text=...)]` (Python) / `references: [{ audio, text }]` (JS). See [voice-cloning](references/voice-cloning.md).
- **Persistent custom voice to reuse** → create a voice model, then use its `id` as `reference_id`.
- **Stream tokens from an LLM and play speech as it arrives** → `tts.stream_websocket` (Python) / `textToSpeech.convertRealtime` (JS). See [websocket](references/websocket.md).
- **Transcribe audio** → `asr.transcribe` (Python) / `speechToText.convert` (JS) with the `model: transcribe-1-pro` header (recommended; without it, the request runs on `transcribe-1`). Pro request fields, `speaker_turns`, `request_id`, and the language fields need raw HTTP in Python (`fish-audio-sdk` 1.3.0). See [speech-to-text](references/speech-to-text.md).
## Gotchas (verified against the SDK source)
- Python `latency` accepts only **`"normal"` or `"balanced"`** (default `"balanced"`); there is no `"low"`.
- The Python client has **no `max_retries`** and does **not** auto-retry; the JS client **does** auto-retry (configurable via per-call `requestOptions.maxRetries`). See [errors](references/errors.md).
- Python defines a `ValidationError` class but **never raises it**, so don't catch it expecting validation failures; a 422 surfaces as `APIError`. The JS SDK throws `UnprocessableEntityError` on 422.
- ASR segment `start` / `end` and `duration` are all in **seconds**. The Python SDK's `ASRResponse` docstring says milliseconds; that is wrong. See [speech-to-text](references/speech-to-text.md).
Mirrored from the author's public source. Install counts from the open skills registry.