Async Jobs
Create, poll, and download asynchronous TTS jobs.
Async Jobs
Use async TTS jobs for long text, batch generation, and workflows where the client cannot keep one HTTP request open. The supported flow is create one job, store its id, poll that same job, and download the returned audio. Do not create another job just to recover a download.
Create Job
POST /api/open/v1/speech/tts/jobs
Authorization: Bearer KITTA_API_KEY
Content-Type: application/json{
"text": "Hello from Kitta Audio.",
"voiceId": "00a1b221-6137-4b73-ad62-b0cbce134167",
"modelId": "fishaudio-s21pro-flash",
"format": "mp3"
}The example uses the public system test voice 00a1b221-6137-4b73-ad62-b0cbce134167. Replace it with a Voice ID returned by GET /api/open/v1/voices when you are ready to use another voice.
curl https://kittaai.com/api/open/v1/speech/tts/jobs \
-H "Authorization: Bearer KITTA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"text":"Hello from Kitta Audio.","voiceId":"00a1b221-6137-4b73-ad62-b0cbce134167","modelId":"fishaudio-s21pro-flash","format":"mp3"}'The endpoint returns 202 Accepted. Store task.id before starting to poll.
{
"task": {
"id": "task_123",
"taskId": "task_123",
"status": "pending",
"audioUrl": null,
"delivery": {
"playbackUrl": null,
"downloadUrl": null,
"downloadReady": false
}
},
"record": {
"id": "task_123",
"status": "pending"
},
"requestId": "req_123",
"quotaRemaining": 99994,
"creditsUsed": 6
}Poll Job
GET /api/open/v1/speech/tts/jobs/{taskId}
Authorization: Bearer KITTA_API_KEYPoll every 3 to 5 seconds with backoff. The exact status values are:
| Status | Terminal | Meaning |
|---|---|---|
pending | No | Accepted and waiting for a worker |
processing | No | Audio generation is in progress |
success | Yes | Final audio is ready |
partial_fail | Yes | Some segments failed, but partial final audio is ready |
fail | Yes | Generation failed and no final audio is available |
When audio is ready, the task includes Open API delivery routes:
{
"task": {
"id": "task_123",
"status": "success",
"audioUrl": "/api/open/tts/jobs/task_123/audio",
"delivery": {
"playbackUrl": "/api/open/tts/jobs/task_123/audio",
"downloadUrl": "/api/open/tts/jobs/task_123/audio?download=1",
"downloadReady": true
},
"segments": []
},
"record": {
"id": "task_123",
"status": "success"
},
"requestId": "req_456"
}Recover a Lost Task ID
If the create request reached the server but the 202 Accepted response was lost, list recent jobs before submitting the text again:
GET /api/open/v1/speech/tts/jobs?limit=20&status=success&createdAfter=2026-07-20T00%3A00%3A00Z
Authorization: Bearer KITTA_API_KEYcurl "https://kittaai.com/api/open/v1/speech/tts/jobs?limit=20&createdAfter=2026-07-20T00%3A00%3A00Z" \
-H "Authorization: Bearer KITTA_API_KEY"The response is ordered newest first. Use createdAt, textFingerprint, voiceId, and engineModelId to match the local request. textFingerprint is sha256: followed by the SHA-256 digest of the exact submitted UTF-8 text. The list returns an 80-character preview instead of the full text.
{
"items": [
{
"taskId": "task_123",
"status": "success",
"characters": 7200,
"textPreview": "The opening words of the submitted text…",
"textFingerprint": "sha256:0123456789abcdef...",
"voiceId": "00a1b221-6137-4b73-ad62-b0cbce134167",
"engineModelId": "fishaudio-s21pro-flash",
"creditsUsed": 287,
"createdAt": "2026-07-20T09:07:36.000Z",
"audioUrl": "/api/open/tts/jobs/task_123/audio"
}
],
"pagination": {
"page": 1,
"limit": 20,
"hasMore": false,
"nextPage": null
},
"requestId": "req_list_123"
}Supported query parameters are page (default 1), limit (default 20, maximum 100), status, and the ISO 8601 createdAfter timestamp. Listing jobs does not consume API quota. After recovering taskId, continue with the normal poll and download flow.
Download Audio
Use task.delivery.downloadUrl exactly as returned. It is an authenticated Open API route, not an anonymous CDN URL. Send the same Bearer API key used to create and poll the job, and follow the signed redirect:
GET /api/open/tts/jobs/{taskId}/audio?download=1
Authorization: Bearer KITTA_API_KEYcurl -L "https://fishaudio.org/api/open/tts/jobs/task_123/audio?download=1" \
-H "Authorization: Bearer KITTA_API_KEY" \
--output speech.mp3Completed segment URLs in task.segments[].playbackUrl require the same Bearer header. A 401 means the API key is missing or invalid; a 404 means the task does not belong to the key's account or the requested audio is unavailable.
Billing And Retries
The API reserves creditsUsed once when it accepts the job. Polling, downloading, and downloading the same audio again do not create a task and do not charge again. A failed precharged job is refunded by the server through an idempotent refund path.
Store the task id before retrying network operations. Retry the create request only when you know the server did not accept it; otherwise recover by polling the existing task. For batches, limit concurrency and check Profile before creating more jobs.
Errors
| Status | Meaning | Action |
|---|---|---|
400 | Invalid text, voice, model, or output format | Fix the payload |
401 | Authentication failed | Stop and refresh the API key |
402 | Insufficient API quota | Pause job creation |
404 | Task or audio is unavailable for this key | Use the task id and API key from the same account |
429 | Too many create, poll, or download requests | Back off while preserving the existing task id |
500 | Server or provider processing error | Inspect the existing task's terminal state before retrying |