Fish Audio Docs
API ReferenceText to SpeechSynchronous Text to Speech

Synchronous Text to Speech v3

Generate audio synchronously or create recoverable asynchronous jobs with the provider-neutral HTTP v3 contract.

HTTP TTS v3 is the recommended contract for new integrations. The platform owns provider routing; clients use only public voiceId, modelId, and generic controls.

The endpoint, authentication, and response structure stay stable when you change models. You must still confirm that the voice's modelIds contains the target modelId, because supported controls, text limits, and output formats vary by model.

Base URL

https://fishaudio.org/api/open/v3

Every endpoint except capabilities and the OpenAPI document requires a server-side Bearer API key. Never embed the key in browser code, mobile binaries, or public repositories.

Choose synchronous or asynchronous delivery

Use caseEndpointWhy
Short text and the caller can keep the HTTP request openPOST /speech/ttsReturns audio bytes directly
Long text, batches, or durable result lookupPOST /speech/tts/jobsReturns a job ID that can be polled and downloaded again
The output must be recoverable after a lost responseAsynchronous JobsThe synchronous endpoint does not replay completed audio

Step 1: discover model capabilities

Capabilities is public. Read it before constructing a generation request instead of hard-coding model behavior.

curl "https://fishaudio.org/api/open/v3/speech/tts/capabilities"
{
  "contractVersion": "v3",
  "route": "/api/open/v3/speech/tts",
  "models": [
    {
      "id": "fishaudio-s21pro-flash",
      "available": true,
      "maxTextLength": 10000,
      "outputFormats": ["mp3", "wav", "ogg"],
      "controls": {
        "speed": true,
        "volume": true,
        "stability": true,
        "similarity": true
      },
      "supportedOperations": {
        "synchronous": true,
        "asynchronous": true
      }
    }
  ]
}

Choose only a model with available=true. Treat its maxTextLength, outputFormats, and controls as authoritative.

Step 2: find a compatible voice

curl "https://fishaudio.org/api/open/v3/voices?page=1&pageSize=20&includePersonal=false" \
  -H "Authorization: Bearer $FISHAUDIO_API_KEY"
{
  "total": 1,
  "page": 1,
  "pageSize": 20,
  "totalPages": 1,
  "items": [
    {
      "voiceId": "00a1b221-6137-4b73-ad62-b0cbce134167",
      "name": "System test voice",
      "isPersonal": false,
      "primaryLanguage": "en",
      "languages": ["en"],
      "modelIds": ["fishaudio-s21pro-flash"]
    }
  ],
  "requestId": "req_xxx"
}

modelIds is the list of public models currently supported by that voice. Generation requires a combination where the model is available and the voice supports it. Use GET /voices/{voiceId} to inspect one voice.

Step 3: generate synchronously

POST /api/open/v3/speech/tts
curl "https://fishaudio.org/api/open/v3/speech/tts" \
  -H "Authorization: Bearer $FISHAUDIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Request-Id: trace-123-segment-1" \
  -H "Idempotency-Key: tts-order-123-segment-1" \
  -d '{
    "text": "Hello from HTTP TTS v3.",
    "voiceId": "00a1b221-6137-4b73-ad62-b0cbce134167",
    "modelId": "fishaudio-s21pro-flash",
    "format": "mp3",
    "speed": 1,
    "volume": 0,
    "stability": 1,
    "similarity": 1,
    "language": "en",
    "textNormalization": true
  }' \
  --output speech.mp3

Request fields

FieldTypeRequiredConstraints and defaults
textstringYes1–10,000 characters and no more than the model's maxTextLength
voiceIdstringYesReturned by /voices and must support the selected modelId
modelIdstringYesPublic model ID returned by capabilities
formatstringNomp3, wav, or ogg; default mp3; must be in the model's outputFormats
speednumberNo0.52; default 1
volumenumberNo-2020; default 0
pitchnumberNo-1212; send only when capabilities declares support
stabilitynumberNo0.51.5; send only when capabilities declares support
similaritynumberNo0.51.5; send only when capabilities declares support
languagestringNoLanguage hint, 1–64 characters
emotionstringNoGlobal emotion ID, 1–64 characters
instructionstringNoStyle, dialect, role, or emotion instruction, up to 1,600 characters
textNormalizationbooleanNoEnables structured-text pronunciation normalization

The request contract is strict. provider, provider API keys, provider-native model fields, and other unknown properties return 400. Valid generic controls unsupported by the selected model are ignored and listed in X-OpenAPI-Ignored-Parameters.

Successful response

HTTP/1.1 200 OK
Content-Type: audio/mpeg
X-Request-Id: trace-123-segment-1
X-OpenAPI-Quota-Remaining: 99994
X-OpenAPI-Credits-Used: 6
X-OpenAPI-Ignored-Parameters: pitch

<binary audio data>

Check the HTTP status first, then branch on Content-Type. A successful response contains audio bytes; an error contains JSON. X-OpenAPI-Ignored-Parameters is present only when a control was ignored.

Asynchronous jobs

Use Jobs for long text, batches, or workflows that require recoverable output. The create body is the same as synchronous v3.

curl "https://fishaudio.org/api/open/v3/speech/tts/jobs" \
  -H "Authorization: Bearer $FISHAUDIO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Request-Id: trace-job-001" \
  -H "Idempotency-Key: tts-job-001" \
  -d '{
    "text": "Text that needs durable generation and download.",
    "voiceId": "00a1b221-6137-4b73-ad62-b0cbce134167",
    "modelId": "fishaudio-s21pro-flash",
    "format": "mp3"
  }'

Creation returns 202 Accepted. Location points to the job status resource and Retry-After provides the suggested polling delay. Store task.taskId immediately:

{
  "task": {
    "taskId": "task_123",
    "status": "pending",
    "voiceId": "00a1b221-6137-4b73-ad62-b0cbce134167",
    "modelId": "fishaudio-s21pro-flash"
  },
  "requestId": "trace-job-001",
  "quotaRemaining": 99994,
  "creditsUsed": 6,
  "ignoredParameters": []
}
GET /speech/tts/jobs/{jobId}
GET /speech/tts/jobs?page=1&limit=20&status=success
GET /speech/tts/jobs/{jobId}/audio?download=1
GET /speech/tts/jobs/{jobId}/segments/{segmentIndex}
Authorization: Bearer FISHAUDIO_API_KEY

pending and processing are non-terminal. Only success, partial_fail, and fail are terminal. Keep Bearer authentication when downloading audio and allow the client to follow redirects.

Error format and handling

{
  "code": "ERR_REQUEST_ID_CONFLICT",
  "message": "Request id was reused with a different request",
  "requestId": "tts-order-123-segment-1"
}
StatusCommon causeClient action
400Invalid JSON, field, model, voice, or formatFix the request; do not retry unchanged
401Missing or invalid API keyReplace the server-side credential
402Insufficient API quotaStop the workflow and surface a billing state
404Voice or asynchronous job not foundCheck the ID and owning account
409Request active, completed, refunded, or reused with different inputBranch on code; do not blindly use a new ID
413HTTP request body exceeds the limitReduce the request; use Jobs for long text
429Rate limit exceededHonor Retry-After and use exponential backoff
500Platform or provider generation failedPreserve requestId; confirm settlement before retrying
503The selected model is currently unavailableRefresh capabilities and select an available model

Idempotency, billing, and retries

X-Request-Id is a trace id and is echoed in the response; the server generates it when omitted. Idempotency-Key is the business idempotency key and is at most 128 characters. Create one stable idempotency key per logical generation and reuse it only for an identical retry. Never send concurrent requests with the same key.

Existing clients may temporarily continue using X-Request-Id as a compatibility idempotency key. New clients must send the two headers separately. When Idempotency-Key is omitted, the request still runs, but the client cannot rely on an idempotent retry for that call.

  • Synchronous generation has at-most-once semantics. Reusing a completed idempotency key returns 409 ERR_REQUEST_ALREADY_COMPLETED; it does not replay audio and does not charge again.
  • Asynchronous creation replays the accepted task for an identical request, so it remains queryable and is not billed twice.
  • Reusing an idempotency key with different parameters returns 409 ERR_REQUEST_ID_CONFLICT.
  • A client timeout does not mean the server stopped. Use Jobs whenever output must be recovered after a lost response.
  • Validation, authentication, and quota failures are not charged. Generation or settlement failures enter the refund path.

Machine-readable contract and migration

The complete OpenAPI 3.1 document is public:

GET /api/open/v3/openapi.json

OpenAPI defines stable fields and response shapes; capabilities describes live model availability. Read both together. Existing v1 and v2 clients remain compatible, while new integrations should use v3. See the migration guide for field and path changes.