Documentation

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.

Bash
1curl -L https://api.navy/v1/audio/speech/jobs/job_a1b2c3d4e5f6/download \
2  -H "Authorization: Bearer sk-navy-YOUR_KEY" \
3  --output speech.mp3

Polling in Python

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 slot
  • processing — A provider is actively generating the audio
  • done — Audio is finished and can be downloaded
  • failed — Generation failed, with details in error

Limits

  • Poll every 3–5 seconds. Polling is free.
  • Jobs and their audio are retained for one hour after submission. expires_at tells 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 input limit applies exactly as it does on the sync endpoint.
  • pronunciation_dictionary works 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 still Pending or processing; poll again before downloading.
  • 429 too_many_active_jobs — You already have 10 jobs in flight. Wait for one to finish before submitting another.