message.comDevelopers

Voice profiles Planned

Clone your brand voice from a 15-second audio sample and use it for AI-driven phone interactions: Smart Hold, AI Receptionist, automated confirmations, and IVR prompts. Voice profiles are workspace-scoped and can be attached per phone number.

This endpoint is on the roadmap (Phase 1, alongside the rest of the AI phone stack). The API surface may change before launch. Watch the changelog for the live announcement.

Create a voice profile

POST/api/v1/voice-profilesAuth: Bearer

Uploads a 15-second sample as multipart form-data. The clone runs server-side through our neural voice synthesis pipeline and is typically ready within 30 seconds. Returns the profile record with status: pending initially; poll or subscribe for the voice_profile:ready event.

Form fields

FieldTypeDescription
displayNamerequiredstringInternal label. Shown in the dashboard voice picker.
personalityTagrequiredenumOne of warm, professional, energetic, calm, custom.
sampleAudiorequiredfile15-second mp3 or wav. We accept 10 to 30 seconds; longer samples are truncated.
Form request
POST /api/v1/voice-profiles
Authorization: Bearer YOUR_WORKSPACE_JWT
Content-Type: multipart/form-data

displayName: Acme Brand Voice
personalityTag: warm
sampleAudio: @./sample-15s.mp3

Code samples

cURL
curl -X POST 'https://app.message.com/api/v1/voice-profiles' \
  -H 'Authorization: Bearer YOUR_WORKSPACE_JWT' \
  -F 'displayName=Acme Brand Voice' \
  -F 'personalityTag=warm' \
  -F 'sampleAudio=@./sample-15s.mp3'
JavaScript
const form = new FormData();
form.append('displayName', 'Acme Brand Voice');
form.append('personalityTag', 'warm');
form.append('sampleAudio', audioBlob, 'sample-15s.mp3');

const res = await fetch('https://app.message.com/api/v1/voice-profiles', {
  method: 'POST',
  headers: { Authorization: 'Bearer ' + token },
  body: form
});
const profile = await res.json();
Python
import requests
files = {"sampleAudio": open("sample-15s.mp3", "rb")}
data = {"displayName": "Acme Brand Voice", "personalityTag": "warm"}
r = requests.post(
    "https://app.message.com/api/v1/voice-profiles",
    headers={"Authorization": f"Bearer {token}"},
    files=files,
    data=data,
)
profile = r.json()
Ruby
require "net/http"
require "uri"
require "json"

uri = URI("https://app.message.com/api/v1/voice-profiles")
File.open("sample-15s.mp3") do |sample|
  req = Net::HTTP::Post.new(uri)
  req["Authorization"] = "Bearer #{token}"
  req.set_form([
    ["displayName", "Acme Brand Voice"],
    ["personalityTag", "warm"],
    ["sampleAudio", sample]
  ], "multipart/form-data")
  res = Net::HTTP.start(uri.host, uri.port, use_ssl: true) { |http| http.request(req) }
  @profile = JSON.parse(res.body)
end
PHP
<?php
$file = new CURLFile("sample-15s.mp3", "audio/mpeg", "sample-15s.mp3");
$ch = curl_init("https://app.message.com/api/v1/voice-profiles");
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_HTTPHEADER => ["Authorization: Bearer $token"],
  CURLOPT_POSTFIELDS => [
    "displayName" => "Acme Brand Voice",
    "personalityTag" => "warm",
    "sampleAudio" => $file
  ],
  CURLOPT_RETURNTRANSFER => true,
]);
$profile = json_decode(curl_exec($ch), true);

List profiles

GET/api/v1/voice-profilesAuth: Bearer

Cursor-paginated list. Includes both ready and pending profiles.

Get one profile

GET/api/v1/voice-profiles/:idAuth: Bearer
200 OK
{
  "id": "uuid",
  "displayName": "Acme Brand Voice",
  "personalityTag": "warm",
  "providerVoiceId": "fish_xyz_abc",
  "sampleAudioUrl": "https://cdn.message.com/...mp3",
  "status": "ready",
  "createdAt": "2026-05-13T15:30:00Z"
}

Preview audio

POST/api/v1/voice-profiles/:id/previewAuth: Bearer

Synthesizes a short phrase in the cloned voice. Pass text (up to 250 chars). Returns a signed mp3 URL valid for 15 minutes. Use this to QA before wiring the profile to a phone number.

Delete a profile

DELETE/api/v1/voice-profiles/:idAuth: Bearer

Removes the profile. Phone numbers referencing it fall back to the workspace default voice. Sample audio is purged from object storage; the upstream voice resource is released.

Attaching to a phone number

Once status: ready, PATCH the phone-number record with voiceProfileId:

PATCH /phone-numbers/:id body
{
  "voiceProfileId": "uuid-of-profile",
  "expectedVersion": 2
}

Use policy

Voice cloning is regulated in some jurisdictions. By using this API you assert that you have written consent from the speaker whose voice you cloned. We require workspace owners to confirm this on first use; abuse triggers profile revocation under our Acceptable Use Policy. See Acceptable use.

Errors

CodeWhen
400 invalid_audioSample is too short, too long, or in an unsupported format.
402 plan_limitWorkspace plan does not include voice cloning.
403 forbiddenAgent role is not admin.
409 conflictProfile with this display name already exists.
422 still_processingPreview attempted before status: ready.

Common pitfalls

  • Recording in a noisy room. Background hiss degrades the clone. Use a quiet space and a phone or USB mic.
  • Sample too short. Below 10 seconds the clone is generic. 15 seconds of natural speech is the sweet spot.
  • Voice consent. Cloning a public figure or a competitor's voice without consent will get the profile revoked.