> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nativeport.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nativeport.ai/_mcp/server.

# Fish Audio

Fronts four stateless actions on [Fish Audio](https://docs.fish.audio)'s platform (`https://api.fish.audio`); the secret `Authorization: Bearer` key comes from the gateway — Fish Audio's only credential channel. Fish Audio is a full voice-cloning platform with a persistent-model and account-balance surface too, but only the four routes below are mounted; every other Fish Audio path draws the gateway's own `404`:

```
POST /fishaudio/v1/tts                          → https://api.fish.audio/v1/tts
POST /fishaudio/v1/tts/stream/with-timestamp     → https://api.fish.audio/v1/tts/stream/with-timestamp
POST /fishaudio/v1/asr                           → https://api.fish.audio/v1/asr
POST /fishaudio/v1/voice-design                  → https://api.fish.audio/v1/voice-design
```

**Persistent voice models and account balance are not exposed.** `/model` (create/list/get/update/delete a named, persistent voice-clone model) and `/wallet/{user_id}/package`, `/wallet/{user_id}/api-credit` (the shared account's subscription and credit balance) are never mounted — the gateway injects one shared Fish Audio key, so a persistent model or balance reachable by one client would be reachable by every client. Per-request **zero-shot cloning stays available**: the `references` field on `POST /v1/tts` (and its streaming variant) sends reference audio inline with the call, which Fish Audio uses for that one request only and never turns into a shared resource.

## Credential handling

* **`Authorization: Bearer` is overwritten** with the injected Fish Audio key — your gateway token and anything you send are both replaced. This is the only credential channel Fish Audio documents; there's no query-param spelling to strip.
* The **`model` header** — how `/v1/tts`, its streaming variant, and `/v1/voice-design` pick a model — is an ordinary content header, untouched. `/v1/asr` takes no `model` header; Fish Audio's ASR is a single model.

## Text to speech

`POST /fishaudio/v1/tts` — `model` header required (`s1`, `s2-pro`, or `s2.1-pro`). Body: `text` is the only required field; `reference_id` picks a voice from Fish Audio's library or your own models, `references` (`application/msgpack` bodies only) supplies inline audio+transcript pairs for zero-shot cloning, and `format` (`mp3` default, `wav`, `pcm`, `opus`), `sample_rate`, `prosody`, and `latency` control the output.

```bash
curl -X POST "$GATEWAY/fishaudio/v1/tts" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "model: s2.1-pro" \
  -d '{"text": "Hello! Welcome to the gateway.", "reference_id": "model-id", "format": "mp3"}' \
  --output speech.mp3
```

The response is chunked binary audio (`Transfer-Encoding: chunked`) in the requested `format`, relayed byte-for-byte.

`POST /fishaudio/v1/tts/stream/with-timestamp` takes the identical request shape and `model` header, but answers as **Server-Sent Events**: each event is one JSON payload carrying `audio_base64` (concatenate every chunk, in arrival order, to reconstruct the audio), `content`, a cumulative `alignment` snapshot (`{audio_duration, segments: [{text, start, end}]}`) keyed by `chunk_seq` — replace the stored snapshot for that `chunk_seq` on each new event, don't append — and `chunk_audio_offset_sec`.

## Speech to text

`POST /fishaudio/v1/asr` — no `model` header. Body: `audio` (required; base64 in a JSON body, or raw bytes in `application/msgpack`), an optional `language` hint, and `ignore_timestamps` (default `true`; set `false` for per-segment timing on audio under 30 seconds, at higher latency).

```bash
curl -X POST "$GATEWAY/fishaudio/v1/asr" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"audio\": \"$(base64 -i speech.mp3)\"}"
```

Response: JSON `{"text", "duration", "segments": [{"text", "start", "end"}, ...]}`, relayed untouched.

## Voice design

`POST /fishaudio/v1/voice-design` — `model` header pinned to `voice-design-1`, its only value. Body: `instruction` (required, 1–2000 characters describing the voice), plus optional `reference_text`, `language`, `n` (1–4 candidates, default 2), `speed`, `num_step`, `guidance_scale`, `instruct_guidance_scale`, and `seed`.

```bash
curl -X POST "$GATEWAY/fishaudio/v1/voice-design" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "model: voice-design-1" \
  -d '{"instruction": "Warm, confident studio narrator with a natural tone", "reference_text": "Welcome to the gateway.", "n": 2}'
```

Response: JSON `{"candidates": [{"id", "index", "audio_base64", "sample_rate", "duration_ms", "text", "instruct", "language"}, ...]}`. Candidates are one-off generations, not persisted voices — turning one into a reusable model goes through `/model`, which isn't mounted here.

## Billing

Metered on Fish Audio's own documented prices, read from the request or response rather than a flat guess:

| Route                                      | Meter                                                            | Rate                |
| ------------------------------------------ | ---------------------------------------------------------------- | ------------------- |
| `/v1/tts`, `/v1/tts/stream/with-timestamp` | Real UTF-8 byte count of the request's `text`                    | \$15 / 1M bytes     |
| `/v1/asr`                                  | The response's real `duration`, rounded up to the nearest second | \$0.36 / audio hour |
| `/v1/voice-design`                         | Flat, per successful call                                        | \$0.01 / request    |

`text` has no documented length ceiling, so TTS bills the exact byte count instead of a flat rate that could silently under-bill a long request. A `references`-bearing `application/msgpack` body, or a malformed/non-string `text`, is billed on the whole request body's byte length instead — never smaller than the `text` bytes it contains.

## WebSocket streaming

**Fish Audio's WebSocket streaming endpoint is not proxied** — the gateway's bearer gate, logging, and billing are HTTP-only, and unlike ElevenLabs, Fish Audio has no short-lived-credential broker to mint a socket-scoped secret through. For low-latency audio over HTTP, use `/v1/tts/stream/with-timestamp`'s chunked SSE response.

Field-level request and response detail lives in [Fish Audio's API reference](https://docs.fish.audio/api-reference/introduction); the gateway modifies neither.