> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aireserve.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating from Fish Audio

> Keep your Fish Audio client. Point it at api.aireserve.com/fish-audio with your AI Reserve API key — text to speech, speech to text and voice design pass through unchanged and land on your AI Reserve ledger.

AI Reserve serves the **Fish Audio API** at `https://api.aireserve.com/fish-audio`.
A Fish Audio client whose base URL is repointed here — the `fish-audio-sdk`
Python package, the `fish-audio` JavaScript package, curl — keeps working after
**two settings change**: the base URL and the key. The gateway authenticates
your AI Reserve key, swaps it for AI Reserve's Fish Audio key, forwards the
request exactly as you sent it (JSON or MessagePack, multipart uploads, the
`model` header), relays Fish's answer exactly as Fish gave it, and books each
successful request to your wallet at Fish's list price.

Any AI Reserve API key works here; there is no separate credential and no
organization setting to turn on.

| Fish Audio setting | AI Reserve value |
| - | - |
| Base URL (default `https://api.fish.audio`) | `https://api.aireserve.com/fish-audio` — the client appends its own paths, so `/v1/tts` becomes `https://api.aireserve.com/fish-audio/v1/tts` |
| `FISH_API_KEY` from fish.audio | Your AI Reserve API key (`aireserve_api_…`). Same `Authorization: Bearer` header |

> [!NOTE]
>
> **This is a passthrough, not a translation.** Request bodies, the `model` header, voice ids (`reference_id`),
> inline `references` for zero-shot cloning, output formats, response shapes and Fish's own errors are all Fish's.
> The OpenAI-shaped alternative — `POST /v1/audio/speech` with `grok-tts` or the `eleven-*` models — is documented
> under [Text to speech](/capabilities/text-to-speech).

## What is served

| Fish Audio endpoint | Through AI Reserve | Billed as |
| - | - | - |
| `POST /v1/tts` | ✅ | UTF-8 bytes of `text` at \*\*$15 per million** (`s2.1-pro`, `s2-pro`, `drama-3`, `s1`); `s2.1-pro-free` at $0 |
| `POST /v1/tts/stream/with-timestamp` | ✅ | Same as `/v1/tts` |
| `POST /v1/asr` | ✅ | Audio seconds, rounded up, at **\$0.36 per hour** (`transcribe-1-pro` and `transcribe-1`) |
| `POST /v1/voice-design` | ✅ | **\$0.01** per successful request |
| `GET /model`, `GET /model/{id}` | ✅ | Free — browse the voice library and read a voice's details |
| `POST` / `PATCH` / `DELETE /model` | ❌ `403` | Stored voice models are not available yet (below) |
| `wss://…/v1/tts/live` | ❌ `403` | WebSocket streaming is not served yet; `POST /v1/tts` streams its bytes |
| `/wallet/*` | ❌ `403` | Your usage is billed to your AI Reserve wallet, not a Fish balance |
| `/v1/agent/*` | ❌ `403` | The Fish voice-agent platform is not available through AI Reserve |
| Anything else | ❌ `404` | |

The model is selected the way Fish selects it: the `model` **HTTP header**.
Without it, or with an id Fish does not recognise, synthesis is served and
billed as `s2.1-pro`; transcription is served and billed as `transcribe-1`
unless the header reads exactly `transcribe-1-pro`. The header is forwarded
as you sent it.

Each successful call appears as its own line in **Observe › Event Log** and
**Financials** (model `fish-audio:s2.1-pro`, `fish-audio:transcribe-1-pro`,
`fish-audio:voice-design-1`, …, with Fish's `X-Generation-Id`, the metered
bytes or seconds and the rate in its details). Fish does not bill requests
that fail, and neither does AI Reserve: a `4xx` or `5xx` from Fish is relayed
and books nothing.

## What to change

```diff Shell theme={"dark"}
-export FISH_API_KEY="…"
+export FISH_API_KEY="aireserve_api_…"                     # your AI Reserve API key
+export FISH_BASE_URL="https://api.aireserve.com/fish-audio"
```

```python Python (fish-audio-sdk ≥ 1.0) theme={"dark"}
import os
from fishaudio import FishAudio
from fishaudio.core.request_options import RequestOptions
from fishaudio.utils import save

client = FishAudio(
    api_key=os.environ["FISH_API_KEY"],          # aireserve_api_…
    base_url="https://api.aireserve.com/fish-audio",
)

audio = client.tts.convert(text="Hello from Fish Audio, through AI Reserve.", model="s2.1-pro")
save(audio, "hello.mp3")

with open("meeting.wav", "rb") as f:
    transcript = client.asr.transcribe(
        audio=f.read(),
        request_options=RequestOptions(additional_headers={"model": "transcribe-1-pro"}),
    )
print(transcript.text, transcript.duration)
```

```ts TypeScript (fish-audio) theme={"dark"}
import { FishAudioClient } from "fish-audio";

const fishAudio = new FishAudioClient({
  apiKey: process.env.FISH_API_KEY,                 // aireserve_api_…
  baseUrl: "https://api.aireserve.com/fish-audio",
});

const audio = await fishAudio.textToSpeech.convert({ text: "Hello from Fish Audio, through AI Reserve." }, "s2.1-pro");
const transcript = await fishAudio.speechToText.convert({ audio: myAudio }, { headers: { model: "transcribe-1-pro" } });
const voices = await fishAudio.voices.search({ title: "narrator" });
```

```bash curl theme={"dark"}
# Text to speech (JSON; the SDKs send MessagePack — both pass through)
curl -X POST https://api.aireserve.com/fish-audio/v1/tts \
  -H "Authorization: Bearer $FISH_API_KEY" -H "Content-Type: application/json" -H "model: s2.1-pro" \
  -d '{"text":"Hello from Fish Audio, through AI Reserve.","reference_id":"933563129e564b19a115bedd57b7406a","format":"mp3"}' \
  --output hello.mp3

# Speech to text
curl -X POST https://api.aireserve.com/fish-audio/v1/asr \
  -H "Authorization: Bearer $FISH_API_KEY" -H "model: transcribe-1-pro" \
  -F "audio=@meeting.wav" -F "ignore_timestamps=false"

# Voice library
curl "https://api.aireserve.com/fish-audio/model?title=narrator&language=en" -H "Authorization: Bearer $FISH_API_KEY"
```

## Voice cloning

Fish offers two ways to clone a voice. **Instant cloning** — reference audio
and its transcript sent inline in `references` on `/v1/tts` — works unchanged
through AI Reserve and needs nothing stored. **Stored voice models**
(`POST /model`, which returns a reusable `_id`) are not available yet: every
AI Reserve organization reaches Fish through one shared account, and a model
stored there would be visible to every other organization's `GET /model?self=true`.
Until stored clones are tenant-scoped, `POST`, `PATCH` and `DELETE /model`
answer `403` with
`Stored voice models cannot be created, changed or deleted through AI Reserve yet. Clone per request by sending reference audio in \`references\` on /v1/tts, or pick a library voice from GET /model.\`

## What AI Reserve enforces

Every refusal is a JSON `{ "status": <code>, "message": "…" }` body, the
smallest shape Fish's clients read, and names its cause in `message`.

* **Your organization's policies.** The key is checked exactly as on every AI
  Reserve endpoint: paused keys (`401`), invalid keys (`401`, `Invalid API
  key.`), per-key IP allowlists and organization network policies (`403`),
  rate limits (`429` with `Retry-After`), and — on every `POST` — your
  organization's spend caps and wallet (`402`). A Fish request is a wallet
  request: an exhausted budget refuses it before Fish is called.
* **Metering is a precondition, not an afterthought.** A `/v1/tts` body the
  gateway cannot read as JSON or MessagePack with a string `text` is refused
  with `400` before any upstream call; a deployment that cannot reach the
  ledger refuses billable calls with `503` rather than serving them unmetered.
* **Headers.** Your `Authorization` and `x-api-key` are replaced by AI
  Reserve's Fish key; `Accept-Encoding` is pinned to `identity` so Fish's
  bytes are the bytes relayed. The `model` header, content types and your
  client's user agent go through. Fish's `X-Generation-Id` comes back on
  every synthesis and transcription.
* **A request log.** Each call is recorded with method, path, Fish's
  generation id, upstream status, byte counts, time-to-headers, duration,
  outcome, your key id, and the metered amount. Refusals are recorded with
  their outcome before any upstream request. Neither includes bodies,
  audio, or credentials.
* **Transport.** Fish unreachable → `503`; no response headers within the
  time-to-headers bound (sized for multi-minute transcriptions) → `504`; a
  stream that has started is never cut by that bound.

## Limits

* **Rate limits are Fish's concurrency tiers, shared.** Fish limits
  concurrent requests per account, not per key; AI Reserve's account tier is
  shared across every organization using this endpoint, and a `429` from Fish
  is relayed as Fish sends it (without `Retry-After`). Back off
  exponentially, as Fish's own docs advise.
* **One Fish account, Fish's data handling.** Text, audio uploads and
  reference audio reach Fish Audio under AI Reserve's account and Fish's
  terms. See the [provider data-handling page](https://privacy.aireserve.com/data-handling).
* **No stored voices, no WebSockets, no agents, no wallet** (above).
* **The native API only.** Fish's OpenAI- and ElevenLabs-compatible
  endpoints (`/compat/*`, `/elevenlabs/*`) are not mounted; use the OpenAI
  shape on AI Reserve's own [`/v1/audio/speech`](/capabilities/text-to-speech)
  instead.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.