Clone a voice
/v1/custom-voicesClone a voice from a recording and use its voice ID to generate speech.
1. Upload your recording
Send multipart/form-data with file, a unique name, provider, and consent=true to confirm the speaker's permission and your rights to use the recording.
The examples use a WAV file named sample.wav. Replace it with a clean recording of one speaker, without music or background noise. Other accepted audio formats depend on the provider. See the sample requirements below before uploading.
2. Get the voice ID from the response
The response returns your voice ID in id:
{ "id": "AM:@narrator", "voices": [{ "voice": { "id": "AM:@narrator", "status": "active" }, "model": { "id": "soniox/tts-rt-v2" }, "providers": [{ "status": "active" }] }]}In this example, the voice ID is AM:@narrator. Copy the complete value. You will use it to generate speech and to get or delete the voice later.
When both status values are active, the voice is ready. If either is still pending, wait and get the voice again. If cloning failed, failure_reason explains why.
3. Generate speech
Use id from the response as voice, and voices[].model.id as model:
{ "model": "soniox/tts-rt-v2", "voice": "AM:@narrator", "input": "Hello from my cloned voice."}The complete code examples below upload your recording, check that your voice is ready, and save the generated audio to speech.mp3.
Sample requirements
| Provider | Maximum sample size | Duration | Language field |
|---|---|---|---|
soniox | 10 MiB | At most 120 seconds | Omit |
fish | 10 MiB | Not specified | Omit |
minimax | 10 MiB | 10–300 seconds | Omit |
cartesia | 10 MiB | At least 10 seconds | Required |
fal | 10 MiB | At least 10 seconds | Omit |
inworld | 10 MiB | 3–30 seconds | Required |
The complete multipart request must fit within 11 MiB. Use List providers to find providers with voice_cloning support. A cloning fee may apply; speech generation is billed separately.
Language codes
Use one of the codes below when sending language. Omit the field for providers marked Omit above. Where listed, auto asks the provider to detect the language.
- cartesia:
en,fr,de,es,pt,zh,ja,hi,it,ko,nl,pl,ru,sv,tr,tl,bg,ro,ar,cs,el,fi,hr,ms,sk,da,ta,uk,hu,no,vi,bn,th,he,ka,id,te,gu,kn,ml,mr,pa,or,ur - inworld:
auto,ar,de,en,es,fr,he,hi,it,ja,ko,nl,pl,pt,ru,zh
Optional recording fields
Omit capture_script_version unless your application tracks versions of a recording script. Omit consent_id for the consent=true flow; it is only used with a separate spoken-consent challenge.
Authorization
BearerAuth Tenant key supplied as the Authorization Bearer token.
In: header
Request Body
multipart/form-data
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
import { readFile, writeFile } from "node:fs/promises";// Node.js 22+. Set ALLMODELS_API_KEY and use your own sample.wav.const form = new FormData();form.set("file", new Blob([await readFile("sample.wav")], { type: "audio/wav" }), "sample.wav");form.set("name", "narrator");form.set("provider", "soniox");form.set("consent", "true");const response = await fetch("https://api.allmodels.io/v1/custom-voices", { method: "POST", headers: { Authorization: "Bearer " + process.env.ALLMODELS_API_KEY }, body: form});if (!response.ok) throw new Error("Clone failed (" + response.status + "): " + await response.text());const result = await response.json();const voiceId = result.id;console.log("Your voice ID:", voiceId);// If the voice is still pending, the speech call fails; get the voice again to check status and failure_reason.const speech = await fetch("https://api.allmodels.io/oai/audio/speech", { method: "POST", headers: { Authorization: "Bearer " + process.env.ALLMODELS_API_KEY, "Content-Type": "application/json" }, body: JSON.stringify({ model: "soniox/tts-rt-v2", voice: voiceId, input: "Hello from my cloned voice.", response_format: "mp3" })});if (!speech.ok) throw new Error("Speech failed: " + await speech.text());await writeFile("speech.mp3", Buffer.from(await speech.arrayBuffer()));import osimport requests# Set ALLMODELS_API_KEY and use your own sample.wav.with open("sample.wav", "rb") as audio: response = requests.post( "https://api.allmodels.io/v1/custom-voices", headers={"Authorization": f"Bearer {os.environ['ALLMODELS_API_KEY']}"}, files={"file": ("sample.wav", audio, "audio/wav")}, data={"name": "narrator", "provider": "soniox", "consent": "true"}, timeout=120, )response.raise_for_status()result = response.json()voice_id = result["id"]print("Your voice ID:", voice_id)# If the voice is still pending, the speech call fails; get the voice again to check status and failure_reason.speech = requests.post( "https://api.allmodels.io/oai/audio/speech", headers={"Authorization": f"Bearer {os.environ['ALLMODELS_API_KEY']}"}, json={"model": "soniox/tts-rt-v2", "voice": voice_id, "input": "Hello from my cloned voice.", "response_format": "mp3"}, timeout=60,)speech.raise_for_status()with open("speech.mp3", "wb") as output: output.write(speech.content)# Requires jq. Set ALLMODELS_API_KEY and use your own sample.wav.set -ecurl --fail-with-body https://api.allmodels.io/v1/custom-voices \ -H "Authorization: Bearer $ALLMODELS_API_KEY" \ -F "file=@sample.wav;type=audio/wav" \ -F "name=narrator" \ -F "provider=soniox" \ -F "consent=true" \ --output cloned-voice.json# If the voice is still pending, the speech call fails; get the voice again to check status and failure_reason.jq '{model: "soniox/tts-rt-v2", voice: .id, input: "Hello from my cloned voice.", response_format: "mp3"}' \ cloned-voice.json > speech-request.jsoncurl --fail-with-body https://api.allmodels.io/oai/audio/speech \ -H "Authorization: Bearer $ALLMODELS_API_KEY" \ -H "Content-Type: application/json" \ --data @speech-request.json \ --output speech.mp3{ "id": "AM:@narrator", "voices": [ { "model": { "id": "soniox/tts-rt-v2", "name": "Soniox tts-rt-v2" }, "voice": { "id": "AM:@narrator", "name": "narrator", "description": null, "category": { "id": "cloned", "name": "Cloned" }, "languages": [], "scope": { "type": "organization" }, "status": "active", "fee_credits": 0, "first_seen_at": "2026-09-11T02:00:00.000Z", "last_seen_at": "2026-09-11T02:00:05.000Z" }, "providers": [ { "id": "soniox", "name": "Soniox", "provider_model_id": "tts-rt-v2", "status": "active", "failure_reason": null } ] } ], "total_count": 1, "has_more": false, "next_cursor": null}{ "error": "invalid_request"}{ "error": "invalid_api_key"}{ "error": "insufficient_credits"}{ "error": "tenant_disabled"}{ "error": "name_taken", "voice_id": "AM:@narrator"}{ "error": "sample_too_large"}{ "error": "consent_required"}{ "error": "custom_voices_failed", "voice_id": "AM:@narrator", "message": "Creation could not be confirmed. Retrieve this voice using voice_id and check its status before uploading another sample."}{ "error": "clone_failed"}{ "error": "custom_voices_unavailable"}List your cloned voices GET
List your organization's cloned voices across all projects, newest first by default. Deleted voices are excluded. Each result contains a voice ID in `voice.id`, for example `AM:@narrator`. Use that ID as `voice` when generating speech.
Get one cloned voice GET
Retrieve a cloned voice using its voice ID. Pass the ID as `voice_id` in the URL, for example `GET /v1/custom-voices/AM:@narrator`. Use this endpoint to check its status and see which speech models support it.
