Voice API
Make speech from your own code with the voices of Voice Studio: one voice or a whole script, 0.4 credits per spoken character in every language, with a free estimate first.
Make speech from your own code with NueSpeak Apex, the same voices as Voice Studio. Requests spend credits from your NueForm balance: 0.4 credits per spoken character (about $0.04 per 1,000 characters) in every language, rounded up to a whole credit once per request. Estimates are free.
All request and response bodies use snake_case field names. Voices are named by nv_… ids and generations by gen_… ids.
Voice keys work on every plan once your account has made a first credit purchase (or is on a paid plan) and verified its email. See Voice API keys.
Make Speech
/api/v1/voiceMakes the audio and answers when it is ready (synchronous, at most 5,000 spoken characters per request). Send Accept: audio/mpeg (or audio/wav, audio/ogg, audio/basic, audio/*) to get the audio itself; otherwise the answer is JSON with an authenticated audio.url.
Credits are taken when the request starts. Lines that fail are credited back automatically, and nothing that ends in an error is charged. A request that was charged never ends in an error: if its audio isn't ready in time, it answers 202 with a Location header. Fetch the audio from there instead of sending the request again.
Request Body
modestring"speak" (one voice reads text), "script" (several voices) or "estimate" (see Estimate the cost)
textstringspeak: the words to read
voicestringAn nv_… voice from List voices. Required for speak; in a script, the voice for lines without a speaker tag
linesarrayscript: structured lines, each {"speaker", "voice", "text"} or a pause {"pause_ms": 800} (up to 10,000)
scriptstringscript: the multi-voice format, one line per speaker: [Maya] Did you hear that?
castarrayscript: a voice for each tagged speaker, [{"speaker": "Maya", "voice": "nv_…"}]
auto_convertbooleanConvert a pasted screenplay or chat log (MAYA: Hi) to the multi-voice format first. Free, within a daily limit
auto_castbooleanPick a voice for every speaker that has none. Free, within a daily limit. Send language for a script that isn't in English
languagestringA language name or code ("es", "Spanish"). Default: the voice's own language
qualitystring"standard" (default), "studio" or "phone". Same price
speednumber0.85 to 1.15 (default 1)
formatstring"mp3" (default), "wav", "ogg_opus" or "mulaw_8k"
max_creditsintegerRefuse the request (409, nothing charged) if it would cost more
reusebooleanDefault true: an identical request made in the last 30 days is answered with that audio for 0 credits
savebooleantrue keeps the audio until you delete it. Default: deleted after 30 days
retake_ofstringMake a generation again (see Retakes)
titlestringIts title in your Library
Response Fields
idstringThe generation (gen_…)
statusstring"ready", "rendering" (202: still being made), "partial" (some lines failed and cost nothing) or "failed"
audioobject or nullurl (send your API key), format, sample_rate, duration_seconds, retained_until
creditsobjectestimated, charged (what this request charged) and remaining
linesarrayscript only: each line's speaker, voice, text, status, start_ms, end_ms and credits
retakes_remainingintegerFree retakes left on this clip
reusedbooleanThe audio of an identical earlier request, for 0 credits
Code Examples
curl -X POST https://nueform.io/api/v1/voice \
-H "Authorization: Bearer $NUEFORM_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: audio/mpeg" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"mode": "speak",
"voice": "nv_7QmS2xK9aLpe3vT8a",
"text": "Fresh bread every morning at seven. Croissants too. See you on Main Street."
}' \
--output speech.mp3 --dump-header -
This text is 75 spoken characters, so it costs 30 credits (75 × 0.4).
Response Headers
HTTP/1.1 200 OK
Content-Type: audio/mpeg
NueForm-Generation-Id: gen_2cH4kXyQ9vLmNp3Rs
NueForm-Credits-Estimated: 30
NueForm-Credits-Charged: 30
NueForm-Credits-Remaining: 48730
NueForm-Rate-Card: 1
NueForm-Retakes-Remaining: 2
NueForm-Audio-Duration: 4.8
Idempotent-Replayed: false
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
X-RateLimit-Reset: 2026-11-04T18:13:00.000Z
JSON Response
{
"data": {
"id": "gen_2cH4kXyQ9vLmNp3Rs",
"object": "voice.generation",
"status": "ready",
"mode": "speak",
"source": "api",
"voice": { "id": "nv_7QmS2xK9aLpe3vT8a", "name": "Amara" },
"language": "English",
"characters": 75,
"audio": {
"url": "https://nueform.io/api/v1/voice/generations/gen_2cH4kXyQ9vLmNp3Rs/audio?format=mp3",
"format": "mp3",
"sample_rate": 24000,
"duration_seconds": 4.8,
"retained_until": "2026-12-04T18:12:00.000Z"
},
"credits": { "estimated": 30, "charged": 30, "remaining": 48730 },
"retakes_remaining": 2,
"reused": false,
"created_at": "2026-11-04T18:12:00.000Z"
},
"meta": { "rate_card": "1" }
}
Scripts
A script speaks several voices in one audio file. Send structured lines (no parsing at all) or a script in the multi-voice format with its cast. Pauses ({"pause_ms": 800}, or (pause 2s) in a script) cost nothing; only spoken characters are billed.
The answer adds lines: where each line starts and ends in the audio, its status and what its characters come to (credits, a decimal). The request is charged once, rounded up. A line that fails shows "credits": 0 and costs nothing.
With auto_convert a pasted screenplay or chat log is converted first; with auto_cast every speaker without a voice gets one.
Request Example
{
"mode": "script",
"lines": [
{ "speaker": "Maya", "voice": "nv_7QmS2xK9aLpe3vT8a", "text": "Did you hear that?" },
{ "speaker": "Leo", "voice": "nv_P3vT8aLeQ2mwX4kZ9", "text": "Hear what?" },
{ "pause_ms": 800 },
{ "speaker": "Maya", "voice": "nv_7QmS2xK9aLpe3vT8a", "text": "Exactly. Nothing." }
],
"format": "wav"
}
Or the multi-voice format
{
"mode": "script",
"script": "[Maya] Did you hear that?\n[Leo] Hear what?\n(pause 1s)\n[Maya] Exactly. Nothing.",
"auto_cast": true
}
Estimate the Cost
/api/v1/voice"mode": "estimate" takes the same body as speak or script and answers with the exact cost, without making anything. It is free and doesn't count against your generation limits (up to 300 a minute per key).
Response Fields
charactersintegerSpoken characters: what is billed
creditsintegerExactly what the request would charge
seconds_estimatednumberAbout how long the audio will be
balanceintegerYour balance now
enoughbooleanYour balance (and the key's monthly limit, if it has one) covers it
Example
curl -X POST https://nueform.io/api/v1/voice \
-H "Authorization: Bearer $NUEFORM_API_KEY" \
-d '{"mode": "estimate", "voice": "nv_7QmS2xK9aLpe3vT8a", "text": "Fresh bread every morning at seven. Croissants too. See you on Main Street."}'
{
"data": {
"object": "voice.estimate",
"characters": 75,
"credits": 30,
"seconds_estimated": 4.8,
"balance": 48760,
"enough": true
},
"meta": { "rate_card": "1" }
}
Stream Speech
/api/v1/voice/streamSends the audio while it is being made, so your app starts playing in about a second instead of waiting for the whole clip: an agent, a voice bot, a reader. One voice reads text. On a warm voice engine the first audio typically arrives in 1–2 seconds, however long the text.
The price is Make speech's: 0.4 credits per spoken character, rounded up once per request, taken when the stream starts. One stream takes up to about 200 seconds of speech: about 3,100 characters of English, fewer in languages spoken more slowly (a longer text is 413 TEXT_TOO_LONG with max_characters). For longer text, send several streams or use POST /api/v1/voice.
Before the first byte, every failure is a normal error and costs nothing: if the voice engine fails before any audio, the charge is credited back (500, or 503 WARMING_UP with Retry-After while the engine starts). After the first byte the status can't change any more, so a stream that fails partway stops before its end: your HTTP client reports an incomplete body. Use NueForm-Generation-Id with Get a generation to see the saved copy and what it cost. If you hang up, the stream is still finished and saved, and it isn't credited back.
Every stream is also saved as a generation: it is in your Library, and Download the audio serves it, normalized like every clip.
Stream or /voice? Stream when someone is waiting to hear it (agents, voice bots, reading aloud). Use POST /api/v1/voice for files, scripts with several voices, speed, retakes and estimates. MCP tools don't stream: they make audio the /voice way.
Request Body
voicestringAn nv_… voice from List voices
textstringThe words to read
formatstring"mp3" (default, for browsers), "pcm_24k" (raw 16-bit little-endian mono PCM at 24,000 Hz, for agents) or "mulaw_8k" (8 kHz G.711 μ-law, for telephony; "ulaw_8000" works too)
languagestringA language name or code. Default: the voice's own language
qualitystring"standard" (default; "phone" for mulaw_8k), "studio" or "phone". Same price
max_creditsintegerRefuse the request (409, nothing charged) if it would cost more
savebooleantrue keeps the saved copy until you delete it. Default: deleted after 30 days
titlestringIts title in your Library
speed, scripts, retake_of and reuse aren't available on a stream (400, reason: "not_on_stream").
Code Examples
curl -N -X POST https://nueform.io/api/v1/voice/stream \
-H "Authorization: Bearer $NUEFORM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"voice": "nv_7QmS2xK9aLpe3vT8a", "text": "Fresh bread every morning at seven.", "format": "mp3"}' \
--output stream.mp3
This text is 35 spoken characters, so it costs 14 credits (35 × 0.4).
Response Headers
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Transfer-Encoding: chunked
NueForm-Generation-Id: gen_2cH4kXyQ9vLmNp3Rs
NueForm-Credits-Estimated: 14
NueForm-Credits-Charged: 14
NueForm-Credits-Remaining: 48716
NueForm-Rate-Card: 1
NueForm-Audio-Format: pcm_24k
NueForm-Audio-Encoding: pcm_s16le
NueForm-Audio-Sample-Rate: 24000
NueForm-Audio-Channels: 1
X-Accel-Buffering: no
Retakes
Send {"mode": "speak", "retake_of": "gen_…"} to make a generation again with the same words, voices, language, quality and speed. Only mode, format, max_credits and save go with retake_of.
- Free twice within 24 hours of the generation, as long as the key's free retakes this month stay at or under a quarter of the characters it was billed this month. A free retake keeps the same
gen_…id. - Otherwise it is a new generation, charged like any request (0.4 credits per character), and
retake_ofnames the one it retook.
Send "mode": "estimate" with retake_of to see first: the answer says "free": true or what it would cost.
Idempotency
Send an Idempotency-Key header (1–255 characters, for example a UUID) with speak and script requests, and a retry can never charge twice:
- the same key with the same body answers again with what the first request made, without charging (
Idempotent-Replayed: true,NueForm-Credits-Charged: 0); - the same key with a different body is
409 CONFLICTwithreason: "idempotency_conflict"; - while the first request is still running,
409 CONFLICTwithreason: "request_in_progress"andRetry-After.
Keys are remembered for 24 hours, per API key.
On a stream, the same key with the same body answers with JSON: the generation the first request made (it isn't streamed again).
Response Headers
Every answer says what it cost:
| Header | Meaning |
|---|---|
NueForm-Generation-Id | The generation (gen_…) |
NueForm-Credits-Estimated | What the request was quoted |
NueForm-Credits-Charged | What this request charged, after any automatic credit-back (0 for an estimate, a reused clip, a free retake, a replay and every error) |
NueForm-Credits-Remaining | Your balance afterwards |
NueForm-Rate-Card | The rate card version it was priced under |
NueForm-Retakes-Remaining | Free retakes left on this clip |
NueForm-Audio-Duration | The audio's length in seconds |
Idempotent-Replayed | With an Idempotency-Key: whether this answers an earlier request |
NueForm-Audio-Format, NueForm-Audio-Encoding, NueForm-Audio-Sample-Rate, NueForm-Audio-Channels | A stream's audio: its format, encoding, sample rate and channels |
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset | This key's requests this minute (see Rate Limits) |
List Voices
/api/v1/voice/voicesNueForm's stock voices, then your own and your teams' voices, each with its nv_… id, its language, a free preview and its pace. Free.
Query Parameters
sourcestring"all" (default), "stock" or "mine"
languagestringA language name or code
genderstringFor example "female"
qstringSearch names, styles and labels
featuredbooleantrue: featured stock voices only
Response
{
"data": [
{
"id": "nv_7QmS2xK9aLpe3vT8a",
"object": "voice",
"name": "Amara",
"source": "stock",
"type": "clone",
"language": "English",
"language_label": "English (US)",
"gender": "female",
"age": null,
"style": "Warm",
"featured": true,
"preview_url": "https://nueform.io/voice-previews/amara.mp3",
"team_id": null,
"can_delete": false,
"chars_per_second": 15.5,
"created_at": null
}
],
"meta": { "count": 1 }
}
Delete a Voice
/api/v1/voice/voices/:idDeletes one of your voices for real: its sample, prompts and preview, then the voice. Credits aren't refunded. While forms or phone numbers still use the voice, the answer is 409 CONFLICT with reason: "voice_in_use" and how many; send ?replacement=nv_… to switch them to another voice first.
Response
{
"data": {
"id": "nv_9LmQ2xT4aPe7vK3sW",
"object": "voice",
"deleted": true,
"switched": { "forms": 2, "numbers": 1 },
"forms_to_republish": 1
}
}
List Generations
/api/v1/voice/generationsYour generations, newest first. Page on with meta.next_cursor.
Query Parameters
limitinteger1 to 100 (default 20)
cursorstringnext_cursor from the previous page
sourcestringOnly those made through "api", "studio", "mcp" or "app"
Response
{
"data": [
{ "id": "gen_2cH4kXyQ9vLmNp3Rs", "object": "voice.generation", "status": "ready", "credits": { "charged": 30 } }
],
"meta": { "next_cursor": null, "has_more": false }
}
Get a Generation
/api/v1/voice/generations/:idOne generation, with its lines for a script. Here credits.charged is what it cost in all, after any automatic credit-back.
Download the Audio
/api/v1/voice/generations/:id/audioThe audio itself, streamed with your API key (byte ranges supported). ?format= is mp3 (default), wav, ogg_opus or mulaw_8k. Ogg/Opus and 8 kHz μ-law are made the first time you ask: a 202 means "ask again after Retry-After seconds". Free.
Delete a Generation
/api/v1/voice/generations/:idDeletes the generation and its audio for good. Credits aren't refunded. A generation still being made answers 409 CONFLICT with reason: "still_rendering".
Check Your Credits
/api/v1/creditsThe balance your key spends from, the key's month and the rate card. Read-only, with a Voice key or a full-access key.
Response Fields
balanceintegerCredits you can spend now (10,000 credits = $1)
approxstringThe balance in dollars
keyobjectThis key's monthly_credit_limit (null = none), spent_this_month, paused and resets_at (the 1st, UTC)
voice_apiobjectcredits_per_character (0.4), how it rounds, and that estimates are free
rate_cardobjectEvery price, by action
Response
{
"data": {
"object": "credits",
"balance": 48730,
"approx": "≈ $4.87",
"currency": "usd",
"frozen": false,
"key": {
"id_prefix": "nf_a1b2c3d4",
"monthly_credit_limit": 100000,
"spent_this_month": 1240,
"paused": false,
"resets_at": "2026-12-01T00:00:00.000Z"
},
"voice_api": { "credits_per_character": 0.4, "rounding": "up_per_request", "estimate_free": true }
},
"meta": { "rate_card": "1" }
}
Pricing
| What | Credits | In dollars |
|---|---|---|
| Speech through the Voice API | 0.4 per spoken character, every language | about $0.04 per 1,000 characters |
| An estimate | 0 | free |
A reused clip (reuse) | 0 | free |
| A retake | 0 twice within 24 hours (fair use), then 0.4 per character |
Credits come from packs (from $5 for 50,000 credits) and never expire. A key can have a monthly credit limit: once it has spent that much, its speech requests and deletes get 403 FORBIDDEN with reason: "key_limit_reached" until the 1st (it can still list and download what it made), and your other keys keep working. Requests are paid from the credits the key's owner picked in Pay for Voice Studio from in the Wallet: their own, unless they chose a team. A team that can't pay is never swapped for the owner's own credits: the request gets 403 FORBIDDEN with reason: "team_payer_unavailable", or reason: "member_cap_reached" once they reach their monthly cap as a member, and nothing is charged.
Errors
Every error uses the standard error format, and nothing that ends in an error is charged. The most common:
| Status | code | When |
|---|---|---|
| 402 | INSUFFICIENT_CREDITS | Your balance doesn't cover the request (credits_required, credits_available, top_up_url; payer: "team" when the team the key's owner pays from is short: its top_up_url then opens the Wallet, where Pay for Voice Studio from can be switched to their own credits) |
| 403 | FORBIDDEN | reason: key_limit_reached, account_gate, scope_not_allowed, cooldown, voice_studio_suspended, wallet_frozen, member_cap_reached, team_payer_unavailable |
| 409 | CONFLICT | reason: quote_exceeds_max_credits, idempotency_conflict, request_in_progress, unit_rendering |
| 413 | TEXT_TOO_LONG | More than 5,000 spoken characters (max_characters) |
| 422 | CONTENT_BLOCKED, VOICE_UNAVAILABLE, LANGUAGE_NOT_SUPPORTED_BY_VOICE, INVALID_SCRIPT | The text or the voices can't be spoken as sent |
| 429 | RATE_LIMITED, CONCURRENCY_LIMIT | Too many requests (see Rate Limits) |
| 503 | CAPACITY, WARMING_UP, MODERATION_UNAVAILABLE, AI_UNAVAILABLE | Try again after Retry-After seconds |