Fish Audio S2.1 Pro API: Pricing, Documentation
by Fish Audio
Fish Audio API provides a powerful, high-performance text-to-speech platform designed for rapid voice synthesis and cloning. It features advanced artificial intelligence models that process written text into highly natural, human-like voice outputs with extremely low latency. The interface enables developers to easily integrate professional audio capabilities, customize tonal qualities, and generate high-fidelity audio streams for diverse applications.

Models Version
Fish Audio Drama 3 API Documentation
Direct a voice performance in plain language: put directions in square brackets, place sounds and pauses where they happen, change the delivery of specific words with paired tags, and render a whole multi-speaker scene in one request, in 83 languages. Asynchronous: submit returns a request_id; poll the status endpoint until the request is COMPLETED.
POST https://gateway.pixazo.ai/fish-audio-drama-3/v1/text-to-speechPreview. Results vary between takes and Fish Audio may update the model during the preview; each take is a separate billed request.
Authentication
All requests require an API key passed via header.
| Header | Type | Required | Description |
|---|---|---|---|
| Ocp-Apim-Subscription-Key | string | Yes | Your API subscription key |
Text to Speech - Fish Audio Drama 3
Request Code
POST https://gateway.pixazo.ai/fish-audio-drama-3/v1/text-to-speech
Content-Type: application/json
Ocp-Apim-Subscription-Key: YOUR_SUBSCRIPTION_KEY
{
"text": "[tired, trying to sound upbeat] I told you this would happen. [sigh] <whisper>But I am glad you came back.</whisper>",
"reference_id": "f3a2b90078d54a65af4b95f63bc7798e",
"format": "mp3"
}import requests
url = "https://gateway.pixazo.ai/fish-audio-drama-3/v1/text-to-speech"
headers = {
"Content-Type": "application/json",
"Ocp-Apim-Subscription-Key": "YOUR_SUBSCRIPTION_KEY"
}
data = {
"text": "[tired, trying to sound upbeat] I told you this would happen. [sigh] <whisper>But I am glad you came back.</whisper>",
"reference_id": "f3a2b90078d54a65af4b95f63bc7798e",
"format": "mp3"
}
resp = requests.post(url, json=data, headers=headers)
print(resp.json())const res = await fetch("https://gateway.pixazo.ai/fish-audio-drama-3/v1/text-to-speech", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Ocp-Apim-Subscription-Key": "YOUR_SUBSCRIPTION_KEY"
},
body: JSON.stringify({
"text": "[tired, trying to sound upbeat] I told you this would happen. [sigh] <whisper>But I am glad you came back.</whisper>",
"reference_id": "f3a2b90078d54a65af4b95f63bc7798e",
"format": "mp3"
})
});
console.log(await res.json());curl -X POST 'https://gateway.pixazo.ai/fish-audio-drama-3/v1/text-to-speech' \
-H 'Content-Type: application/json' \
-H 'Ocp-Apim-Subscription-Key: YOUR_SUBSCRIPTION_KEY' \
--data-raw '{"text": "[tired, trying to sound upbeat] I told you this would happen. [sigh] <whisper>But I am glad you came back.</whisper>", "reference_id": "f3a2b90078d54a65af4b95f63bc7798e", "format": "mp3"}'Output
{
"request_id": "fish-audio-drama-3_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "QUEUED",
"polling_url": "https://gateway.pixazo.ai/v2/requests/status/fish-audio-drama-3_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}Webhook (Optional)
Instead of polling, you can receive a Webhook callback when the request reaches a terminal state. Provide a Webhook URL via header on the submit request.
| Header | Required | Description |
|---|---|---|
| X-Webhook-URL | To enable | HTTPS URL to receive the Webhook callback. |
| X-Webhook-Mode | No | terminal (default, one callback on COMPLETED/ERROR) or sync (per-poll callbacks). |
Example: enable Webhook
curl -X POST 'https://gateway.pixazo.ai/fish-audio-drama-3/v1/text-to-speech' \
-H 'Content-Type: application/json' \
-H 'Ocp-Apim-Subscription-Key: YOUR_SUBSCRIPTION_KEY' \
-H 'X-Webhook-URL: https://your-server.com/webhook' \
--data-raw '{"text": "[tired, trying to sound upbeat] I told you this would happen. [sigh] <whisper>But I am glad you came back.</whisper>", "reference_id": "f3a2b90078d54a65af4b95f63bc7798e", "format": "mp3"}'Callback Payload (success)
{
"request_id": "fish-audio-drama-3_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "COMPLETED",
"model_id": "fish-audio-drama-3",
"error": null,
"output": {
"media_url": [
"https://pub-582b7213209642b9b995c96c95a30381.r2.dev/v1/fish-audio-drama-3_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/output.mp3"
],
"media_type": "audio/mpeg"
},
"created_at": "2026-10-14T16:40:58.264Z",
"updated_at": "2026-10-14 16:42:31",
"completed_at": "2026-10-14 16:42:31"
}Failure callback shape
{
"request_id": "fish-audio-drama-3_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "ERROR",
"model_id": "fish-audio-drama-3",
"error": "Description of the failure",
"output": null,
"created_at": "2026-10-14T16:40:58.264Z",
"updated_at": "2026-10-14 16:42:31",
"completed_at": "2026-10-14 16:42:31"
}Delivery semantics
- terminal mode: one Webhook callback when the request is COMPLETED or ERROR.
- sync mode: a Webhook callback on each status change.
- Callbacks are idempotent on
request_id— de-duplicate on it. - Respond
200within a few seconds; the Webhook endpoint must be HTTPS.
Request Parameters
| Parameter | Required | Type | Default | Allowed values / range | Description |
|---|---|---|---|---|---|
text | Yes | string | — | 1 – 5000 characters | The script to perform. Directions in square brackets, paired tags and speaker tags go inline in this string (see Directions and tags below); every one of them counts toward the 5,000-character limit and toward the billed size. |
reference_id | No | string or array | — | 32-char hex voice id, or an array of up to 8 | The voice(s) to perform with. Pass a single string for one speaker, or an array of ids for multi-speaker dialogue (see Multi-Speaker Dialogue below). Omit to use the provider default voice. Browse voices at fish.audio; the id is the last path segment of fish.audio/m/<id>. You are responsible for holding the rights to any voice you reference. |
format | No | string | mp3 | mp3, wav | Output audio container. mp3 returns audio/mpeg; wav returns 16-bit mono PCM at 44.1 kHz. |
mp3_bitrate | No | integer | 128 | 64, 128, 192 | MP3 bitrate in kbps. Only valid when format is mp3; sent together with wav, the request fails. |
latency | No | string | balanced | normal, balanced | Synthesis latency trade-off. balanced (our default) favours a shorter time to first audio; normal accepts more latency in exchange for the provider's best quality. |
chunk_length | No | integer | 300 | 100 to 300 | Text chunk size used during synthesis. Smaller chunks start faster; larger chunks give steadier prosody across long passages. |
normalize | No | boolean | true | true, false | Normalize numbers, dates and abbreviations in English and Chinese text before synthesis. |
temperature | No | number | — | 0.0 to 1.0 | Controls expressiveness: higher is more varied, lower is more consistent. Omit it to use the model's own default. |
top_p | No | number | — | 0.0 to 1.0 | Controls diversity via nucleus sampling. Omit it to use the model's own default. |
prosody | No | object | — | — | Optional prosody controls (see the two fields below). |
prosody.speed | No | number | 1.0 | 0.5 to 2.0 | Speaking rate multiplier. 2.0 is twice as fast, 0.5 is half speed. |
prosody.volume | No | number | 0 | -20 to 20 | Volume adjustment in decibels. |
How text is measured
Billed on the UTF-8 bytes of text, including every direction, tag and speaker marker, rounded up to whole 1,000-byte blocks (minimum one). Most non-Latin scripts use 2–4 bytes per character. The rate is in the pricing panel on this page.
Billing is measured in UTF-8 bytes, not characters. For English and other Latin-script text the two are the same — one character is one byte — but characters outside the ASCII range take more than one byte. Directions and tags are text too: [sigh] adds 6 bytes and <|speaker:0|> adds 13.
| Input | Characters | UTF-8 bytes | Billed blocks |
|---|---|---|---|
| English | 500 | 500 | 1 |
| English | 1,200 | 1,200 | 2 |
| Chinese / Japanese (3 bytes per character) | 416 | 1,248 | 2 |
| Longest single request, English | 5,000 | 5,000 | 5 |
| Longest single request, Chinese / Japanese | 5,000 | 15,000 | 15 |
The 5,000-character limit on text counts characters, not bytes — and characters outside the Basic Multilingual Plane (most emoji) count as two toward it.
Parameters not supported
Worth reading if you are porting existing Fish Audio code — some parameters are rejected outright, some fail once the request is processed, and others are accepted and then ignored.
Rejected with 400
Checked when you submit: the request is rejected before it is queued.
| Parameter | Why | Use instead |
|---|---|---|
model | The model is fixed for this endpoint (Fish Audio Drama 3) and is not caller-selectable. | — |
references | Inline zero-shot voice cloning requires a MessagePack request body, which this JSON endpoint cannot carry. | reference_id with a Fish Audio voice id |
format: "pcm" / "opus" | Not offered; mp3 and wav cover playback in every browser and player. | mp3 or wav |
n, batch_size, count, num_images, number_of_images | This API returns one audio file per request. | One request per audio file (every take is its own request) |
Accepted, then the request fails
These values pass the submit check, so the submit returns 202, but they are validated while the request is processed: the request fails, and the status endpoint (or Webhook) reports status: "ERROR" with an error message that names the problem.
| Parameter | When the request fails |
|---|---|
reference_id | Not a 32-character hexadecimal voice id; an empty array; more than 8 ids; two or more ids without any <|speaker:N|> tag in text; an id that does not name an existing voice. |
mp3_bitrate | Sent together with format: "wav". |
prosody | Not an object; prosody.speed outside 0.5 to 2.0; prosody.volume outside -20 to 20. |
Accepted but ignored
These are silently dropped — the request still returns 202, but the value has no effect on the generated audio. Only the parameters listed in the table above are forwarded.
sample_rate, opus_bitrate, max_new_tokens, repetition_penalty, min_chunk_length, condition_on_previous_chunks, early_stop_threshold, features, pronunciation_dictionary, prosody.normalize_loudness, and any parameter not listed above |
Voice, Directions & Dialogue
Choosing a voice
reference_id accepts two forms. Omit it entirely to use the provider default voice. A voice that fits the role makes the biggest difference to how well a performance lands.
| Form | Use for | Value |
|---|---|---|
| String | A single speaker | "f3a2b90078d54a65af4b95f63bc7798e" |
| Array | Multi-speaker dialogue | ["<speaker-0-id>", "<speaker-1-id>"] — up to 8 ids |
Voice ids are 32-character hexadecimal strings. Browse the Fish Audio voice library at fish.audio — the id is the last path segment of fish.audio/m/<id>. Three public voices to start with:
| Voice | Language | reference_id |
|---|---|---|
| Sheila | English (US), female | f3a2b90078d54a65af4b95f63bc7798e |
| Delia | English (US), female | c70cfb98867a4bfb9ae20d2401257b92 |
| Adrian | English (US), male | bf322df2096a46f18c579d0baa36f41d |
Directions and tags
Directions are plain text inside text, and you can mix them freely in the same line. Bracketed directions are performance notes, not words to speak. There is no fixed list: describe tone, pacing and character in your own words.
| Technique | How to write it | Example |
|---|---|---|
| Describe the delivery | A direction in square brackets before the words it applies to. Write it the way you would brief a voice actor: emotion, intent, pacing, or who the character is talking to. A new direction mid-line changes the read from that point. | [warm, a little nervous, speaking to an old friend] It has been a long time. [suddenly serious] We need to talk. |
| Sounds and pauses | Put the sound or pause in brackets exactly where it should happen: a sigh, a laugh, a breath, a short pause. | I did it. [sigh] I finally did it. [small laugh] [pause] Nobody believed me. |
| Directions in any language | Directions can be written in English or in another language, and they do not have to match the language of the script. | [con voz suave y cansada] I will be home late tonight. |
| Change only specific words | Wrap the words in a paired tag to change the delivery of those words only: <whisper>, <emphasis>, <soft>, <fast>, <slow>, <stress>, <forceful>. | Listen carefully. <whisper>They are right behind us.</whisper> <fast>Run, now!</fast> |
| Switch language mid-line (Experimental) | Wrap a phrase in a language tag such as <french>, <spanish>, <german>, <japanese>, <chinese>, <korean> or <italian> to switch language for those words only. | My grandmother always said <spanish>poco a poco</spanish>, which means little by little. |
| Pin a pronunciation | Wrap the phonemes in <|phoneme_start|> and <|phoneme_end|>: CMU Arpabet for English (the digit on a vowel marks stress: 1 primary, 2 secondary, 0 unstressed), pinyin for Chinese, romaji for Japanese. | You say <|phoneme_start|>T AH0 M EY1 T OW2<|phoneme_end|>, I say <|phoneme_start|>T AH0 M AA1 T OW2<|phoneme_end|>. |
- Paired tags are written exactly as listed; in this API the speed tags are
<fast>and<slow>. - Language tags are experimental: they are not guaranteed for every language or voice, and the model may switch language on its own when the text is already multilingual. Wrap only the words that change language; a voice whose speaker is fluent in both languages works best.
- Directions, tags and speaker markers count toward the 5,000-character limit and toward the billed size (see How text is measured above).
Multi-Speaker Dialogue
Pass an array of voice ids and start each turn in text with a <|speaker:N|> tag. The index in the tag selects the voice at that position in the array — <|speaker:0|> uses the first id, <|speaker:1|> the second, and so on. Directions and paired tags work inside each turn. This scene uses two voices:
{
"text": "<|speaker:0|>[calm, measured storyteller voice] The ship had been silent for three days.<|speaker:1|>[low, gravelly, tired but in control] Status report.<|speaker:0|>[barely above a whisper] Nobody answered.<|speaker:1|>[slow, dangerously quiet] Then we wait. <emphasis>Together.</emphasis>",
"reference_id": [
"c70cfb98867a4bfb9ae20d2401257b92",
"bf322df2096a46f18c579d0baa36f41d"
],
"format": "mp3"
}- Text before any tag is spoken by the first voice in the array.
- Supply one id per speaker index you use, up to 8 ids.
- With two or more ids and no
<|speaker:N|>tag intext, the request fails, because every line would otherwise be rendered in the first voice. - For a single speaker, pass the id as a plain string — tags are not needed.
Example Request
{
"text": "[tired, trying to sound upbeat] I told you this would happen. [sigh] <whisper>But I am glad you came back.</whisper>",
"reference_id": "f3a2b90078d54a65af4b95f63bc7798e",
"format": "mp3"
}Example Response
{
"request_id": "fish-audio-drama-3_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "QUEUED",
"polling_url": "https://gateway.pixazo.ai/v2/requests/status/fish-audio-drama-3_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}Request Headers
| Header | Required | Description |
|---|---|---|
| Content-Type | Yes | application/json |
| Ocp-Apim-Subscription-Key | Yes | Your API subscription key. |
| X-Webhook-URL | No | Enable Webhook callbacks (see Webhook section). |
Response Handling
| Status Code | Meaning |
|---|---|
| 202 | Accepted — request queued; returns request_id and polling_url. |
| 400 | Bad request — the body failed the submit check and was not queued: text missing, blank or over 5000 characters; a format other than mp3 or wav; mp3_bitrate, latency or chunk_length outside its allowed values; temperature or top_p not a number from 0 to 1; normalize not a boolean; or any of model, references, n, batch_size, count, num_images, number_of_images (see “Parameters not supported” above). |
| 401 | Unauthorized — missing or invalid subscription key. |
| 402 | Insufficient balance. |
| 403 | Forbidden. |
| 429 | Too many requests. |
| 500 | Internal server error. |
Problems that can only be detected while the request is processed (“Accepted, then the request fails” above) are not a 400: they are reported through the status endpoint or Webhook with status: "ERROR".
Error Responses
A 400 names every rejected parameter in violations:
{
"error": "Invalid request parameters",
"message": "'format' must be one of: \"mp3\", \"wav\" (got \"opus\")",
"violations": [
"'format' must be one of: \"mp3\", \"wav\" (got \"opus\")"
]
}A request that fails after it was queued is reported via the status endpoint (or Webhook) with status: "ERROR" and an error message — for example when reference_id does not name an existing voice.
Retrieving Results
Poll the status endpoint with the request_id from the submit response until status is COMPLETED or ERROR, then download the audio from output.media_url.
curl 'https://gateway.pixazo.ai/v2/requests/status/fish-audio-drama-3_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' \
-H 'Ocp-Apim-Subscription-Key: YOUR_SUBSCRIPTION_KEY'Completed response
Captured from a real Drama 3 request at launch; the request id is masked.
{
"request_id": "fish-audio-drama-3_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "COMPLETED",
"model_id": "fish-audio-drama-3",
"error": null,
"output": {
"media_url": [
"https://pub-582b7213209642b9b995c96c95a30381.r2.dev/v1/fish-audio-drama-3_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/output.mp3"
],
"media_type": "audio/mpeg"
},
"created_at": "2026-10-14T16:40:58.264Z",
"updated_at": "2026-10-14 16:42:31",
"completed_at": "2026-10-14 16:42:31"
}Response Fields
| Field | Type | Description |
|---|---|---|
| request_id | string | Unique request identifier. |
| status | string | QUEUED, PROCESSING, COMPLETED or ERROR. |
| model_id | string | The model that handled the request. |
| error | string or null | Error message when status is ERROR; otherwise null. |
| output.media_url | array of strings | URL of the generated audio (one file per request). Download it and keep your own copy. |
| output.media_type | string | Audio MIME type (audio/mpeg for mp3, audio/wav for wav). |
| created_at | string | Request creation timestamp. |
| updated_at | string | Last status change timestamp. |
| completed_at | string | Completion timestamp. |
| polling_url | string | Status URL for this request (in the submit response). |
Status Values & Flow
QUEUED → PROCESSING → COMPLETED (success) or ERROR (failure). Treat any status other than QUEUED, PROCESSING or COMPLETED as a failure.
- Save the
request_idfrom the202response. - Poll the status endpoint every few seconds, or use a Webhook instead.
- When
statusisCOMPLETED, download the audio fromoutput.media_url.
Fish Audio Drama 3 API Pricing
No data available
Could not load current pricing
Fish Audio S2.1 Pro API Documentation
Generate natural, expressive speech from text in 83 languages, with free-form emotion markers and a library of over 2.5 million community voices. Asynchronous: submit returns a request_id; poll the status endpoint until the request is COMPLETED. Typical synthesis completes in a few seconds.
POST https://gateway.pixazo.ai/fish-audio-s2-1-pro/v1/text-to-speechAuthentication
All requests require an API key passed via header.
| Header | Type | Required | Description |
|---|---|---|---|
| Ocp-Apim-Subscription-Key | string | Yes | Your API subscription key |
Text to Speech - Fish Audio S2.1 Pro
Request Code
POST https://gateway.pixazo.ai/fish-audio-s2-1-pro/v1/text-to-speech
Content-Type: application/json
Ocp-Apim-Subscription-Key: YOUR_SUBSCRIPTION_KEY
{
"text": "[whispering] When you are building something new, there is this mix of wonder and fear. [chuckling] But that is what makes it worth building.",
"reference_id": "933563129e564b19a115bedd57b7406a",
"format": "mp3",
"mp3_bitrate": 128
}import requests
url = "https://gateway.pixazo.ai/fish-audio-s2-1-pro/v1/text-to-speech"
headers = {
"Content-Type": "application/json",
"Ocp-Apim-Subscription-Key": "YOUR_SUBSCRIPTION_KEY"
}
data = {
"text": "[whispering] When you are building something new, there is this mix of wonder and fear. [chuckling] But that is what makes it worth building.",
"reference_id": "933563129e564b19a115bedd57b7406a",
"format": "mp3",
"mp3_bitrate": 128
}
resp = requests.post(url, json=data, headers=headers)
print(resp.json())const res = await fetch("https://gateway.pixazo.ai/fish-audio-s2-1-pro/v1/text-to-speech", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Ocp-Apim-Subscription-Key": "YOUR_SUBSCRIPTION_KEY"
},
body: JSON.stringify({
"text": "[whispering] When you are building something new, there is this mix of wonder and fear. [chuckling] But that is what makes it worth building.",
"reference_id": "933563129e564b19a115bedd57b7406a",
"format": "mp3",
"mp3_bitrate": 128
})
});
console.log(await res.json());curl -X POST 'https://gateway.pixazo.ai/fish-audio-s2-1-pro/v1/text-to-speech' \
-H 'Content-Type: application/json' \
-H 'Ocp-Apim-Subscription-Key: YOUR_SUBSCRIPTION_KEY' \
--data-raw '{"text": "[whispering] When you are building something new, there is this mix of wonder and fear. [chuckling] But that is what makes it worth building.", "reference_id": "933563129e564b19a115bedd57b7406a", "format": "mp3", "mp3_bitrate": 128}'Output
{
"request_id": "fish-audio-s2-1-pro_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "QUEUED",
"polling_url": "https://gateway.pixazo.ai/v2/requests/status/fish-audio-s2-1-pro_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}Webhook (Optional)
Instead of polling, you can receive a Webhook callback when the request reaches a terminal state. Provide a Webhook URL via header on the submit request.
| Header | Required | Description |
|---|---|---|
| X-Webhook-URL | To enable | HTTPS URL to receive the Webhook callback. |
| X-Webhook-Mode | No | terminal (default, one callback on COMPLETED/ERROR) or sync (per-poll callbacks). |
Example: enable Webhook
curl -X POST 'https://gateway.pixazo.ai/fish-audio-s2-1-pro/v1/text-to-speech' \
-H 'Content-Type: application/json' \
-H 'Ocp-Apim-Subscription-Key: YOUR_SUBSCRIPTION_KEY' \
-H 'X-Webhook-URL: https://your-server.com/webhook' \
--data-raw '{"text": "[whispering] When you are building something new, there is this mix of wonder and fear. [chuckling] But that is what makes it worth building.", "reference_id": "933563129e564b19a115bedd57b7406a", "format": "mp3", "mp3_bitrate": 128}'Callback Payload (success)
{
"request_id": "fish-audio-s2-1-pro_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "COMPLETED",
"model_id": "fish-audio-s2-1-pro",
"output": {
"media_url": "https://pub-582b7213209642b9b995c96c95a30381.r2.dev/fish-audio/1785407392754-66f0f615.mp3",
"media_type": "audio/mpeg"
},
"created_at": "2026-07-30T10:29:44.313Z",
"completed_at": "2026-07-30T10:29:53.391Z"
}Failure callback shape
{
"request_id": "fish-audio-s2-1-pro_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "ERROR",
"model_id": "fish-audio-s2-1-pro",
"error": "Description of the failure"
}Delivery semantics
- terminal mode: one Webhook callback when the request is COMPLETED or ERROR.
- sync mode: a Webhook callback on each status change.
- Callbacks are idempotent on
request_id— de-duplicate on it. - Respond
200within a few seconds; the Webhook endpoint must be HTTPS.
Request Parameters
| Parameter | Required | Type | Default | Allowed values / range | Description |
|---|---|---|---|---|---|
text | Yes | string | — | 1 – 5000 characters | The text to synthesize. Supports emotion and effect markers such as [whispering], [chuckling], [laughing], [sighing] and [audience laughing]. Markers are free-form and add no cost or latency. |
reference_id | No | string or array | — | 32-char hex voice id, or an array of up to 8 | The voice(s) to speak with. Pass a single string for one speaker, or an array of ids for multi-speaker dialogue (see Multi-Speaker Dialogue below). Omit to use the provider default voice. Browse voices at fish.audio; the id is the last path segment of fish.audio/m/<id>. You are responsible for holding the rights to any voice you reference. |
format | No | string | mp3 | mp3, wav | Output audio container. mp3 returns audio/mpeg; wav returns 16-bit mono PCM at 44.1 kHz. |
mp3_bitrate | No | integer | 128 | 64, 128, 192 | MP3 bitrate in kbps. Only valid when format is mp3; sending it with wav returns 400. |
latency | No | string | balanced | normal, balanced | Synthesis latency trade-off. balanced (our default) favours the lowest time-to-first-audio; normal accepts more latency in exchange for the provider's best quality. |
chunk_length | No | integer | 300 | 100 to 300 | Text chunk size used during synthesis. Smaller chunks start faster; larger chunks give steadier prosody across long passages. |
normalize | No | boolean | true | true, false | Normalize numbers, dates and abbreviations in English and Chinese text before synthesis. |
temperature | No | number | 0.7 | 0.0 to 1.0 | Controls expressiveness. Higher is more varied, lower is more consistent. |
top_p | No | number | 0.7 | 0.0 to 1.0 | Controls diversity via nucleus sampling. |
prosody | No | object | — | — | Optional prosody controls (see the two fields below). |
prosody.speed | No | number | 1.0 | 0.5 to 2.0 | Speaking rate multiplier. 2.0 is twice as fast, 0.5 is half speed. |
prosody.volume | No | number | 0 | -20 to 20 | Volume adjustment in decibels. |
Parameters not supported
Worth reading if you are porting existing Fish Audio code — some parameters are rejected outright, while others are accepted and then ignored.
Rejected with 400
| Parameter | Why | Use instead |
|---|---|---|
model | The model is fixed for this endpoint and is not caller-selectable. | — |
references | Inline zero-shot voice cloning requires a MessagePack request body, which this JSON endpoint cannot carry. | reference_id with a Fish Audio voice id |
format: "pcm" / "opus" | Not offered; mp3 and wav cover playback in every browser and player. | mp3 or wav |
Accepted but ignored
These are silently dropped — the request still returns 202, but the value has no effect on the generated audio. Only the parameters listed in the table above are forwarded.
sample_rate, opus_bitrate, max_new_tokens, repetition_penalty, min_chunk_length, condition_on_previous_chunks, early_stop_threshold, features, prosody.normalize_loudness, and any parameter not listed above |
Voice & Emotion Control
Choosing a voice
reference_id accepts two forms. Omit it entirely to use the provider default voice.
| Form | Use for | Value |
|---|---|---|
| String | A single speaker | "933563129e564b19a115bedd57b7406a" |
| Array | Multi-speaker dialogue | ["<speaker-0-id>", "<speaker-1-id>"] — up to 8 ids |
Voice ids are 32-character hexadecimal strings. Browse the Fish Audio voice library at fish.audio — the id is the last path segment of fish.audio/m/<id>.
Multi-Speaker Dialogue
Pass an array of voice ids and mark speaker changes in text with <|speaker:N|> tags. The index in the tag selects the voice at that position in the array — <|speaker:0|> uses the first id, <|speaker:1|> the second, and so on.
{
"text": "<|speaker:0|>Good morning!<|speaker:1|>Good morning! How are you?<|speaker:0|>I am great, thanks for asking!",
"reference_id": [
"933563129e564b19a115bedd57b7406a",
"d13f84b987ad4f22b56d2b47f4eb838e"
],
"format": "mp3"
}- Text before any tag is spoken by the first voice in the array.
- A tag index with no matching array entry is not valid — supply one id per speaker index you use.
- Passing two or more ids without any
<|speaker:N|>tag returns400, because every line would otherwise be rendered in the first voice. - For a single speaker, pass the id as a plain string — tags are not needed.
Emotion & effect markers
Place markers inline in text using square brackets. They are interpreted as performance direction and are not spoken aloud.
| Category | Examples |
|---|---|
| Emotions | [happy], [sad], [angry], [excited], [calm], [nervous], [confident], [sarcastic] |
| Tone | [whispering], [shouting], [soft tone], [in a hurry tone], [emphasis] |
| Audio effects | [laughing], [chuckling], [sighing], [gasping], [panting], [clear throat] |
| Special | [audience laughing], [background laughter], [break], [long-break] |
Markers are not restricted to this list — any descriptive phrase works, for example [whispers sweetly] or [laughing nervously]. Use at most three emotion markers per sentence, and place sentence-level direction at the start of the sentence. Markers add no character cost and no latency.
Example Request
{
"text": "[whispering] When you are building something new, there is this mix of wonder and fear. [chuckling] But that is what makes it worth building.",
"reference_id": "933563129e564b19a115bedd57b7406a",
"format": "mp3",
"mp3_bitrate": 128
}Example Response
{
"request_id": "fish-audio-s2-1-pro_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "QUEUED",
"polling_url": "https://gateway.pixazo.ai/v2/requests/status/fish-audio-s2-1-pro_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}Request Headers
| Header | Required | Description |
|---|---|---|
| Content-Type | Yes | application/json |
| Ocp-Apim-Subscription-Key | Yes | Your API subscription key. |
| X-Webhook-URL | No | Enable Webhook callbacks (see Webhook section). |
Response Handling
| Status Code | Meaning |
|---|---|
| 202 | Accepted — request queued; returns request_id and polling_url. |
| 400 | Bad request — invalid or missing parameters: missing text, text over 5000 characters, a malformed reference_id, mp3_bitrate sent with format: "wav", or two or more reference_id values without <|speaker:N|> tags. Also returned for references and model — see “Parameters not supported” above. |
| 401 | Unauthorized — missing or invalid subscription key. |
| 402 | Insufficient balance. |
| 403 | Forbidden. |
| 429 | Too many requests. |
| 500 | Internal server error. |
Error Responses
{
"error": "Insufficient Balance",
"message": "Your wallet does not have enough balance."
}A failed generation is reported via the status endpoint (or Webhook) with status: "ERROR" and an error message — for example Reference not found when reference_id does not name an existing voice.
Retrieving Results
Poll the status endpoint with the request_id from the submit response until status is COMPLETED (or FAILED/ERROR).
curl 'https://gateway.pixazo.ai/v2/requests/status/fish-audio-s2-1-pro_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' \
-H 'Ocp-Apim-Subscription-Key: YOUR_SUBSCRIPTION_KEY'Completed response
{
"request_id": "fish-audio-s2-1-pro_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "COMPLETED",
"model_id": "fish-audio-s2-1-pro",
"output": {
"media_url": "https://pub-582b7213209642b9b995c96c95a30381.r2.dev/fish-audio/1785407392754-66f0f615.mp3",
"media_type": "audio/mpeg"
},
"created_at": "2026-07-30T10:29:44.313Z",
"completed_at": "2026-07-30T10:29:53.391Z"
}Response Fields
| Field | Type | Description |
|---|---|---|
| request_id | string | Unique request identifier. |
| status | string | QUEUED, PROCESSING, COMPLETED, FAILED, or ERROR. |
| model_id | string | The model that handled the request. |
| output.media_url | string | Permanent URL of the generated audio. |
| output.media_type | string | Audio MIME type (audio/mpeg for mp3, audio/wav for wav). |
| created_at | string | Request creation timestamp. |
| completed_at | string | Completion timestamp. |
| polling_url | string | Status URL for this request. |
| error | string | Error message when status is FAILED/ERROR. |
Status Values & Flow
QUEUED → PROCESSING → COMPLETED (success) or FAILED/ERROR (failure).
Pricing
Billed at $0.02 per 1,000 UTF-8 bytes of input text, rounded up to the next whole 1,000. Every request bills at least one block.
Billing is measured in UTF-8 bytes, not characters. For English and other Latin-script text the two are the same — one character is one byte — but characters outside the ASCII range take more than one byte, so the same character count costs more in those languages.
| Input | Characters | UTF-8 bytes | Blocks | Cost |
|---|---|---|---|---|
| English | 500 | 500 | 1 | $0.02 |
| English | 1,200 | 1,200 | 2 | $0.04 |
| Chinese / Japanese (3 bytes per character) | 416 | 1,248 | 2 | $0.04 |
| Maximum single request, English | 5,000 | 5,000 | 5 | $0.10 |
The 5,000-character limit on text counts characters, not bytes — and characters outside the Basic Multilingual Plane (most emoji) count as two toward it. The most expensive possible request is therefore 5,000 three-byte characters, such as Chinese or Japanese: 15,000 bytes, or $0.30. Text made of 4-byte characters hits the limit at 2,500 characters, or $0.20.
Fish Audio S2.1 Pro API Pricing
No data available
Could not load current pricing
Fish Audio Speech to Text API Documentation
Transcribe audio into text with automatic language detection and optional word-level timestamps. Asynchronous: submit returns a request_id; poll the status endpoint until the request is COMPLETED. The transcript is returned as a JSON file.
POST https://gateway.pixazo.ai/fish-audio-speech-to-text/v1/speech-to-textAuthentication
All requests require an API key passed via header.
| Header | Type | Required | Description |
|---|---|---|---|
| Ocp-Apim-Subscription-Key | string | Yes | Your API subscription key |
Speech to Text - Fish Audio
Request Code
POST https://gateway.pixazo.ai/fish-audio-speech-to-text/v1/speech-to-text
Content-Type: application/json
Ocp-Apim-Subscription-Key: YOUR_SUBSCRIPTION_KEY
{
"audio_url": "https://pub-582b7213209642b9b995c96c95a30381.r2.dev/doc-assets/audio/speech-17s.mp3",
"language": "en",
"ignore_timestamps": false
}import requests
url = "https://gateway.pixazo.ai/fish-audio-speech-to-text/v1/speech-to-text"
headers = {
"Content-Type": "application/json",
"Ocp-Apim-Subscription-Key": "YOUR_SUBSCRIPTION_KEY"
}
data = {
"audio_url": "https://pub-582b7213209642b9b995c96c95a30381.r2.dev/doc-assets/audio/speech-17s.mp3",
"language": "en",
"ignore_timestamps": false
}
resp = requests.post(url, json=data, headers=headers)
print(resp.json())const res = await fetch("https://gateway.pixazo.ai/fish-audio-speech-to-text/v1/speech-to-text", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Ocp-Apim-Subscription-Key": "YOUR_SUBSCRIPTION_KEY"
},
body: JSON.stringify({
"audio_url": "https://pub-582b7213209642b9b995c96c95a30381.r2.dev/doc-assets/audio/speech-17s.mp3",
"language": "en",
"ignore_timestamps": false
})
});
console.log(await res.json());curl -X POST 'https://gateway.pixazo.ai/fish-audio-speech-to-text/v1/speech-to-text' \
-H 'Content-Type: application/json' \
-H 'Ocp-Apim-Subscription-Key: YOUR_SUBSCRIPTION_KEY' \
--data-raw '{"audio_url": "https://pub-582b7213209642b9b995c96c95a30381.r2.dev/doc-assets/audio/speech-17s.mp3", "language": "en", "ignore_timestamps": false}'Output
{
"request_id": "fish-audio-speech-to-text_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "QUEUED",
"polling_url": "https://gateway.pixazo.ai/v2/requests/status/fish-audio-speech-to-text_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}Webhook (Optional)
Instead of polling, you can receive a Webhook callback when the request reaches a terminal state. Provide a Webhook URL via header on the submit request.
| Header | Required | Description |
|---|---|---|
| X-Webhook-URL | To enable | HTTPS URL to receive the Webhook callback. |
| X-Webhook-Mode | No | terminal (default, one callback on COMPLETED/ERROR) or sync (per-poll callbacks). |
Example: enable Webhook
curl -X POST 'https://gateway.pixazo.ai/fish-audio-speech-to-text/v1/speech-to-text' \
-H 'Content-Type: application/json' \
-H 'Ocp-Apim-Subscription-Key: YOUR_SUBSCRIPTION_KEY' \
-H 'X-Webhook-URL: https://your-server.com/webhook' \
--data-raw '{"audio_url": "https://pub-582b7213209642b9b995c96c95a30381.r2.dev/doc-assets/audio/speech-17s.mp3", "language": "en", "ignore_timestamps": false}'Callback Payload (success)
{
"request_id": "fish-audio-speech-to-text_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "COMPLETED",
"model_id": "fish-audio-speech-to-text",
"output": {
"media_url": "https://pub-582b7213209642b9b995c96c95a30381.r2.dev/v1/{request_id}/output.json",
"media_type": "application/json"
},
"created_at": "2026-07-30T14:49:39.898Z",
"completed_at": "2026-07-30T14:49:59.000Z"
}Failure callback shape
{
"request_id": "fish-audio-speech-to-text_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "ERROR",
"model_id": "fish-audio-speech-to-text",
"error": "Description of the failure"
}Delivery semantics
- terminal mode: one Webhook callback when the request is COMPLETED or ERROR.
- sync mode: a Webhook callback on each status change.
- Callbacks are idempotent on
request_id— de-duplicate on it. - Respond
200within a few seconds; the Webhook endpoint must be HTTPS.
Request Parameters
| Parameter | Required | Type | Default | Allowed values / range | Description |
|---|---|---|---|---|---|
audio_url | Yes | string | — | http(s) URL, file up to 25 MB | Public URL of the audio to transcribe. Common formats are supported, including mp3, wav, m4a, flac and ogg. The file is fetched by the gateway, so the URL must be reachable without authentication. |
language | No | string | — (auto-detected) | One of 57 ISO codes: af (Afrikaans), ar (Arabic), hy (Armenian), az (Azerbaijani), be (Belarusian), bs (Bosnian), bg (Bulgarian), ca (Catalan), zh (Chinese), hr (Croatian), cs (Czech), da (Danish), nl (Dutch), en (English), et (Estonian), fi (Finnish), fr (French), gl (Galician), de (German), el (Greek), he (Hebrew), hi (Hindi), hu (Hungarian), is (Icelandic), id (Indonesian), it (Italian), ja (Japanese), kn (Kannada), kk (Kazakh), ko (Korean), lv (Latvian), lt (Lithuanian), mk (Macedonian), ms (Malay), mr (Marathi), mi (Maori), ne (Nepali), no (Norwegian), fa (Persian), pl (Polish), pt (Portuguese), ro (Romanian), ru (Russian), sr (Serbian), sk (Slovak), sl (Slovenian), es (Spanish), sw (Swahili), sv (Swedish), tl (Tagalog), ta (Tamil), th (Thai), tr (Turkish), uk (Ukrainian), ur (Urdu), vi (Vietnamese), cy (Welsh) | Optional language hint. Omit it and the model detects the language automatically, returning what it found in the transcript. There is no auto option — sending a placeholder such as "auto-detected" is rejected with HTTP 400. |
ignore_timestamps | No | boolean | true | true, false | Set to false to include word-level segments with start and end times. Adds latency on audio shorter than 30 seconds. |
Audio limits
- The audio file must be at most 25 MB. Larger files are rejected before transcription starts.
audio_urlmust be anhttporhttpsURL and must be publicly reachable.- There is no fixed duration limit; cost scales with the length of the audio (see Pricing).
Transcript Format
The completed request returns output.media_url, a JSON file containing the transcript. Fetch that URL to read the result.
{
"text": "Strong consistency test. This should complete in a few seconds, not sixty.",
"language": "English",
"language_code": "en",
"duration": 5.8253125,
"segments": [
{
"text": "Strong",
"start": 0,
"end": 0.56
},
{
"text": "consistency",
"start": 0.56,
"end": 1.12
}
]
}| Field | Type | Description |
|---|---|---|
| text | string | The full transcript. |
| language | string | Detected language name, e.g. English. |
| language_code | string | Detected language code, e.g. en. |
| duration | number | Length of the transcribed audio in seconds. This is the quantity you are billed on. |
| segments | array | Word-level entries of {text, start, end} with times in seconds. Empty unless ignore_timestamps is false. |
Example Request
{
"audio_url": "https://pub-582b7213209642b9b995c96c95a30381.r2.dev/doc-assets/audio/speech-17s.mp3",
"language": "en",
"ignore_timestamps": false
}Example Response
{
"request_id": "fish-audio-speech-to-text_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "QUEUED",
"polling_url": "https://gateway.pixazo.ai/v2/requests/status/fish-audio-speech-to-text_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}Request Headers
| Header | Required | Description |
|---|---|---|
| Content-Type | Yes | application/json |
| Ocp-Apim-Subscription-Key | Yes | Your API subscription key. |
| X-Webhook-URL | No | Enable Webhook callbacks (see Webhook section). |
Response Handling
| Status Code | Meaning |
|---|---|
| 202 | Accepted — request queued; returns request_id and polling_url. |
| 400 | Bad request — missing audio_url, a non-http(s) URL, or a wrongly typed language / ignore_timestamps. |
| 401 | Unauthorized — missing or invalid subscription key. |
| 402 | Insufficient balance. |
| 429 | Too many requests. |
| 500 | Internal server error. |
An unreachable audio_url, an oversized file, or a file that is not recognisable audio is reported through the status endpoint as status: "ERROR", not as a synchronous error. Failed requests are not billed.
Retrieving Results
Poll the status endpoint with the request_id from the submit response until status is COMPLETED (or FAILED/ERROR), then fetch output.media_url for the transcript.
curl 'https://gateway.pixazo.ai/v2/requests/status/fish-audio-speech-to-text_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' \
-H 'Ocp-Apim-Subscription-Key: YOUR_SUBSCRIPTION_KEY'Completed response
{
"request_id": "fish-audio-speech-to-text_019dxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "COMPLETED",
"model_id": "fish-audio-speech-to-text",
"output": {
"media_url": "https://pub-582b7213209642b9b995c96c95a30381.r2.dev/v1/{request_id}/output.json",
"media_type": "application/json"
},
"created_at": "2026-07-30T14:49:39.898Z",
"completed_at": "2026-07-30T14:49:59.000Z"
}Response Fields
| Field | Type | Description |
|---|---|---|
| request_id | string | Unique request identifier. |
| status | string | QUEUED, PROCESSING, COMPLETED, FAILED, or ERROR. |
| model_id | string | The model that handled the request. |
| output.media_url | string | URL of the transcript JSON file. |
| output.media_type | string | application/json. |
| created_at | string | Request creation timestamp. |
| completed_at | string | Completion timestamp. |
| error | string | Error message when status is FAILED/ERROR. |
Status Values & Flow
QUEUED → PROCESSING → COMPLETED (success) or FAILED/ERROR (failure).
Pricing
Billed at $0.006 per minute of audio ($0.36 per audio hour), rounded up to the next whole minute. Billing is based on the length of the audio you submit, not on the size of the transcript.
| Audio length | Billed minutes | Cost |
|---|---|---|
| 6 seconds | 1 | $0.006 |
| 90 seconds | 2 | $0.012 |
| 10 minutes | 10 | $0.06 |
| 1 hour | 60 | $0.36 |
A hold is placed when the request is submitted, because the audio length is not known until transcription completes. The hold is reduced to the real cost once the duration is known, and released in full if the request fails.
Fish Audio Speech to Text API Pricing
No data available
Could not load current pricing
⚡ Performance
Live usage measured on Pixazo's gateway, split by model version. Generation time is how long a generation takes end-to-end (lower is better). Success rate is the percent of generations that complete (higher is better).
〰 Uptime
Percent of generations that succeeded over the selected period, per model version.