> ## Documentation Index
> Fetch the complete documentation index at: https://hanabiaiinc-auto-go-api-docs.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Migration Guide

> Migrate from the legacy Session-based API to the modern client-based Fish Audio Python SDK

export const AudioTranscript = ({voices = []}) => {
  const [selectedVoice, setSelectedVoice] = useState(0);
  const [isPlaying, setIsPlaying] = useState(false);
  const [currentTime, setCurrentTime] = useState(0);
  const [duration, setDuration] = useState(0);
  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
  const audioRef = useRef(null);
  const dropdownRef = useRef(null);
  useEffect(() => {
    const audio = audioRef.current;
    if (!audio) return;
    const updateTime = () => setCurrentTime(audio.currentTime);
    const updateDuration = () => setDuration(audio.duration);
    const handleEnded = () => setIsPlaying(false);
    audio.addEventListener('timeupdate', updateTime);
    audio.addEventListener('loadedmetadata', updateDuration);
    audio.addEventListener('ended', handleEnded);
    return () => {
      audio.removeEventListener('timeupdate', updateTime);
      audio.removeEventListener('loadedmetadata', updateDuration);
      audio.removeEventListener('ended', handleEnded);
    };
  }, []);
  useEffect(() => {
    const handleClickOutside = event => {
      if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
        setIsDropdownOpen(false);
      }
    };
    if (isDropdownOpen) {
      document.addEventListener('mousedown', handleClickOutside);
    }
    return () => {
      document.removeEventListener('mousedown', handleClickOutside);
    };
  }, [isDropdownOpen]);
  useEffect(() => {
    if (audioRef.current) {
      audioRef.current.pause();
      audioRef.current.load();
      setIsPlaying(false);
      setCurrentTime(0);
    }
  }, [selectedVoice]);
  const togglePlay = () => {
    if (isPlaying) {
      audioRef.current.pause();
    } else {
      audioRef.current.play();
    }
    setIsPlaying(!isPlaying);
  };
  const handleProgressChange = e => {
    const newTime = parseFloat(e.target.value);
    audioRef.current.currentTime = newTime;
    setCurrentTime(newTime);
  };
  const formatTime = time => {
    if (isNaN(time)) return '0:00';
    const minutes = Math.floor(time / 60);
    const seconds = Math.floor(time % 60);
    return `${minutes}:${seconds.toString().padStart(2, '0')}`;
  };
  const currentVoice = voices[selectedVoice];
  return <div className="border rounded-lg bg-card border-gray-200 dark:border-gray-800">
      {}
      <div className="grid grid-cols-3 items-center px-3 py-1.5 bg-muted border-b border-gray-200 dark:border-gray-800">
        <span className="text-xs font-medium">Listen to Page</span>

        <span className="text-xs font-semibold text-muted-foreground text-center">Powered by Fish Audio S1</span>

        {voices.length > 1 ? <div className="relative justify-self-end" ref={dropdownRef}>
            <button onClick={() => setIsDropdownOpen(!isDropdownOpen)} className="flex items-center gap-1.5 px-3 py-1 rounded-full bg-muted hover:bg-gray-200 dark:hover:bg-gray-700 transition-all duration-200 cursor-pointer text-xs">
              <span className="text-muted-foreground">Voice:</span>
              <span className="font-medium">{voices[selectedVoice]?.name}</span>
              <svg className={`w-3 h-3 transition-transform duration-200 ${isDropdownOpen ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
              </svg>
            </button>

            {isDropdownOpen && <div className="absolute right-0 mt-1 w-auto bg-white dark:bg-black border border-gray-200 dark:border-gray-700 rounded-lg overflow-hidden z-50">
                {voices.map((voice, index) => <button key={index} onClick={() => {
    setSelectedVoice(index);
    setIsDropdownOpen(false);
  }} className={`w-full px-3 py-1.5 text-left text-xs hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors flex items-center gap-2 ${index === selectedVoice ? 'bg-gray-100 dark:bg-gray-800 font-medium' : ''}`}>
                    {voice.id && <img src={`https://public-platform.r2.fish.audio/coverimage/${voice.id}`} alt={voice.name} className="w-5 h-5 rounded-full m-0 flex-shrink-0 object-cover" />}
                    <span className="flex-1 whitespace-nowrap">{voice.name}</span>
                  </button>)}
              </div>}
          </div> : <div className="justify-self-end" />}
      </div>

      {}
      <div className="px-3 py-1.5 bg-card">
        <audio ref={audioRef} src={currentVoice?.url} preload="metadata" />

        <div className="flex items-center gap-2">
          {}
          <button onClick={togglePlay} className="flex-shrink-0 w-6 h-6 flex items-center justify-center bg-gray-300 dark:bg-gray-600 text-gray-800 dark:text-gray-200 rounded-full hover:opacity-80 transition-opacity relative overflow-hidden" aria-label={isPlaying ? 'Pause' : 'Play'}>
            <div className="transition-transform duration-300 ease-in-out" style={{
    transform: isPlaying ? 'rotate(180deg)' : 'rotate(0deg)'
  }}>
              {isPlaying ? <svg className="w-3 h-3" fill="currentColor" viewBox="0 0 24 24">
                  <path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z" />
                </svg> : <svg className="w-3 h-3 ml-0.5" fill="currentColor" viewBox="0 0 24 24">
                  <path d="M8 5v14l11-7z" />
                </svg>}
            </div>
          </button>

          {}
          <div className="flex-1 flex items-center gap-2">
            <span className="text-xs font-mono text-gray-500 dark:text-gray-400 min-w-[35px]">
              {formatTime(currentTime)}
            </span>

            <div className="flex-1 relative h-1 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
              <div className="absolute top-0 left-0 h-full bg-gray-400 dark:bg-gray-500 transition-all duration-100" style={{
    width: `${duration ? currentTime / duration * 100 : 0}%`
  }} />
              <input type="range" min="0" max={duration || 0} value={currentTime} onChange={handleProgressChange} className="absolute top-0 left-0 w-full h-full opacity-0 cursor-pointer" />
            </div>
            <span className="text-xs font-mono text-gray-500 dark:text-gray-400 min-w-[35px]">
              {formatTime(duration)}
            </span>
          </div>
        </div>
      </div>
    </div>;
};

<AudioTranscript
  voices={[
{
  "id": "8ef4a238714b45718ce04243307c57a7",
  "name": "E-girl",
  "url": "https://pub-b995142090474379a930b856ab79b4d4.r2.dev/audio/python-sdk-legacy-migration-guide/8ef4a238714b45718ce04243307c57a7.mp3"
},
{
  "id": "802e3bc2b27e49c2995d23ef70e6ac89",
  "name": "Energetic Male",
  "url": "https://pub-b995142090474379a930b856ab79b4d4.r2.dev/audio/python-sdk-legacy-migration-guide/802e3bc2b27e49c2995d23ef70e6ac89.mp3"
},
{
  "id": "933563129e564b19a115bedd57b7406a",
  "name": "Sarah",
  "url": "https://pub-b995142090474379a930b856ab79b4d4.r2.dev/audio/python-sdk-legacy-migration-guide/933563129e564b19a115bedd57b7406a.mp3"
},
{
  "id": "bf322df2096a46f18c579d0baa36f41d",
  "name": "Adrian",
  "url": "https://pub-b995142090474379a930b856ab79b4d4.r2.dev/audio/python-sdk-legacy-migration-guide/bf322df2096a46f18c579d0baa36f41d.mp3"
},
{
  "id": "b347db033a6549378b48d00acb0d06cd",
  "name": "Selene",
  "url": "https://pub-b995142090474379a930b856ab79b4d4.r2.dev/audio/python-sdk-legacy-migration-guide/b347db033a6549378b48d00acb0d06cd.mp3"
},
{
  "id": "536d3a5e000945adb7038665781a4aca",
  "name": "Ethan",
  "url": "https://pub-b995142090474379a930b856ab79b4d4.r2.dev/audio/python-sdk-legacy-migration-guide/536d3a5e000945adb7038665781a4aca.mp3"
}
]}
/>

<Note>
  This guide helps you migrate from the legacy `fish_audio_sdk` (Session-based API) to the new `fishaudio` (client-based API) available in `fish-audio-sdk` v1.0+.
</Note>

## Quick Migration

<Steps>
  <Step title="Uninstall and reinstall the package">
    ```bash theme={null}
    pip uninstall fish-audio-sdk
    pip install fish-audio-sdk
    ```

    The package name stays the same, but the import changes from `fish_audio_sdk` to `fishaudio`.

    You can still keep using the `fish_audio_sdk` package if you'd like though. Just be aware that it will not receive any new features or updates.

    Since this is a versioning jump from `v2025.6.3` to `v1.0.0`, you may need to pin the version explicitly with `fish-audio-sdk==1.0.0`.
  </Step>

  <Step title="Update imports">
    ```python theme={null}
    # Before
    from fish_audio_sdk import Session, TTSRequest, ASRRequest

    # After
    from fishaudio import FishAudio
    from fishaudio.types import TTSConfig, ReferenceAudio
    ```
  </Step>

  <Step title="Replace Session with Client">
    ```python theme={null}
    # Before
    session = Session("your_api_key")

    # After
    client = FishAudio(api_key="your_api_key")
    # Or use environment variable
    client = FishAudio()  # Reads from FISH_API_KEY
    ```
  </Step>

  <Step title="Update API calls">
    See the quick reference below for common operations.
  </Step>
</Steps>

## Key Changes at a Glance

| Legacy                   | New                             | Notes                         |
| ------------------------ | ------------------------------- | ----------------------------- |
| `Session()`              | `FishAudio()`                   | Client-based architecture     |
| `session.tts()`          | `client.tts.convert()`          | Returns complete audio bytes  |
| `session.asr()`          | `client.asr.transcribe()`       | Clearer method name           |
| `session.create_model()` | `client.voices.create()`        | "Model" → "Voice" terminology |
| `session.list_models()`  | `client.voices.list()`          | Resource namespacing          |
| `TTSRequest(...)`        | Direct parameters               | No request objects            |
| `WebSocketSession`       | `client.tts.stream_websocket()` | Integrated into client        |
| `HttpCodeErr`            | Specific exceptions             | Better error handling         |

## Text-to-Speech Migration

<CodeGroup>
  ```python Legacy theme={null}
  from fish_audio_sdk import Session, TTSRequest

  session = Session("your_api_key")

  # Basic TTS - returns chunks
  audio = b""
  for chunk in session.tts(TTSRequest(text="Hello, world!")):
      audio += chunk

  with open("output.mp3", "wb") as f:
      f.write(audio)
  ```

  ```python New theme={null}
  from fishaudio import FishAudio
  from fishaudio.utils import save

  client = FishAudio()

  # Basic TTS - returns complete audio
  audio = client.tts.convert(text="Hello, world!")
  save(audio, "output.mp3")
  ```
</CodeGroup>

<Note>
  The new SDK's `convert()` returns complete audio bytes instead of chunks. Use `stream()` for chunk-by-chunk transfer or `stream_websocket()` for real-time streaming.
</Note>

## Voice Cloning Migration

<CodeGroup>
  ```python Legacy theme={null}
  from fish_audio_sdk import Session, TTSRequest, ReferenceAudio

  session = Session("your_api_key")

  # Instant cloning
  with open("voice.wav", "rb") as f:
      request = TTSRequest(
          text="Cloned voice",
          references=[ReferenceAudio(
              audio=f.read(),
              text="Reference transcript"
          )]
      )
      audio = b"".join(session.tts(request))

  # Create voice model
  model = session.create_model(
      title="My Voice",
      voices=[voice_data],
      texts=["Sample text"]
  )
  ```

  ```python New theme={null}
  from fishaudio import FishAudio
  from fishaudio.types import ReferenceAudio

  client = FishAudio()

  # Instant cloning
  with open("voice.wav", "rb") as f:
      audio = client.tts.convert(
          text="Cloned voice",
          references=[ReferenceAudio(
              audio=f.read(),
              text="Reference transcript"
          )]
      )

  # Create voice model
  voice = client.voices.create(
      title="My Voice",
      voices=[voice_data],
      texts=["Sample text"]
  )
  ```
</CodeGroup>

## Speech-to-Text Migration

<CodeGroup>
  ```python Legacy theme={null}
  from fish_audio_sdk import Session, ASRRequest

  session = Session("your_api_key")

  with open("audio.mp3", "rb") as f:
      response = session.asr(ASRRequest(
          audio=f.read(),
          language="en"
      ))

  print(response.text)

  # Timestamps in SECONDS
  for segment in response.segments:
      print(f"[{segment.start}s - {segment.end}s]")
  ```

  ```python New theme={null}
  from fishaudio import FishAudio

  client = FishAudio()

  with open("audio.mp3", "rb") as f:
      result = client.asr.transcribe(
          audio=f.read(),
          language="en"
      )

  print(result.text)

  # Timestamps in MILLISECONDS
  for segment in result.segments:
      print(f"[{segment.start}ms - {segment.end}ms]")
  ```
</CodeGroup>

<Warning>
  ASR timestamps changed from seconds to milliseconds. Divide by 1000 to convert: `seconds = segment.start / 1000`
</Warning>

## WebSocket Streaming Migration

<CodeGroup>
  ```python Legacy theme={null}
  from fish_audio_sdk import WebSocketSession, TTSRequest

  ws_session = WebSocketSession("your_api_key")

  def text_stream():
      yield "Hello, "
      yield "streaming!"

  with ws_session:
      for chunk in ws_session.tts(TTSRequest(text=""), text_stream()):
          # Process audio chunks
          pass
  ```

  ```python New theme={null}
  from fishaudio import FishAudio

  client = FishAudio()

  def text_chunks():
      yield "Hello, "
      yield "streaming!"

  # No empty text required, no context manager needed
  audio_stream = client.tts.stream_websocket(text_chunks())
  for chunk in audio_stream:
      # Process audio chunks
      pass
  ```
</CodeGroup>

## Error Handling Migration

<CodeGroup>
  ```python Legacy theme={null}
  from fish_audio_sdk.exceptions import HttpCodeErr

  try:
      audio = session.tts(request)
  except HttpCodeErr as e:
      if e.status_code == 429:
          print("Rate limited")
      elif e.status_code == 401:
          print("Auth failed")
  ```

  ```python New theme={null}
  from fishaudio.exceptions import (
      RateLimitError,
      AuthenticationError,
      FishAudioError
  )

  try:
      audio = client.tts.convert(text="...")
  except RateLimitError as e:
      print(f"Rate limited. Retry after {e.retry_after}s")
  except AuthenticationError:
      print("Auth failed")
  except FishAudioError as e:
      print(f"General error: {e}")
  ```
</CodeGroup>

## Async Support

The new SDK has full async support with `AsyncFishAudio`:

```python theme={null}
import asyncio
from fishaudio import AsyncFishAudio

async def main():
    client = AsyncFishAudio()

    # All methods work with await
    audio = await client.tts.convert(text="Async speech")
    result = await client.asr.transcribe(audio=audio_bytes)
    voices = await client.voices.list()

asyncio.run(main())
```

## Breaking Changes Summary

<AccordionGroup>
  <Accordion title="TTS now returns complete audio, not chunks">
    **Before:** Iterator of chunks

    ```python theme={null}
    audio = b""
    for chunk in session.tts(request):
        audio += chunk
    ```

    **After:** Complete audio bytes

    ```python theme={null}
    audio = client.tts.convert(text="...")
    ```

    Use `stream()` or `stream_websocket()` if you need chunks.
  </Accordion>

  <Accordion title="No more request objects (TTSRequest, ASRRequest)">
    **Before:**

    ```python theme={null}
    request = TTSRequest(text="...", format="mp3")
    audio = session.tts(request)
    ```

    **After:**

    ```python theme={null}
    audio = client.tts.convert(text="...", format="mp3")
    ```

    Pass parameters directly to methods.
  </Accordion>

  <Accordion title="ASR timestamps changed from seconds to milliseconds">
    **Before:** `segment.start` in seconds (e.g., 1.5)

    **After:** `segment.start` in milliseconds (e.g., 1500)

    Convert: `seconds = segment.start / 1000`
  </Accordion>

  <Accordion title="Model terminology changed to Voice">
    * `session.create_model()` → `client.voices.create()`
    * `session.list_models()` → `client.voices.list()`
    * `session.get_model()` → `client.voices.get()`

    Plus new methods: `client.voices.update()` and `client.voices.delete()`
  </Accordion>
</AccordionGroup>

## Common Issues

<AccordionGroup>
  <Accordion title="ModuleNotFoundError: No module named 'fishaudio'">
    Upgrade the package:

    ```bash theme={null}
    pip install --upgrade fish-audio-sdk
    python -c "import fishaudio; print(fishaudio.__version__)"
    ```
  </Accordion>

  <Accordion title="Code expects TTS chunks but gets complete audio">
    The new `convert()` returns complete audio. Use `stream()` for chunks:

    ```python theme={null}
    audio_stream = client.tts.stream(text="...")
    for chunk in audio_stream:
        process_chunk(chunk)
    ```
  </Accordion>

  <Accordion title="WebSocket requires empty text parameter">
    Remove the empty text. Just pass your generator:

    ```python theme={null}
    # Before
    ws_session.tts(TTSRequest(text=""), text_stream())

    # After
    client.tts.stream_websocket(text_stream())
    ```
  </Accordion>

  <Accordion title="ASR timestamps are off by 1000x">
    New SDK uses milliseconds instead of seconds:

    ```python theme={null}
    seconds = segment.start / 1000
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Python SDK Guide" icon="python" href="/developer-guide/sdk-guide/python">
    Complete guide for the new SDK
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/sdk/python">
    Detailed API documentation
  </Card>

  <Card title="Text-to-Speech" icon="microphone" href="/developer-guide/sdk-guide/python/text-to-speech">
    TTS features and examples
  </Card>

  <Card title="Voice Cloning" icon="clone" href="/developer-guide/sdk-guide/python/voice-cloning">
    Clone voices and manage models
  </Card>
</CardGroup>

## Need Help?

* [GitHub Repository](https://github.com/fishaudio/fish-audio-python) - Report issues or request features
* [Discord Community](https://discord.gg/fishaudio) - Get help from the community
* [PyPI Package](https://pypi.org/project/fish-audio-sdk/) - Package information
