curl --request POST \
--url https://api.nineninesix.ai/voices/clone \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form clip=@example-file \
--form 'name=<string>' \
--form language=en-US \
--form 'description=<string>' \
--form 'transcript=<string>'import requests
url = "https://api.nineninesix.ai/voices/clone"
files = { "clip": ("example-file", open("example-file", "rb")) }
payload = {
"name": "<string>",
"language": "en-US",
"description": "<string>",
"transcript": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('clip', '<string>');
form.append('name', '<string>');
form.append('language', 'en-US');
form.append('description', '<string>');
form.append('transcript', '<string>');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.nineninesix.ai/voices/clone', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.nineninesix.ai/voices/clone",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"clip\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"language\"\r\n\r\nen-US\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"transcript\"\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.nineninesix.ai/voices/clone"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"clip\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"language\"\r\n\r\nen-US\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"transcript\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.nineninesix.ai/voices/clone")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"clip\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"language\"\r\n\r\nen-US\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"transcript\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.nineninesix.ai/voices/clone")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"clip\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"language\"\r\n\r\nen-US\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"transcript\"\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "5148a2db-5f7c-480c-8f7c-39c6414a4d5d",
"is_owner": false,
"is_public": true,
"name": "Jessica",
"description": "Polished, quick New York professional around thirty-five, warm underneath the hustle. Jessica makes a stressful thing feel manageable.",
"gender": "feminine",
"language": "en-US",
"country": "US",
"created_at": "2026-08-14T18:26:40.876Z",
"preview_file_url": "<string>"
}Clone a voice
Clone a voice from a reference audio clip. Use the returned voice id in your /tts/bytes, /tts/sse, and /tts/websocket requests.
WAV is recommended; FLAC, OGG, MP3, and M4A also work. Clips must be 1–300 s and the upload under 20 MB. The server trims leading and trailing silence, uses the first 20 s of speech, and normalizes loudness. 10–20 s of clean speech works best, and the voice takes on the clip’s pace and pauses. A 48 kHz preview is rendered automatically after cloning; fetch it with GET /voices?expand[]=preview_file_url.
Errors come in two shapes: audio validation errors are { "detail": "..." }; invalid_language, clone_failed, and db_unavailable use { error, message }.
curl --request POST \
--url https://api.nineninesix.ai/voices/clone \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: multipart/form-data' \
--form clip=@example-file \
--form 'name=<string>' \
--form language=en-US \
--form 'description=<string>' \
--form 'transcript=<string>'import requests
url = "https://api.nineninesix.ai/voices/clone"
files = { "clip": ("example-file", open("example-file", "rb")) }
payload = {
"name": "<string>",
"language": "en-US",
"description": "<string>",
"transcript": "<string>"
}
headers = {"Authorization": "Bearer <token>"}
response = requests.post(url, data=payload, files=files, headers=headers)
print(response.text)const form = new FormData();
form.append('clip', '<string>');
form.append('name', '<string>');
form.append('language', 'en-US');
form.append('description', '<string>');
form.append('transcript', '<string>');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
fetch('https://api.nineninesix.ai/voices/clone', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.nineninesix.ai/voices/clone",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"clip\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"language\"\r\n\r\nen-US\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"transcript\"\r\n\r\n<string>\r\n-----011000010111000001101001--",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: multipart/form-data"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.nineninesix.ai/voices/clone"
payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"clip\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"language\"\r\n\r\nen-US\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"transcript\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.nineninesix.ai/voices/clone")
.header("Authorization", "Bearer <token>")
.body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"clip\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"language\"\r\n\r\nen-US\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"transcript\"\r\n\r\n<string>\r\n-----011000010111000001101001--")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.nineninesix.ai/voices/clone")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"clip\"; filename=\"example-file\"\r\nContent-Type: application/octet-stream\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"language\"\r\n\r\nen-US\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"description\"\r\n\r\n<string>\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"transcript\"\r\n\r\n<string>\r\n-----011000010111000001101001--"
response = http.request(request)
puts response.read_body{
"id": "5148a2db-5f7c-480c-8f7c-39c6414a4d5d",
"is_owner": false,
"is_public": true,
"name": "Jessica",
"description": "Polished, quick New York professional around thirty-five, warm underneath the hustle. Jessica makes a stressful thing feel manageable.",
"gender": "feminine",
"language": "en-US",
"country": "US",
"created_at": "2026-08-14T18:26:40.876Z",
"preview_file_url": "<string>"
}Authorizations
Pass your API key as a Bearer token: Authorization: Bearer sk_996_....
Body
Reference audio to clone from. WAV recommended; FLAC, OGG, MP3, and M4A also work. 1–300 s, under 20 MB. Only the first 20 s of speech (after silence trimming) is used.
A name for the cloned voice.
A well-formed BCP-47 language tag, e.g. en-US, en-GB, es-MX, pt-BR, nl-NL. Stored as metadata (used for listing and filtering); it doesn't change synthesis. The value is canonicalized on save (en-us → en-US, pt-br → pt-BR). Malformed tags (en_US, en-USA, english_, 123) are rejected with invalid_language (400).
"en-US"
A description of the voice.
The exact words spoken in the clip. With a transcript, the clone follows the speaker, including their pace, more closely. It must match the audio exactly, and it's ignored if the clip has 20 s or more of speech after silence trimming, so send a clip under 20 s when you use it. Not a Cartesia SDK field: pass it via extra_body (Python) or as an extra key with a type cast (TypeScript).
Response
The newly created voice.
"5148a2db-5f7c-480c-8f7c-39c6414a4d5d"
true if your org owns the voice (you cloned it).
false
true for shared library voices.
true
"Jessica"
"Polished, quick New York professional around thirty-five, warm underneath the hustle. Jessica makes a stressful thing feel manageable."
masculine, feminine, gender_neutral "feminine"
BCP-47 dialect tag (e.g. en-US, nl-NL).
"en-US"
Country code.
"US"
When the voice was created.
"2026-08-14T18:26:40.876Z"
Only present when the request sends expand[]=preview_file_url. A signed, browser-playable URL of the voice's preview.