Klone V2 Flash
Klone V2 Flash is our faster voice-cloning model. It is a drop-in option on the same one-shot endpoint as Klone V2 Pro: add model=klone_v2_flash to the request and everything else stays the same. Flash can also stream audio over HTTPS or a WebSocket while it is being generated.
Quick start
- 1
Get an API key
Create a key in the Console and replace
YOUR_API_KEYin the examples below. - 2
Install the SDK
pip install fonadalabs - 3
Make your first request
Generate speech in a Voice Arena voice with the Flash model. Replace YOUR_SHARE_ID with the voice's share ID.
from fonadalabs.tts.client import TTSClient client = TTSClient(api_key="YOUR_API_KEY") audio_bytes = client.generate_audio( text="Hello from flash cloning.", language="Hindi", model="klone_v2_flash", share_id="YOUR_SHARE_ID", output_file="flash.wav", )
Overview
Key characteristics
- Synchronous: the audio comes back inline in the HTTP response. No job ID and no polling.
- 12 languages: Bengali, English, Gujarati, Hindi, Kannada, Malayalam, Marathi, Odia, Punjabi, Tamil, Telugu, Urdu.
- Up to 5,000 characters per request. The server splits and merges long text.
- Three delivery modes: a whole clip (
/chunks), a streamed response (/stream), or a WebSocket with many utterances per connection (/ws). /chunksdoes not usespeed,duration,instructornum_step; see Voice settings.
Request
API endpoint
Base URL: https://api.fonada.ai
Endpoint: /v1/voice-clone/chunks
Method: POST
Content-Type: multipart/form-data (always, even with no file upload)
Authorization: Bearer YOUR_API_KEY
curl -X POST "https://api.fonada.ai/v1/voice-clone/chunks" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "model=klone_v2_flash" \
-F "share_id=YOUR_SHARE_ID" \
-F "text=यह एक छोटा सा फ़्लैश वॉइस क्लोन टेस्ट है।" \
-F "language=hi" \
-F "output_audio_codec=mp3" \
-o voice.mp3const form = new FormData()
form.append('model', 'klone_v2_flash')
form.append('share_id', 'YOUR_SHARE_ID')
form.append('text', 'यह एक छोटा सा फ़्लैश वॉइस क्लोन टेस्ट है।')
form.append('language', 'hi')
form.append('output_audio_codec', 'mp3') // optional, default wav
// Do not set Content-Type yourself - the browser adds the multipart boundary.
const res = await fetch('https://api.fonada.ai/v1/voice-clone/chunks', {
method: 'POST',
headers: { Authorization: 'Bearer YOUR_API_KEY' },
body: form,
})
if (!res.ok) {
const err = await res.json() // { detail: { error, message, details? } }
if (err?.detail?.error === 'rate_limit_exceeded') {
const wait = err.detail.details?.retry_after_seconds ?? 5
// wait `wait` seconds, then retry
}
throw new Error(err?.detail?.message || 'voice clone failed')
}
const url = URL.createObjectURL(await res.blob())
audioElement.src = urlThe body of a successful response is the raw audio. Read it as a Blob or ArrayBuffer, not JSON. Response headers: Content-Type, Content-Disposition, X-Output-Codec, X-Upstream-Time-Ms, X-Processing-Time-Ms. No credit or model headers are returned.
Parameters (multipart form)
| Field | Required | Description |
|---|---|---|
| text | yes | Text to synthesize. Non-empty, at most 5,000 characters. |
| language | yes (default hi) | ISO code or display name. See Languages. |
| model | yes for Flash | Must be klone_v2_flash. |
| output_audio_codec | no (wav) | See Output formats. |
| share_id | one of 3 | Catalog voice ID. Works on all tiers. audio, audio_url and audio_text are ignored when it is set. |
| audio | one of 3 | Reference upload (WAV/MP3/M4A/FLAC/OGG/WebM), max 10 MB and 30 s. Enterprise only. |
| audio_url | one of 3 | Public HTTPS URL of a reference clip. Enterprise only. |
| audio_text | no | Transcript of the reference audio. Omit it to auto-transcribe. Ignored with share_id. |
Languages
Pass an ISO code or a display name (case-insensitive). Anything else returns 400 invalid_language.
| Code | Language |
|---|---|
| bn | Bengali |
| en | English |
| gu | Gujarati |
| hi | Hindi |
| kn | Kannada |
| ml | Malayalam |
| mr | Marathi |
| or | Odia |
| pa | Punjabi |
| ta | Tamil |
| te | Telugu |
| ur | Urdu |
Output formats
| output_audio_codec | Content-Type | Extension | Encoding |
|---|---|---|---|
| wav (default) | audio/wav | .wav | PCM WAV |
| mp3 | audio/mpeg | .mp3 | MP3 |
| opus | audio/ogg | .opus | Opus in Ogg |
| pcm (linear16) | audio/pcm | .pcm | Raw headerless 16-bit PCM |
| mulaw (ulaw) | audio/basic | .ulaw | μ-law (telephony) |
| alaw | audio/x-alaw-basic | .alaw | A-law (telephony) |
For web playback, mp3 or opus give the smallest payload, and wav gives the lowest latency. pcm, mulaw and alaw are headerless raw streams, so your player needs to be told the format. An unsupported value returns 400 invalid_output_audio_codec.
Streaming and WebSocket
Klone V2 Flash can also send audio while it is being generated, so playback starts before the whole clip is ready. All three modes use the same API key, voices, languages, pricing and rate limit. Streaming and the WebSocket are Flash only; Klone V2 Pro uses /v1/voice-clone/chunks.
| Mode | Endpoint | Best for |
|---|---|---|
| One-shot | POST /v1/voice-clone/chunks | Generate a whole clip and get it back in one response. |
| HTTPS streaming | POST /v1/voice-clone/stream | Start playback while the audio is still being generated (first audio in well under a second). |
| WebSocket | wss://api.fonada.ai/v1/voice-clone/ws | Voice agents and chat: keep one connection open and speak many utterances. |
Which endpoint should I use?
| You want to… | Use |
|---|---|
| Save a file, or play a short clip after it is ready | /chunks |
| Play audio as soon as possible in a web page, app or server | /stream |
| Speak many replies in a row with the lowest overhead (voice agents, IVR, chat) | /ws |
| Use instruct or num_step | /stream or /ws (/chunks ignores them) |
HTTPS streaming: POST /v1/voice-clone/stream
The response body is the audio itself, sent as it is generated. Text is split by sentence and generated in order, so playback can start on the first sentence while the rest is still being produced.
API endpoint
Base URL: https://api.fonada.ai
Endpoint: /v1/voice-clone/stream
Method: POST
Content-Type: multipart/form-data (or application/x-www-form-urlencoded when there is no file upload)
Authorization: Bearer YOUR_API_KEY
Model: Flash only. model may be omitted. Sending klone_v2_pro returns 400 streaming_not_supported; use /v1/voice-clone/chunks (also served as /v2/voice-clone/chunks) for Pro.
Parameters
Same fields as /chunks, plus the voice settings. Note that the default output is pcm here, not wav.
| Field | Required | Description |
|---|---|---|
| text | yes | Non-empty, at most 5,000 characters. |
| language | no (hi) | ISO code or display name. See Languages. |
| share_id / audio / audio_url | one of 3 | Reference voice, exactly as on /chunks (audio and audio_url are enterprise only). |
| audio_text | no | Transcript of the reference clip; auto-transcribed when omitted (not available for Urdu). |
| output_audio_codec | no (pcm) | pcm, wav, mp3, opus, mulaw, alaw. See Streaming formats. |
| instruct | no | Style hint, e.g. calm, friendly. Up to 500 characters (longer text is cut at 500). |
| num_step | no | Diffusion steps, 8 to 64 (advanced). More steps can improve quality and take longer. |
| speed, duration | no | See Voice settings. Currently unreliable; omit them unless you need them. |
| model | no | klone_v2_flash (default; the only streaming model). |
Response
Status 200 with the audio as a chunked body, and these headers:
| Header | Example | Meaning |
|---|---|---|
| Content-Type | audio/pcm | Media type of the chosen format. |
| X-Output-Codec | pcm | Format of the body. |
| X-Sample-Rate | 24000 | 8000 for mulaw / alaw, otherwise 24000. |
| X-Channels | 1 | Always mono. |
| X-Credits-Charged | 13.65 | Credits charged for this request. |
Errors found before any audio is sent come back as a normal JSON error (same format as /chunks) and the credits are refunded. If generation fails after audio has started, the stream simply ends early.
curl -N -X POST "https://api.fonada.ai/v1/voice-clone/stream" \
-H "Authorization: Bearer YOUR_API_KEY" \
-F "share_id=YOUR_SHARE_ID" \
-F "text=नमस्ते, आपका स्वागत है।" \
-F "language=hi" \
-F "output_audio_codec=mp3" \
-o stream.mp3-N turns off cURL's buffering so bytes are written as they arrive. To play it live: ... -o - | ffplay -nodisp -autoexit -.
const form = new FormData()
form.append('share_id', 'YOUR_SHARE_ID')
form.append('text', 'नमस्ते, आपका स्वागत है।')
form.append('language', 'hi')
const res = await fetch('https://api.fonada.ai/v1/voice-clone/stream', {
method: 'POST',
headers: { Authorization: 'Bearer YOUR_API_KEY' },
body: form,
})
if (!res.ok) throw new Error((await res.json()).detail.message)
// The body is raw PCM (16-bit, 24 kHz, mono). Play each piece as it arrives.
const ctx = new AudioContext({ sampleRate: 24000 })
const reader = res.body.getReader()
let playAt = ctx.currentTime
let extra = new Uint8Array(0) // a byte left over from the previous piece
while (true) {
const { value, done } = await reader.read()
if (done) break
const bytes = new Uint8Array([...extra, ...value])
const even = bytes.length - (bytes.length % 2)
extra = bytes.slice(even)
const samples = new Int16Array(bytes.buffer, 0, even / 2)
const buffer = ctx.createBuffer(1, samples.length, 24000)
buffer.getChannelData(0).set(Float32Array.from(samples, (s) => s / 32768))
const source = ctx.createBufferSource()
source.buffer = buffer
source.connect(ctx.destination)
playAt = Math.max(playAt, ctx.currentTime)
source.start(playAt)
playAt += buffer.duration
}/stream from your server and pass the audio through to the browser.Streaming formats
Used by /stream and /ws (default pcm).
| output_audio_codec | Content-Type | Sample rate | Notes |
|---|---|---|---|
| pcm (default) | audio/pcm | 24 kHz | Raw headerless 16-bit little-endian, mono. Lowest latency. |
| wav | audio/wav | 24 kHz | 44-byte WAV header first, then PCM. The size fields in the header are 0xFFFFFFFF because the length is unknown while streaming; fix them after the download if a tool needs real sizes (the Python SDK does this for you in generate_audio). |
| mp3 | audio/mpeg | 24 kHz | Plays progressively in browsers and most players. |
| opus | audio/ogg | 24 kHz input (Ogg Opus, decoded at 48 kHz) | Smallest payload. |
| mulaw | audio/basic | 8 kHz | Raw μ-law, mono (telephony). |
| alaw | audio/x-alaw-basic | 8 kHz | Raw A-law, mono (telephony). |
linear16 is accepted as an alias of pcm, and ulaw of mulaw. pcm, mulaw and alaw are headerless, so your player needs to be told the format. An unsupported value returns 400 invalid_output_audio_codec.
WebSocket: wss://api.fonada.ai/v1/voice-clone/ws
One connection carries many utterances. Send a JSON message per utterance; the server answers with binary audio frames for it. Utterances are processed one at a time, in the order they are sent.
Connect and authenticate
- URL:
wss://api.fonada.ai/v1/voice-clone/ws - Auth:
Authorization: Bearer YOUR_API_KEYheader, or?api_key=YOUR_API_KEYin the URL (for clients that cannot set headers). - Right after connecting, the server sends a
readymessage (below). - Without a key, the server refuses the handshake with HTTP 403 (most clients report it as a connection failure, close code 1006). With an invalid key, the server sends
{"type": "error", "error": "invalid_api_key", ...}and then closes with code1008. - A connection with no message for 5 minutes is closed with code
1000(idle timeout). Open a new one when you need it. - Opening a connection is free and does not count toward the rate limit; each utterance does.
{
"type": "ready",
"model": "klone_v2_flash",
"codecs": ["pcm", "wav", "mp3", "opus", "mulaw", "alaw"],
"default_codec": "pcm",
"sample_rate": 24000,
"channels": 1
}Client → server: one utterance
{
"text": "नमस्ते, मैं आपकी कैसे मदद कर सकता हूँ?",
"share_id": "YOUR_SHARE_ID",
"language": "hi",
"output_audio_codec": "pcm",
"request_id": "reply-42"
}| Field | Required | Description |
|---|---|---|
| text | yes | Non-empty, at most 5,000 characters. |
| share_id | one of 2 | Catalog voice ID. |
| audio_url | one of 2 | Reference clip URL (enterprise only). File upload is not available on the WebSocket. |
| audio_text | no | Transcript of the audio_url clip; auto-transcribed when omitted (not available for Urdu). |
| language | no (hi) | ISO code or display name. |
| output_audio_codec | no (pcm) | Same values as Streaming formats. |
| request_id | no | Your ID for the utterance; echoed in start, end and error. Generated if omitted. |
| instruct, num_step, speed, duration | no | Same ranges as Voice settings. |
Server → client
For each utterance you receive, in order:
- start (text frame)
- Audio as binary frames (any number). Concatenate or play them in order.
- end (text frame)
{"type": "start", "request_id": "reply-42", "output_audio_codec": "pcm",
"sample_rate": 24000, "channels": 1, "credits_charged": 1.95}{"type": "end", "request_id": "reply-42", "audio_bytes": 74880,
"first_audio_ms": 234, "total_ms": 503}If the utterance fails you get an error instead of end (or instead of start, when it fails before any audio):
{"type": "error", "request_id": "reply-42", "status": 400,
"error": "invalid_language", "message": "Language 'xx' is not supported by the klone_v2_flash model."}status is the HTTP status the same error would have on /stream, and error uses the same codes (see Errors). After a failure mid-utterance, audio_bytes_sent tells you how much audio was already sent. The connection stays open after an error, so you can send the next utterance. A message that is not a JSON object returns {"type": "error", "error": "invalid_message"}.
import fs from 'fs'
import WebSocket from 'ws'
const ws = new WebSocket('wss://api.fonada.ai/v1/voice-clone/ws', {
headers: { Authorization: 'Bearer YOUR_API_KEY' },
})
const file = fs.createWriteStream('hello.pcm') // raw PCM: 16-bit, 24 kHz, mono
ws.on('message', (data, isBinary) => {
if (isBinary) return file.write(data) // audio
const msg = JSON.parse(data)
if (msg.type === 'ready') {
ws.send(JSON.stringify({ text: 'Hello! How can I help you?', share_id: 'YOUR_SHARE_ID', language: 'en' }))
}
if (msg.type === 'end') ws.close() // or send the next utterance
if (msg.type === 'error') console.error(msg.error, msg.message)
})Play the file with ffplay -f s16le -ar 24000 -ac 1 hello.pcm. In a browser, pass the key as ?api_key= only from trusted environments; for public pages, connect to your own server and relay.
Voice settings
Applied on /stream and /ws. Ignored by /chunks (the Python SDK routes to /stream when you set any of them).
| Setting | Range | Default | Effect |
|---|---|---|---|
| instruct | up to 500 characters | none | Comma-separated style hints, e.g. calm, whispering. |
| num_step | 8 – 64 | server default | Diffusion steps (advanced). |
| speed | 0.25 – 4.0 | model's natural pace | Speaking rate. >1 faster, <1 slower. |
| duration | 0.1 – 60 s | none | Target length for the whole clip. Overrides speed. |
speed and duration are currently unreliable on Flash: the output length may not follow them, and sending speed=1.0 can change the pace. Omit them unless you need them. instruct and num_step work as described.Out-of-range values return 400 invalid_speed, invalid_duration or invalid_num_step. A value that is not a number returns 422 with a validation error body on /stream ({"detail": [{"loc": [...], "msg": ..., "type": ...}]}), and invalid_parameter on the WebSocket.
Pricing
Priced per 10K characters, billed per character of input text. The price is the same for /chunks, /stream and /ws.
| Model | Price | Credits | Billing |
|---|---|---|---|
| Klone V2 Flash | ₹6 per 10K characters | ~500 credits | Billed per character |
- Paid Voice Arena voices (2x or higher) multiply this rate. Free voices, your own uploads and audio URLs are billed at 1x. Example: 39 characters on a 7x voice costs 13.65 credits.
- Credits are rounded to 2 decimals, with a minimum of 0.01 credits per request.
- On
/streamand/wsthe full text is charged when the request starts./streamreturns the amount inX-Credits-Charged; the WebSocket sends it ascredits_chargedinstart. - Failed requests are refunded automatically when they fail before any audio is sent. Once streamed audio has started (including when you disconnect or stop reading a WebSocket utterance early), the request is billed in full.
See the full pricing page.
Rate limits
- 10 requests per minute per user by default, shared by
/chunks,/streamand/ws. Each WebSocket utterance counts as one request; opening a connection does not. - On the WebSocket, a rate-limited utterance returns an
errormessage withstatus: 429and the connection stays open. - A global concurrency cap returns the same error with
rate_period: "concurrent"andretry_after_seconds: 5. - Flash handles roughly 1.5 to 2 requests per second in total. Under heavy load, latency grows instead of erroring.
- The wait time is only in the body. There is no
Retry-Afterheader.
{
"detail": {
"error": "rate_limit_exceeded",
"message": "Rate limit exceeded. You can make 10 requests per minute.",
"details": {
"rate_limit": 10,
"current_usage": 10,
"remaining": 0,
"retry_after_seconds": 37,
"rate_period": "minute"
}
}
}Errors
All errors are JSON: {"detail": {"error": "<code>", "message": "<text>"}}
| HTTP | error | Cause |
|---|---|---|
| 400 | empty_text | text is empty or whitespace |
| 400 | text_too_long | text is longer than 5,000 characters (limit_chars and received_chars in the body); nothing is charged |
| 400 | invalid_language | language is not in the supported list (response includes supported_languages) |
| 400 | missing_audio_source | No reference source supplied |
| 400 | ambiguous_audio_source | More than one of share_id / audio / audio_url supplied |
| 400 | empty_audio / audio_decode_failed / audio_too_long | Reference upload is empty, not valid audio, or longer than 30 s |
| 413 | audio_too_large | Reference upload larger than 10 MB |
| 403 | enterprise_only | audio / audio_url on a non-enterprise tier |
| 403 | invalid_api_key / inactive_api_key | Bad or disabled API key |
| 404 | voice_not_found | share_id is not in the catalog |
| 429 | rate_limit_exceeded | More than 10 requests per minute per user, or too many requests in flight (see Rate limits) |
| 429 | credits_exhausted | Insufficient credits / credit limit reached |
| 500 | encoding_failed | Server-side transcode to the requested codec failed |
| 502 | upstream_error / upstream_unreachable / upstream_empty | Upstream cloner failed or returned no audio. Credits are refunded automatically |
Streaming and WebSocket errors
In addition to the errors above. WebSocket errors carry the same error code and message in a {"type": "error", "status": <http status>, ...} message.
| HTTP | error | Cause |
|---|---|---|
| 400 | invalid_speed / invalid_duration / invalid_num_step | Voice setting out of range (/stream, /ws) |
| 400 | invalid_parameter | Voice setting is not a number (WebSocket only) |
| 422 | — | A form field has the wrong type, e.g. speed=fast on /stream (FastAPI validation body: {"detail": [...]}) |
| 400 | streaming_not_supported | model=klone_v2_pro sent to /stream or /ws. Use /v1/voice-clone/chunks (or the /v2/voice-clone/chunks alias) for Pro |
| 400 | invalid_model | Unknown model on /stream or /ws (response includes supported_models) |
| — (WebSocket) | invalid_message | WebSocket message is not a JSON object |
| 502 | upstream_stream_broken | The cloner connection broke after audio had started; the stream ends early and the request is billed |
Python SDK
Set model="klone_v2_flash" on TTSClient.generate_audio(). As with Pro, mp3 and opus are limited to 450 characters in the SDK. Streaming and the WebSocket require fonadalabs 3.0.0 or later (pip install -U fonadalabs). See the Klone V2 Pro SDK section for authentication.
from fonadalabs.tts.client import TTSClient
client = TTSClient(api_key="YOUR_API_KEY")
audio_bytes = client.generate_audio(
text="Hello from flash cloning.",
language="Hindi",
model="klone_v2_flash",
share_id="YOUR_SHARE_ID",
output_file="flash.wav",
)Stream: stream_audio / stream_audio_async
from fonadalabs.tts.client import TTSClient
client = TTSClient(api_key="YOUR_API_KEY")
with open("hello.mp3", "wb") as f:
for chunk in client.stream_audio("नमस्ते, आपका स्वागत है।", language="hi",
share_id="YOUR_SHARE_ID", output_audio_codec="mp3"):
f.write(chunk) # or send it to your player
print(client.last_stream_info.credits_charged)import asyncio
from fonadalabs.tts.client import TTSClient
async def main():
client = TTSClient(api_key="YOUR_API_KEY")
with open("hello.pcm", "wb") as f:
async for chunk in client.stream_audio_async("Hello!", language="en", share_id="YOUR_SHARE_ID"):
f.write(chunk) # raw PCM: 16-bit, 24 kHz, mono
asyncio.run(main())Both accept share_id / audio_url / audio, audio_text, output_audio_codec, instruct, num_step, speed and duration. Invalid arguments raise before anything is sent. Only Flash streams; model="klone_v2_pro" raises TTSError.
WebSocket: stream_session
import asyncio
from fonadalabs.tts.client import TTSClient
async def main():
client = TTSClient(api_key="YOUR_API_KEY")
async with client.stream_session() as session: # one connection, many replies
with open("replies.pcm", "wb") as f:
for text in ["Hello!", "How can I help you today?"]:
async for chunk in session.speak(text, language="en", share_id="YOUR_SHARE_ID"):
f.write(chunk) # raw PCM: 16-bit, 24 kHz, mono
asyncio.run(main())session.speak(text, language, share_id=... | audio_url=..., audio_text=..., output_audio_codec=..., instruct=..., num_step=..., request_id=...)yields the audio of one utterance.session.readyholds the server'sreadymessage,session.last_infothestartdetails (codec, sample rate, credits) andsession.last_endtheendmessage.- A rejected utterance raises
VoiceCloneError(fromfonadalabs.voice_clone;EnterpriseOnlyError,RateLimitErrorandCreditsExhaustedErrorare subclasses). The session stays open for the next utterance. - If you stop reading an utterance early, the next
speakskips its remaining audio automatically (the abandoned utterance is still billed).
Voice settings with generate_audio
generate_audio (one-shot) also accepts instruct, num_step, speed and duration for Flash. When any is set, the SDK collects the audio from /stream and returns it in one piece (with correct WAV sizes).
generate_audio also sends speed=1.0 whenever you set one of the other settings, which can change the pace (see Voice settings). Until a fixed release, use stream_audio or stream_session for instruct and num_step; they send only what you pass.FAQ
Both use POST /v1/voice-clone/chunks with the same reference sources, response headers and errors. Flash is faster, supports 12 languages and takes the whole text in one request (the server splits and merges it). On /chunks it ignores speed, duration, instruct and num_step; they apply on /stream and the WebSocket, which are Flash only. Flash also costs a tenth of Pro; see Pricing.
