Async Text-to-Speech Jobs
Use the async speech flow whenever a generation might run long. A synchronous request that outlives the connection returns a 524 *after* the audio has already been generated and billed — you lose the result and pay for it anyway. Submitting a job instead runs the generation off the request, so the audio is always waiting for you when it finishes.
You send the same JSON body as POST /v1/audio/speech, get a job ID immediately, poll status, then download the finished audio.
Step 1 — Submit a job
1curl -X POST https://api.navy/v1/audio/speech/jobs \
2 -H "Authorization: Bearer sk-navy-YOUR_KEY" \
3 -H "Content-Type: application/json" \
4 -d '{
5 "model": "eleven_multilingual_v2",
6 "voice": "alice",
7 "input": "Welcome to the NavyAI platform."
8 }'Step 2 — Poll status
Poll with the same API key until status is done or failed. Polling does not consume tokens.
1curl https://api.navy/v1/audio/speech/jobs/job_a1b2c3d4e5f6/status \
2 -H "Authorization: Bearer sk-navy-YOUR_KEY"GET /v1/audio/speech/jobs/:id returns the same object if you prefer that shape.
Step 3 — Download the audio
When the job is done, download it. This endpoint redirects (302) to the stored file, so pass -L to curl. You can also use the url from the status response directly.
1curl -L https://api.navy/v1/audio/speech/jobs/job_a1b2c3d4e5f6/download \
2 -H "Authorization: Bearer sk-navy-YOUR_KEY" \
3 --output speech.mp3Polling in Python
1import time
2import requests
3
4BASE = "https://api.navy/v1"
5headers = {"Authorization": "Bearer sk-navy-YOUR_KEY"}
6
7job = requests.post(f"{BASE}/audio/speech/jobs", headers=headers, json={
8 "model": "eleven_multilingual_v2",
9 "voice": "alice",
10 "input": "Welcome to the NavyAI platform.",
11}).json()
12
13while True:
14 status = requests.get(f"{BASE}/audio/speech/jobs/{job['id']}/status", headers=headers).json()
15 if status["status"] in ("done", "failed"):
16 break
17 time.sleep(3)
18
19if status["status"] == "failed":
20 raise RuntimeError(status["error"]["message"])
21
22audio = requests.get(status["url"]).content
23open("speech.mp3", "wb").write(audio)Status values
Pending— Accepted, waiting for a generation slotprocessing— A provider is actively generating the audiodone— Audio is finished and can be downloadedfailed— Generation failed, with details inerror
Limits
- Poll every 3–5 seconds. Polling is free.
- Jobs and their audio are retained for one hour after submission.
expires_attells you exactly when the URL stops working, so download it before then if you need to keep it. - At most 4 jobs generate at once across the platform; the rest wait in
Pending. This is what keeps async submissions from overloading the provider pool. - A single API key may hold 10 jobs in flight at a time.
- The 4,096-character
inputlimit applies exactly as it does on the sync endpoint. pronunciation_dictionaryworks here too, and any warnings appear on the job object.
Errors
404 job_not_found— The job ID does not exist or has expired. Speech jobs are retained for one hour after creation.403 unauthorized_job_access— The job exists, but it was created with a different API key.409 job_not_ready— The job is stillPendingorprocessing; poll again before downloading.429 too_many_active_jobs— You already have 10 jobs in flight. Wait for one to finish before submitting another.