Dev.to AI πŸ€– Ai πŸ‘ 0 πŸ“– 4 min read

How to Add Async Text-to-Speech to an App with the Fish TTS API

Long text-to-speech jobs are easy to prototype, but they can become awkward in production when your app has to hold a request open while audio is being generated. What you can do The Fish TTS endpoint on Ace

Long text-to-speech jobs are easy to prototype, but they can become awkward in production when your app has to hold a request open while audio is being generated.

What you can do

The Fish TTS endpoint on Ace Data Cloud gives you a simple way to turn text into audio while keeping the request shape compatible with the Fish Audio text-to-speech API. In practice, that means you can send a familiar JSON body, authenticate with an Ace Data Cloud bearer token, and receive an audio_url that your app can play, download, or pass to the next step in a workflow.

The base request is intentionally small:

  • Base URL / endpoint: POST https://api.acedata.cloud/fish/tts
  • Authentication: Authorization: Bearer {token}
  • Content type: application/json
  • Model header: model: s2-pro or model: s1 (s2-pro is the default)
  • Required body field: text
  • Optional voice field: reference_id
  • Optional output fields: format, sample_rate, mp3_bitrate
  • Async extension: callback_url
  • Typical result field: audio_url

This makes the endpoint useful for product narration, voice notes, generated learning content, internal tools, or any workflow where the output should become a real audio file instead of just a browser-side demo.

How it works

The synchronous version is the smallest useful call. Send text, include your token, choose the model in the request header, and read the returned audio_url:

curl -X POST 'https://api.acedata.cloud/fish/tts' \
  -H 'accept: application/json' \
  -H 'authorization: Bearer {token}' \
  -H 'content-type: application/json' \
  -H 'model: s2-pro' \
  -d '{
    "text": "δ»Šε€©ε€©ζ°”ηœŸε₯½οΌŒζˆ‘δ»¬δΈ€θ΅·ε‡ΊεŽ»ζ•£ζ•£ζ­₯吧。"
  }'

A successful response returns an audio file URL:

{
  "audio_url": "https://platform.r2.fish.audio/task/8a72ff9840234006a9f74cb2fa04f978.mp3"
}

If your app is already written against Fish Audio's official request body, the important migration detail is that the body structure stays the same. The main change is authentication: use Authorization: Bearer {token} with the token from Ace Data Cloud. The endpoint also accepts Fish request fields such as text, reference_id, references, prosody, format, sample_rate, mp3_bitrate, chunk_length, temperature, and top_p.

Add a cloned voice or explicit audio format

For many real apps, plain TTS is only the first step. You may want a known voice, a predictable file type, or a fixed sample rate for a downstream media pipeline. The endpoint supports reference_id, format, and sample_rate in the JSON body:

curl -X POST 'https://api.acedata.cloud/fish/tts' \
  -H 'accept: application/json' \
  -H 'authorization: Bearer {token}' \
  -H 'content-type: application/json' \
  -H 'model: s2-pro' \
  -d '{
    "text": "δ»Šε€©ε€©ζ°”ηœŸε₯½οΌŒζˆ‘δ»¬δΈ€θ΅·ε‡ΊεŽ»ζ•£ζ•£ζ­₯吧。",
    "reference_id": "d7900c21663f485ab63ebdb7e5905036",
    "format": "mp3",
    "sample_rate": 44100
  }'

That is the shape I would use when integrating TTS into a service that stores generated audio assets. Your backend can submit the text, save the resulting audio_url, and associate it with the original content record.

Use callbacks for longer jobs

For short snippets, a direct request is fine. For longer text, keeping an HTTP connection open can make the rest of your system more fragile: workers time out, clients retry, and duplicate jobs become harder to reason about.

Ace Data Cloud adds an async callback extension through callback_url. When you include this field, the API immediately returns a task_id and started_at timestamp. Later, when generation completes, the final payload is posted to your callback URL with the same task_id and the generated audio_url.

curl -X POST 'https://api.acedata.cloud/fish/tts' \
  -H 'accept: application/json' \
  -H 'authorization: Bearer {token}' \
  -H 'content-type: application/json' \
  -H 'model: s2-pro' \
  -d '{
    "text": "δ»Šε€©ε€©ζ°”ηœŸε₯½οΌŒζˆ‘δ»¬δΈ€θ΅·ε‡ΊεŽ»ζ•£ζ•£ζ­₯吧。",
    "callback_url": "https://webhook.site/4815f79f-a40f-4078-ac85-1cc126b6bb34"
  }'

Immediate response:

{
  "task_id": "2725a2d3-f87e-4905-9c53-9988d5a7b2f5",
  "started_at": "2025-05-09T12:34:56.789Z"
}

Callback payload:

{
  "task_id": "2725a2d3-f87e-4905-9c53-9988d5a7b2f5",
  "audio_url": "https://platform.r2.fish.audio/task/b627c2f7d38a4083a837570ba6d0962f.mp3"
}

In a production app, store the task_id as soon as the first response arrives. When your webhook receives the callback, look up the pending job, save the audio_url, and mark the job complete. If you need active status checks instead, the documentation notes that the task_id can also be used with the Fish Tasks API.

Handle errors as part of the workflow

Do not treat TTS as a fire-and-forget side effect. The endpoint preserves upstream HTTP status behavior and returns a unified platform error format. The common cases to handle are 400 token_mismatched, 400 api_not_implemented, 401 invalid_token, 429 too_many_requests, and 500 api_error.

A typical error response looks like this:

{
  "success": false,
  "error": {
    "code": "api_error",
    "message": "fetch failed"
  },
  "trace_id": "2cf86e86-22a4-46e1-ac2f-032c0f2a4e89"
}

Keep the trace_id in logs. It is the kind of small detail that saves time when debugging a failed media generation job later.

A practical integration pattern

For a builder-friendly implementation, I would start with three states in my own database: queued, processing, and ready. The app creates a row when a user submits text, calls POST /fish/tts with callback_url, stores the returned task_id, and moves the row to processing. The webhook validates the payload, saves audio_url, and marks the row as ready.

This keeps your UI responsive and avoids making users wait on a long request. It also gives you a clean retry boundary: retry the job submission if the initial request fails, and retry webhook processing if your own server has a temporary issue.

If you want the exact compatibility notes and field list, read the full Fish TTS integration guide on Ace Data Cloud: https://platform.acedata.cloud/documents/fish-tts-integration

πŸ“° Read the original article on Dev.to AI

Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β€” full credit and traffic to the original publisher.