Skip to main content
Configure the client once — serverURL takes the /api/v1 prefix:
From Python, OpenRouter’s own SDK works the same way — pip install openrouter, then pass server_url:
The OpenAI SDK works against the same base URL too — the Python tabs below use it.

Text to speech

The response is a ReadableStream: audio bytes arrive as they are synthesized. The optional voice field takes a voice ID from your Fish voice library (omitted or empty = model default — see Voices). input_references — OpenRouter’s first-class field for stateless voice cloning, reference audio plus its transcript — is supported: no saved voice model required. Both SDKs carry the field on their SpeechRequest type (inputReferences in TypeScript, input_references in Python).

temperature, top_p, repetition_penalty

These three sit at the top level of the speech request body, next to model and input — no provider.options wrapper required — matching what the catalog advertises in each model’s supported_parameters.
Neither SDK’s speech type declares them, so they still have to go around the typed request — as raw JSON via fetch, or through the OpenAI SDK’s extra_body. The same three are also reachable through provider.options.fish-audio and the X-Fish-* headers, and if you set a value two ways the more specific one wins: X-Fish-* header beats provider.options, and provider.options beats the top level. Everything else in the Fish parameter set — latency, chunk_length, normalize, prosody, references — is available only through those two channels, not at the top level. See Fish-native parameters.

Transcription

JSON bodies with base64 input_audio also work, matching OpenRouter’s schema. For word-level timing, ask for response_format="verbose_json" together with timestamp_granularities=["word"].

Model catalog

Entries validate against the OpenRouter Model schema: every required field is present, and the ones with no Fish counterpart are explicitly null rather than missing — SDK model validation passes and model.context_length reads as None/null instead of raising. Four fields are always null: context_length, default_parameters, per_request_limits, and supported_voices. Six optional ones — alias_target, benchmarks, expiration_date, hugging_face_id, knowledge_cutoff, and reasoning — are simply absent; don’t write code that expects the key to exist. Filter with output_modalities=speech or text to list one direction only.

Error handling

Errors use the OpenRouter envelope, with the HTTP status as an integer in error.code. Failures from the Fish Audio API surface as type: "provider_error" with their original status and metadata.provider_name: "fish-audio"; errors from the compatibility layer itself carry no metadata — just code, message, and type.

Going further

Compatibility

The full contract: mappings, limits, and explicit refusals.

Fish-native parameters

Reach latency, chunk_length, normalize, and more via provider.options or X-Fish-* headers.