Fish Audio

Text-to-speech, transcription, and voice design behind four fixed routes

View as Markdown

Fronts four stateless actions on 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/ttsmodel 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.

$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).

$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-designmodel 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.

$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:

RouteMeterRate
/v1/tts, /v1/tts/stream/with-timestampReal UTF-8 byte count of the request’s text$15 / 1M bytes
/v1/asrThe response’s real duration, rounded up to the nearest second$0.36 / audio hour
/v1/voice-designFlat, 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; the gateway modifies neither.