> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.heyreach.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.heyreach.io/_mcp/server.

# CreateCustomVoice

POST https://api//api/public/voice/CreateCustomVoice
Content-Type: multipart/form-data

Clone a custom voice from a recorded or uploaded audio sample of **15–30 seconds**, sent as a multipart `file` part whose own `Content-Type` is the audio's MIME type (`audio/mpeg`, `audio/mp3`, `audio/wav`, `audio/x-wav`, `audio/mp4`, `audio/m4a`, `audio/x-m4a`, `audio/ogg`, `audio/webm`, `audio/aac` or `audio/flac`). The file may be at most 25 MiB; a request body over 26 MiB is refused with a bare `413`.

When the sample raises soft warnings, the call returns `200` with a null `voice` and the `warnings`; re-submit the **same** audio with `ignoreSoftWarnings = true` to clone anyway.

Pass a `clientRequestId`: one GUID per recording, reused for the re-submit and for any retry. A token that already produced a voice returns that voice, because a clone can outlive the caller's timeout.

### Errors

- **400** `VOICE_AUDIO_MISSING`, `VOICE_AUDIO_TOO_LARGE`, `VOICE_AUDIO_UNSUPPORTED`, `VOICE_AUDIO_DURATION_OUT_OF_RANGE`, `VOICE_AUDIO_UNINTELLIGIBLE` or `CLIENT_REQUEST_ID_CONFLICT`: change the request.
- **403** `NO_ACTIVE_SUBSCRIPTION` or `VOICE_NOTES_NOT_ENABLED`.
- **404** `CUSTOM_VOICE_NOT_FOUND`: the voice was deleted while it was being created.
- **502** `VOICE_PROVIDER_UNAVAILABLE`, and **503** `VOICE_ENGINE_BUSY`, `VOICE_TRANSCRIPTION_UNAVAILABLE` or `VOICE_AUDIO_PROCESSING_UNAVAILABLE`: transient, so retry with the same `clientRequestId`.

Reference: https://docs.heyreach.io/hey-reach-api/voice/create-custom-voice

## Request

### Headers

- `X-API-KEY` (string, optional) — API key header using this scheme. Example: "X-API-KEY: \{API\_KEY}"

### Body (multipart/form-data)

This endpoint expects a multipart form containing a file.

- `file` (file, required) — The audio sample, 15–30 seconds, at most 25 MiB. The part's own `Content-Type` must be the audio's MIME type.
- `name` (string, optional)
- `gender` (enum, optional) — Defaults to MALE.
- `source` (enum, optional) — Defaults to RECORDED.
- `ignoreSoftWarnings` (boolean, optional) — Set to true to clone despite the soft warnings a previous call returned for the same audio.
- `clientRequestId` (string, optional) — One GUID per recording, reused for the re-submit and for any retry.
- `consentConfirmed` (boolean, optional) — Optional; not required through the API.

## Response

### 200

OK

- `voice` (map from string to any, required, nullable) — The cloned voice, in the shape of a **GetVoices** item. Null when the sample raised soft warnings and `ignoreSoftWarnings` was not set.
- `warnings` (list of CreateCustomVoiceVoiceResponseWarningsItem, required) — Soft findings on the sample: `SCRIPT_MISMATCH` or `LOW_QUALITY`.

## Errors

### 400 Create Custom Voice Voice Request Bad Request Error

Bad Request. `VOICE_AUDIO_MISSING`: no `file` part, or an empty file. `VOICE_AUDIO_TOO_LARGE`: the file is over 25 MiB. `VOICE_AUDIO_UNSUPPORTED`: the file's `Content-Type` is not an accepted audio type, or the audio could not be decoded. `VOICE_AUDIO_DURATION_OUT_OF_RANGE`: the sample is not 15–30 seconds long. `VOICE_AUDIO_UNINTELLIGIBLE`: no speech was detected or transcribed in the sample. `CLIENT_REQUEST_ID_CONFLICT`: the `clientRequestId` is already used by a voice in another workspace; send a new GUID.

- `errorMessage` (string, required)
- `errorCode` (enum, optional)
  - Allowed values: `PAGING_OUT_OF_RANGE`

### 403 Create Custom Voice Voice Request Forbidden Error

Forbidden. `NO_ACTIVE_SUBSCRIPTION`: the workspace has no active subscription. `VOICE_NOTES_NOT_ENABLED`: voice notes are not enabled for the workspace.

- `errorMessage` (string, required)
- `errorCode` (enum, optional)
  - Allowed values: `NO_ACTIVE_SUBSCRIPTION`, `VOICE_NOTES_NOT_ENABLED`, `ELEVENLABS_NOT_ENABLED`

### 404 Create Custom Voice Voice Request Not Found Error

Not Found. `CUSTOM_VOICE_NOT_FOUND`: the voice was deleted while it was being created.

- `errorMessage` (string, required)
- `errorCode` (enum, optional)
  - Allowed values: `ELEVENLABS_VOICE_NOT_FOUND`, `ELEVENLABS_VOICE_ACCESS_DENIED`

### 502 Create Custom Voice Voice Request Bad Gateway Error

Bad Gateway. `VOICE_PROVIDER_UNAVAILABLE`: the voice engine failed. Transient, so retry with the same `clientRequestId`.

- `errorMessage` (string, required)
- `errorCode` (enum, optional)
  - Allowed values: `ELEVENLABS_UNAVAILABLE`

### 503 Create Custom Voice Voice Request Service Unavailable Error

Service Unavailable. `VOICE_ENGINE_BUSY`: the voice engine is at capacity. `VOICE_TRANSCRIPTION_UNAVAILABLE`: a recorded sample's reference text could not be verified just now. `VOICE_AUDIO_PROCESSING_UNAVAILABLE`: the audio could not be processed on our side; this is a server fault, not a problem with the file. All transient, so retry with the same `clientRequestId`.

- `errorMessage` (string, required)
- `errorCode` (enum, optional)
  - Allowed values: `VOICE_AUDIO_PROCESSING_UNAVAILABLE`

## Types

### CreateCustomVoiceVoiceResponseWarningsItem

- `code` (string, required)
- `message` (string, required)

## Examples

**Request**

```json
{
  "file": "<file: <file1>>"
}
```

**Response**

```json
{
  "voice": null,
  "warnings": [
    {
      "code": "LOW_QUALITY",
      "message": "<string>"
    }
  ]
}
```

**SDK Code**

```python
import requests

url = "https://api//api/public/voice/CreateCustomVoice"

files = { "file": "open('<file1>', 'rb')" }
payload = {
    "clientRequestId": ,
    "consentConfirmed": ,
    "gender": ,
    "ignoreSoftWarnings": ,
    "name": ,
    "source": 
}
headers = {"X-API-KEY": "<string>"}

response = requests.post(url, data=payload, files=files, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api//api/public/voice/CreateCustomVoice';
const form = new FormData();
form.append('clientRequestId', '');
form.append('consentConfirmed', '');
form.append('file', '<file1>');
form.append('gender', '');
form.append('ignoreSoftWarnings', '');
form.append('name', '');
form.append('source', '');

const options = {method: 'POST', headers: {'X-API-KEY': '<string>'}};

options.body = form;

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api//api/public/voice/CreateCustomVoice"

	payload := strings.NewReader("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"clientRequestId\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"consentConfirmed\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"<file1>\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"gender\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"ignoreSoftWarnings\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"source\"\r\n\r\n\r\n-----011000010111000001101001--\r\n")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("X-API-KEY", "<string>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api//api/public/voice/CreateCustomVoice")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["X-API-KEY"] = '<string>'
request.body = "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"clientRequestId\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"consentConfirmed\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"<file1>\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"gender\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"ignoreSoftWarnings\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"source\"\r\n\r\n\r\n-----011000010111000001101001--\r\n"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api//api/public/voice/CreateCustomVoice")
  .header("X-API-KEY", "<string>")
  .body("-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"clientRequestId\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"consentConfirmed\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"<file1>\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"gender\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"ignoreSoftWarnings\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"source\"\r\n\r\n\r\n-----011000010111000001101001--\r\n")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api//api/public/voice/CreateCustomVoice', [
  'multipart' => [
    [
        'name' => 'file',
        'filename' => '<file1>',
        'contents' => null
    ]
  ]
  'headers' => [
    'X-API-KEY' => '<string>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api//api/public/voice/CreateCustomVoice");
var request = new RestRequest(Method.POST);
request.AddHeader("X-API-KEY", "<string>");
request.AddParameter("undefined", "-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"clientRequestId\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"consentConfirmed\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"file\"; filename=\"<file1>\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"gender\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"ignoreSoftWarnings\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"name\"\r\n\r\n\r\n-----011000010111000001101001\r\nContent-Disposition: form-data; name=\"source\"\r\n\r\n\r\n-----011000010111000001101001--\r\n", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = ["X-API-KEY": "<string>"]
let parameters = [
  [
    "name": "clientRequestId",
    "value": 
  ],
  [
    "name": "consentConfirmed",
    "value": 
  ],
  [
    "name": "file",
    "fileName": "<file1>"
  ],
  [
    "name": "gender",
    "value": 
  ],
  [
    "name": "ignoreSoftWarnings",
    "value": 
  ],
  [
    "name": "name",
    "value": 
  ],
  [
    "name": "source",
    "value": 
  ]
]

let boundary = "---011000010111000001101001"

var body = ""
var error: NSError? = nil
for param in parameters {
  let paramName = param["name"]!
  body += "--\(boundary)\r\n"
  body += "Content-Disposition:form-data; name=\"\(paramName)\""
  if let filename = param["fileName"] {
    let contentType = param["content-type"]!
    let fileContent = String(contentsOfFile: filename, encoding: String.Encoding.utf8)
    if (error != nil) {
      print(error as Any)
    }
    body += "; filename=\"\(filename)\"\r\n"
    body += "Content-Type: \(contentType)\r\n\r\n"
    body += fileContent
  } else if let paramValue = param["value"] {
    body += "\r\n\r\n\(paramValue)"
  }
}

let request = NSMutableURLRequest(url: NSURL(string: "https://api//api/public/voice/CreateCustomVoice")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```