Your first Nigerian‑accented audio in 5 minutes
Five steps from an empty terminal to a saved .mp3 — create a key, pick a voice, and stream your first synthesis. Every request below uses a Bearer API key.
Create an API key
Sign in with Google, then open your Account page and hit Create key. Copy the full secret the moment it's shown — it looks like sk_live_<id>.<secret> and is revealed exactly once.
Treat the key like a password. Anyone holding it can spend your credits — if it leaks, deactivate it from Account and mint a new one.
Pick a voice
GET /api/v1/voices needs your Bearer key like everything else here — it lists every available voice; use the name field as your voice value.
curl -H "Authorization: Bearer sk_live_XXXX.XXXXXXXXXXXX" https://yarngpt.ai/api/v1/voicesThere's no fixed list to print here — the response above is the only source of truth, and it differs by environment and changes over time. Read name from it directly rather than hardcoding a voice id in production; one entry has default: true if you just want something reasonable to start with.
Make your first request
POST /api/v1/tts queues an asynchronous job — it returns a job_id immediately, not audio. Poll GET /api/v1/status/{job_id} until completed, then download audio_url. Send an Idempotency-Key so a retry returns the original job instead of billing you twice.
curl -X POST "https://yarngpt.ai/api/v1/tts" \ -H "Authorization: Bearer sk_live_XXXX.XXXXXXXXXXXX" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "text": "Ẹ kú àárọ̀. Welcome to YarnGPT.", "voice": "umar", "output_format": "mp3" }' # → {"job_id":"...","status":"queued","accepted_at":"..."} # poll until status is "completed", then download audio_url curl "https://yarngpt.ai/api/v1/status/<job_id>" -H "Authorization: Bearer sk_live_XXXX.XXXXXXXXXXXX" curl "<audio_url>" --output speech.mp3
| Field | Required | Notes |
|---|---|---|
| text | Yes | Non‑empty after trimming. Max 15,000 characters. |
| voice | No | A name from /api/v1/voices — exact, case-sensitive. Omit to use whichever entry has default: true. |
| output_format | No | mp3 (default), wav, pcm, opus, or flac. |
| target_language | No | Translate the text before synthesising it — one of 18 codes: am ar de en es fr ha ig lg pt rw sn sw tw wo xh yo zu. Omit to speak the text as written. Billed on the translated text, which can run longer or shorter than the original. |
An unrecognized field still 422s. Limited to 120 requests/day per key.
Read the response & handle errors
Success from POST /api/v1/tts is 202 JSON — a job to poll, not audio (see step 3). Every error, from every endpoint, has the same shape: {"error":{"code","message","service","trace_id","details"}}. Log trace_id — it's what lets anyone find the request server-side.
| Status | code | Means | Do |
|---|---|---|---|
| 400 | INVALID_INPUT | Missing Idempotency-Key or similar precondition | Check required headers. |
| 401 | — | Missing / invalid / revoked key | Check the Bearer header and that the key is active. |
| 402 | QUOTA_EXCEEDED | Out of credits | Top up, then retry. details carries credits_remaining/credits_required. |
| 404 | VOICE_NOT_FOUND | Unknown voice id | Check the name against /api/v1/voices — exact, case-sensitive. |
| 422 | VALIDATION_ERROR | Body failed schema validation | Read details.errors — often empty/too-long text or an unrecognized field. |
| 400 | TEXT_TOO_LONG | Text expands into more chunks than the server's cap, after numbers/currency are spelled out server-side | Read details.chunk_cap — it differs per environment, don't hardcode it. Split the text and resubmit; don't retry as-is. |
| 429 | RATE_LIMITED | Rate limit hit | Back off — respect retry-after. |
| 503 | SERVICE_UNAVAILABLE | A downstream dependency is down | Transient — details.dependency names it. Safe to retry. |
service in the error tells you who rejected the request. If it reads nginx-ingress, the request never reached a service at all — that's the gateway itself (an auth or edge rate-limit problem), not application logic.
Checks run voice first, then text — an unknown voice combined with over-length text still comes back 404 VOICE_NOT_FOUND, not 400.
Go realtime (optional)
Building a voice agent? POST /api/v1/streaming/conversation returns raw audio directly — no job_id, no polling. It's not saved and not idempotent by default (an optional Idempotency-Key makes a retry of the same turn free, up to 5 times within 450 seconds), and text is capped shorter — 1–5,000 characters instead of 15,000. There's no published per-endpoint rate limit for it beyond the developer-API edge limit (40 req/s, burst 80 — per calling client since release v0.7.0, not pooled across every caller). Use output_format: "pcm" (raw signed 16-bit little-endian mono) for a telephony pipeline with no decode step, or mp3/wav for a container.
The sample rate isn't fixed — don't hardcode one. For pcm, read it from the response's Content-Type header (audio/l16; rate=<N>; channels=1) and hand that number to your decoder; a client that assumes a fixed rate plays back at the wrong speed and pitch, with no error to catch. This route can also return 400 TEXT_TOO_LONG (see step 4) or, on a total provider outage, a 200 with a zero-byte body — check the byte count before treating a response as audio.
curl -N -X POST "https://yarngpt.ai/api/v1/streaming/conversation" \ -H "Authorization: Bearer sk_live_XXXX.XXXXXXXXXXXX" \ -H "Content-Type: application/json" \ -d '{ "text": "How may I help you today?", "voice": "umar", "output_format": "pcm" }' \ --output reply.pcm
You're shipping
That's the whole loop. Dig into the full reference for every field and endpoint, or reach out when you're ready for production limits.