NueForm

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

POST/api/v1/voice

Makes 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)

textstring

speak: the words to read

voicestring

An nv_… voice from List voices. Required for speak; in a script, the voice for lines without a speaker tag

linesarray

script: structured lines, each {"speaker", "voice", "text"} or a pause {"pause_ms": 800} (up to 10,000)

scriptstring

script: the multi-voice format, one line per speaker: [Maya] Did you hear that?

castarray

script: a voice for each tagged speaker, [{"speaker": "Maya", "voice": "nv_…"}]

auto_convertboolean

Convert a pasted screenplay or chat log (MAYA: Hi) to the multi-voice format first. Free, within a daily limit

auto_castboolean

Pick a voice for every speaker that has none. Free, within a daily limit. Send language for a script that isn't in English

languagestring

A language name or code ("es", "Spanish"). Default: the voice's own language

qualitystring

"standard" (default), "studio" or "phone". Same price

speednumber

0.85 to 1.15 (default 1)

formatstring

"mp3" (default), "wav", "ogg_opus" or "mulaw_8k"

max_creditsinteger

Refuse the request (409, nothing charged) if it would cost more

reuseboolean

Default true: an identical request made in the last 30 days is answered with that audio for 0 credits

saveboolean

true keeps the audio until you delete it. Default: deleted after 30 days

retake_ofstring

Make a generation again (see Retakes)

titlestring

Its title in your Library

Response Fields

idstring

The generation (gen_…)

statusstring

"ready", "rendering" (202: still being made), "partial" (some lines failed and cost nothing) or "failed"

audioobject or null

url (send your API key), format, sample_rate, duration_seconds, retained_until

creditsobject

estimated, charged (what this request charged) and remaining

linesarray

script only: each line's speaker, voice, text, status, start_ms, end_ms and credits

retakes_remaininginteger

Free retakes left on this clip

reusedboolean

The audio of an identical earlier request, for 0 credits

Code Examples

bash
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

text
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

json
{
  "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

json
{
  "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

json
{
  "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

POST/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

charactersinteger

Spoken characters: what is billed

creditsinteger

Exactly what the request would charge

seconds_estimatednumber

About how long the audio will be

balanceinteger

Your balance now

enoughboolean

Your balance (and the key's monthly limit, if it has one) covers it

Example

bash
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."}'
json
{
  "data": {
    "object": "voice.estimate",
    "characters": 75,
    "credits": 30,
    "seconds_estimated": 4.8,
    "balance": 48760,
    "enough": true
  },
  "meta": { "rate_card": "1" }
}

Stream Speech

POST/api/v1/voice/stream

Sends 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

voicestring

An nv_… voice from List voices

textstring

The 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)

languagestring

A language name or code. Default: the voice's own language

qualitystring

"standard" (default; "phone" for mulaw_8k), "studio" or "phone". Same price

max_creditsinteger

Refuse the request (409, nothing charged) if it would cost more

saveboolean

true keeps the saved copy until you delete it. Default: deleted after 30 days

titlestring

Its title in your Library

speed, scripts, retake_of and reuse aren't available on a stream (400, reason: "not_on_stream").

Code Examples

bash
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

text
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_of names 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 CONFLICT with reason: "idempotency_conflict";
  • while the first request is still running, 409 CONFLICT with reason: "request_in_progress" and Retry-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:

HeaderMeaning
NueForm-Generation-IdThe generation (gen_…)
NueForm-Credits-EstimatedWhat the request was quoted
NueForm-Credits-ChargedWhat 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-RemainingYour balance afterwards
NueForm-Rate-CardThe rate card version it was priced under
NueForm-Retakes-RemainingFree retakes left on this clip
NueForm-Audio-DurationThe audio's length in seconds
Idempotent-ReplayedWith an Idempotency-Key: whether this answers an earlier request
NueForm-Audio-Format, NueForm-Audio-Encoding, NueForm-Audio-Sample-Rate, NueForm-Audio-ChannelsA stream's audio: its format, encoding, sample rate and channels
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-ResetThis key's requests this minute (see Rate Limits)

List Voices

GET/api/v1/voice/voices

NueForm'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"

languagestring

A language name or code

genderstring

For example "female"

qstring

Search names, styles and labels

featuredboolean

true: featured stock voices only

Response

json
{
  "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

DELETE/api/v1/voice/voices/:id

Deletes 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

json
{
  "data": {
    "id": "nv_9LmQ2xT4aPe7vK3sW",
    "object": "voice",
    "deleted": true,
    "switched": { "forms": 2, "numbers": 1 },
    "forms_to_republish": 1
  }
}

List Generations

GET/api/v1/voice/generations

Your generations, newest first. Page on with meta.next_cursor.

Query Parameters

limitinteger

1 to 100 (default 20)

cursorstring

next_cursor from the previous page

sourcestring

Only those made through "api", "studio", "mcp" or "app"

Response

json
{
  "data": [
    { "id": "gen_2cH4kXyQ9vLmNp3Rs", "object": "voice.generation", "status": "ready", "credits": { "charged": 30 } }
  ],
  "meta": { "next_cursor": null, "has_more": false }
}

Get a Generation

GET/api/v1/voice/generations/:id

One generation, with its lines for a script. Here credits.charged is what it cost in all, after any automatic credit-back.

Download the Audio

GET/api/v1/voice/generations/:id/audio

The 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

DELETE/api/v1/voice/generations/:id

Deletes 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

GET/api/v1/credits

The 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

balanceinteger

Credits you can spend now (10,000 credits = $1)

approxstring

The balance in dollars

keyobject

This key's monthly_credit_limit (null = none), spent_this_month, paused and resets_at (the 1st, UTC)

voice_apiobject

credits_per_character (0.4), how it rounds, and that estimates are free

rate_cardobject

Every price, by action

Response

json
{
  "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

WhatCreditsIn dollars
Speech through the Voice API0.4 per spoken character, every languageabout $0.04 per 1,000 characters
An estimate0free
A reused clip (reuse)0free
A retake0 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:

StatuscodeWhen
402INSUFFICIENT_CREDITSYour 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)
403FORBIDDENreason: key_limit_reached, account_gate, scope_not_allowed, cooldown, voice_studio_suspended, wallet_frozen, member_cap_reached, team_payer_unavailable
409CONFLICTreason: quote_exceeds_max_credits, idempotency_conflict, request_in_progress, unit_rendering
413TEXT_TOO_LONGMore than 5,000 spoken characters (max_characters)
422CONTENT_BLOCKED, VOICE_UNAVAILABLE, LANGUAGE_NOT_SUPPORTED_BY_VOICE, INVALID_SCRIPTThe text or the voices can't be spoken as sent
429RATE_LIMITED, CONCURRENCY_LIMITToo many requests (see Rate Limits)
503CAPACITY, WARMING_UP, MODERATION_UNAVAILABLE, AI_UNAVAILABLETry again after Retry-After seconds
Last updated: October 20, 2026