curl --fail-with-body --request POST \
--url "$XPILOT_API_BASE_URL/v1/voices/clone" \
--header "Authorization: Bearer $XPILOT_API_KEY" \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: lesson-request-001' \
--data '{"name":"<name>","audio_url":"<audio_url>","voice_version":1}'const response = await fetch(process.env.XPILOT_API_BASE_URL + "/v1/voices/clone", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "lesson-request-001",
},
body: JSON.stringify({
"name": "<name>",
"audio_url": "<audio_url>",
"voice_version": 1
}),
});
const result = await response.json();
if (!response.ok) throw new Error(JSON.stringify(result));
console.log(result);import os, json, requests
response = requests.request(
"POST",
os.environ["XPILOT_API_BASE_URL"] + "/v1/voices/clone",
headers={
"Authorization": "Bearer " + os.environ["XPILOT_API_KEY"],
"Content-Type": "application/json",
"Idempotency-Key": "lesson-request-001",
},
json=json.loads("{\"name\":\"<name>\",\"audio_url\":\"<audio_url>\",\"voice_version\":1}"),
timeout=70,
)
print(response.status_code, response.text)
response.raise_for_status(){
"data": {
"_id": "<voice_id>",
"title": "My clone",
"voice_version": 2,
"state": "training",
"selectable": false
}
}{
"error": {
"code": "invalid_parameter",
"message": "Check parameter types, required fields and idempotency key."
},
"request_id": "<request_id>"
}{
"error": {
"code": "invalid_token",
"message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
},
"request_id": "<request_id>"
}{
"error": {
"code": "insufficient_scope",
"message": "Check the credential scope and resource ownership."
},
"request_id": "<request_id>"
}{
"error": {
"code": "idempotency_conflict",
"message": "The same idempotency key must use the exact same input. A conversation may already be busy."
},
"request_id": "<request_id>"
}{
"error": {
"code": "rate_limited",
"message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
"retry_after": 60
},
"request_id": "<request_id>"
}clone voice
Clone a private voice from audio already uploaded to your account. Voice I (default) is an instant clone from one sample. Voice II needs the speaker’s consent: call get_voice_clone_config, have the speaker record the consent_statement for the chosen locale with their name and company_name filled in, upload it separately, then pass voice_talent_name, locale, consent_audio_url and consent_accepted=true. Never fabricate consent or set consent_accepted without the speaker’s explicit agreement. Voice II returns 403 voice_ii_unavailable while cloning is not open for the account. Poll get_voice until state is trained or failed. Reuse Idempotency-Key on retry.
curl --fail-with-body --request POST \
--url "$XPILOT_API_BASE_URL/v1/voices/clone" \
--header "Authorization: Bearer $XPILOT_API_KEY" \
--header 'Content-Type: application/json' \
--header 'Idempotency-Key: lesson-request-001' \
--data '{"name":"<name>","audio_url":"<audio_url>","voice_version":1}'const response = await fetch(process.env.XPILOT_API_BASE_URL + "/v1/voices/clone", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.XPILOT_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": "lesson-request-001",
},
body: JSON.stringify({
"name": "<name>",
"audio_url": "<audio_url>",
"voice_version": 1
}),
});
const result = await response.json();
if (!response.ok) throw new Error(JSON.stringify(result));
console.log(result);import os, json, requests
response = requests.request(
"POST",
os.environ["XPILOT_API_BASE_URL"] + "/v1/voices/clone",
headers={
"Authorization": "Bearer " + os.environ["XPILOT_API_KEY"],
"Content-Type": "application/json",
"Idempotency-Key": "lesson-request-001",
},
json=json.loads("{\"name\":\"<name>\",\"audio_url\":\"<audio_url>\",\"voice_version\":1}"),
timeout=70,
)
print(response.status_code, response.text)
response.raise_for_status(){
"data": {
"_id": "<voice_id>",
"title": "My clone",
"voice_version": 2,
"state": "training",
"selectable": false
}
}{
"error": {
"code": "invalid_parameter",
"message": "Check parameter types, required fields and idempotency key."
},
"request_id": "<request_id>"
}{
"error": {
"code": "invalid_token",
"message": "Use an active scoped API Key. MCP OAuth tokens cannot call REST."
},
"request_id": "<request_id>"
}{
"error": {
"code": "insufficient_scope",
"message": "Check the credential scope and resource ownership."
},
"request_id": "<request_id>"
}{
"error": {
"code": "idempotency_conflict",
"message": "The same idempotency key must use the exact same input. A conversation may already be busy."
},
"request_id": "<request_id>"
}{
"error": {
"code": "rate_limited",
"message": "Credential/workspace rate or concurrency limit reached. Respect Retry-After before retrying.",
"retry_after": 60
},
"request_id": "<request_id>"
}Documentation by X-Pilot
Required scope:audio:write. A stable Idempotency-Key is required for exact retries.
Manage API keys · Authentication and recovery

Authorizations
Scoped X-Pilot API key
Headers
Stable key for exact retries only. Use a different key for a different operation or input.
8 - 128Body
Voice title.
80Owned uploaded training audio URL. Voice II: 5–90 seconds of the same speaker.
Optional owned uploaded cover image URL. Voice I only.
Voice I (1, default) or Voice II (2, consent-based personal voice).
1, 2 Voice II only: the speaker's name exactly as spoken in the consent recording.
128Voice II only: recording locale from get_voice_clone_config, for example en-US or zh-CN.
16Voice II only: owned uploaded recording of the speaker reading the consent_statement for locale. Must be a separate file from audio_url, at most 90 seconds.
Voice II only: true when the speaker has explicitly agreed that their voice may be cloned. Required for Voice II.
true, false Response
Success
