ThunderPhone 2.0 is live.Self-serve, from 2¢/min.Read the announcement

Agents

Custom voices

Request access, clone a voice, track its lifecycle, and use it on an agent.

Custom voices let your organization create a reusable agent voice from a short audio sample. Creation is asynchronous: a new clone starts in queued, moves to processing, and finishes as either ready or failed.

Endpoints

MethodPathDescription
GET/v1/voices/policyCheck access and ThunderPhone quota
POST/v1/voices/quota-requestsRequest access or a larger quota
GET/v1/voices/quota-requests/currentRetrieve the latest quota request
GET/v1/voices/clonesList custom voices
POST/v1/voices/clonesCreate a custom voice
GET/v1/voices/clones/{public_id}Retrieve a custom voice
PATCH/v1/voices/clones/{public_id}Rename a custom voice or set its gender
DELETE/v1/voices/clones/{public_id}Delete a custom voice
GET/v1/voices/clones/{public_id}/previewStream the ready voice's MP3 preview

Custom voice object

{
  "name": "custom:cv_2f6f90b0e9a34ee8b39be7d1",
  "public_id": "cv_2f6f90b0e9a34ee8b39be7d1",
  "display_name": "Support voice",
  "language": "en",
  "gender": "female",
  "status": "ready",
  "failure_reason": "",
  "created_at": "2026-07-30T14:12:08.317Z",
  "sample_url": "https://api.thunderphone.com/v1/voices/clones/cv_2f6f90b0e9a34ee8b39be7d1/preview?sig=..."
}
FieldTypeDescription
namestringAgent voice value in the form custom:<public_id>
public_idstringPublic identifier used in clone endpoints
display_namestringOrganization-facing name, 1–100 characters
languagestringThe clone's single language code
genderstringmale or female. Voices created before gender became required may return an empty string until it is set with PATCH
statusstringqueued, processing, ready, or failed
failure_reasonstringProcessing failure detail; empty unless the clone failed
created_attimestampISO 8601 creation time
sample_urlstring | nullSigned preview URL when a preview is available; otherwise null

Request access and quota

Check policy

Check whether cloning is enabled and how many of your organization's ThunderPhone custom voice slots are in use.

cURL
curl https://api.thunderphone.com/v1/voices/policy \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response (200 OK)
{
  "cloning_enabled": true,
  "max_custom_voices": 5,
  "used": 2,
  "custom_voice_surcharge_cents": 1
}
FieldTypeDescription
cloning_enabledbooleanWhether the organization can create custom voices
max_custom_voicesintegerThunderPhone custom voice limit for the organization
usedintegerNon-deleted custom voices currently using that limit
custom_voice_surcharge_centsintegerAdditional cents per minute when a custom voice is used

Submit a quota request

Use the same flow to request initial access or a larger ThunderPhone limit. Only one request can be pending for an organization at a time.

cURL
curl -X POST https://api.thunderphone.com/v1/voices/quota-requests \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "requested_count": 5,
    "note": "Voices for our support teams"
  }'
FieldTypeRequiredDescription
requested_countintegeryesRequested ThunderPhone custom voice limit; at least 1
notestringnoContext for the request, up to 2,000 characters
Response (201 Created)
{
  "status": "pending",
  "requested_count": 5,
  "created_at": "2026-07-30T13:55:42.104Z"
}
StatusResponseWhen
400{"requested_count":"Must be an integer."}Count is absent or not an integer
400{"requested_count":"Must be at least 1."}Count is less than 1
400{"note":"Must be 2000 characters or fewer."}Note is too long
409{"detail":"A quota request is already pending."}The organization already has a pending request

Check the latest request

cURL
curl https://api.thunderphone.com/v1/voices/quota-requests/current \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response (200 OK)
{
  "status": "approved",
  "requested_count": 5,
  "created_at": "2026-07-30T13:55:42.104Z",
  "resolved_at": "2026-07-30T14:03:19.880Z"
}
FieldTypeDescription
statusstringpending, approved, or denied
requested_countintegerRequested ThunderPhone custom voice limit
created_attimestampISO 8601 submission time
resolved_attimestamp | nullISO 8601 decision time, or null while pending

Returns 404 Not Found when the organization has never submitted a request.

Create a custom voice

Upload one WAV or MP3 file as multipart/form-data. The sample must contain 3–15 seconds of speech and be no larger than 4 MB. Use one language per clone, and only upload a voice you have the rights and consent to clone.

cURL
curl -X POST https://api.thunderphone.com/v1/voices/clones \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -F "audio=@support-voice.wav;type=audio/wav" \
  -F "name=Support voice" \
  -F "language=en" \
  -F "gender=female" \
  -F "consent=true"
FieldTypeRequiredDescription
audiofileyesWAV or MP3, no larger than 4 MB, containing 3–15 seconds of speech
namestringyesDisplay name, 1–100 characters
languagestringyesOne of ar, zh, nl, en, fr, de, he, hi, it, ja, ko, pl, pt, ru, or es
consentbooleanyesMust be exactly true to confirm the right and consent to clone the voice
genderstringyesmale or female. ThunderPhone tells the agent's language models which gender the voice has so replies use the matching first-person forms in gendered languages (Spanish, Russian, Hindi, ...)
Response (202 Accepted)
{
  "name": "custom:cv_2f6f90b0e9a34ee8b39be7d1",
  "public_id": "cv_2f6f90b0e9a34ee8b39be7d1",
  "display_name": "Support voice",
  "language": "en",
  "gender": "female",
  "status": "queued",
  "failure_reason": "",
  "created_at": "2026-07-30T14:12:08.317Z",
  "sample_url": null
}

The 202 Accepted response means the upload was queued, not that the voice is ready. Poll the clone detail until it reaches a terminal status, or subscribe to voice.ready and voice.failed for push notifications.

StatusCode or responseWhen
400{"language":"Unsupported custom voice language."}Language is missing or unsupported
400{"audio":"An audio file is required."}Audio part is missing
400{"audio":"Audio must be a WAV or MP3 file."}Filename extension and MIME type are not an accepted WAV or MP3 combination
400{"audio":"Audio must be 4 MB or smaller."}File exceeds 4 MB
400{"audio":"Audio must not be empty."}File is empty
400{"consent":"Consent must be exactly true to confirm rights to clone this voice."}Consent is not exactly true
400{"name":"Name is required."}Name is empty or absent
400{"name":"Name must be 100 characters or fewer."}Name is too long
400{"gender":"Gender is required and must be male or female."}Gender is missing or unsupported
403cloning_not_enabledCustom voice cloning is not enabled for the organization
422custom_voice_quota_exceededThe organization's ThunderPhone custom voice limit is already in use

The 403 response is:

{
  "code": "cloning_not_enabled",
  "detail": "Custom voice cloning is not enabled. Submit a quota request to request access."
}

The quota response includes current ThunderPhone usage and limit:

{
  "code": "custom_voice_quota_exceeded",
  "detail": "Custom voice quota exceeded.",
  "current": 5,
  "max": 5
}

List and retrieve clones

List all non-deleted custom voices for the organization, newest first:

cURL
curl https://api.thunderphone.com/v1/voices/clones \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"
Response (200 OK)
[
  {
    "name": "custom:cv_2f6f90b0e9a34ee8b39be7d1",
    "public_id": "cv_2f6f90b0e9a34ee8b39be7d1",
    "display_name": "Support voice",
    "language": "en",
    "gender": "female",
    "status": "ready",
    "failure_reason": "",
    "created_at": "2026-07-30T14:12:08.317Z",
    "sample_url": "https://api.thunderphone.com/v1/voices/clones/cv_2f6f90b0e9a34ee8b39be7d1/preview?sig=..."
  }
]

Retrieve one clone with GET /v1/voices/clones/{public_id}. It returns the same custom voice object, or 404 Not Found if the voice does not exist, belongs to another organization, or was deleted.

Rename a clone or set its gender

display_name and gender can be changed; send either or both. The agent-facing name, custom:<public_id>, stays stable. Use gender to backfill clones created before it became required.

cURL
curl -X PATCH \
  https://api.thunderphone.com/v1/voices/clones/cv_2f6f90b0e9a34ee8b39be7d1 \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"display_name":"Billing voice","gender":"female"}'

Returns the updated custom voice object.

StatusResponseWhen
400{"detail":"Only display_name and gender may be changed."}The body is empty or contains any other field
400{"display_name":"Display name must be between 1 and 100 characters."}display_name is present but empty or too long
400{"gender":"Gender must be male or female."}gender is present but unsupported
404Not foundThe clone is unavailable to the organization

Delete a clone

cURL
curl -X DELETE \
  https://api.thunderphone.com/v1/voices/clones/cv_2f6f90b0e9a34ee8b39be7d1 \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY"

Returns 204 No Content. Deletion is soft: the voice disappears from clone and catalog responses immediately and frees one ThunderPhone custom voice slot. A deleted voice can no longer be assigned to an agent.

Play the preview

A ready clone's sample_url is a signed URL to:

GET /v1/voices/clones/{public_id}/preview?sig={signature}

It streams audio/mpeg without an Authorization header. You can also call the endpoint without sig while authenticated:

cURL
curl \
  https://api.thunderphone.com/v1/voices/clones/cv_2f6f90b0e9a34ee8b39be7d1/preview \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  --output preview.mp3

An unsigned, unauthenticated request—or one with an invalid or expired signature—returns 401 Unauthorized. A valid request returns 404 Not Found when the clone is not ready or has no stored preview.

Use a custom voice

Once status is ready, pass the clone's name to an agent's voice or draft_voice field:

cURL
curl -X PATCH https://api.thunderphone.com/v1/agents/42 \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"voice":"custom:cv_2f6f90b0e9a34ee8b39be7d1"}'

The clone must be ready and owned by the agent's organization. A clone from another organization, a non-ready clone, or a deleted clone is rejected.

Ready custom voices also appear at the start of the voices array returned by GET /v1/voices:

{
  "name": "custom:cv_2f6f90b0e9a34ee8b39be7d1",
  "display_name": "Billing voice",
  "description": "",
  "gender": "female",
  "category": "custom",
  "accent": null,
  "languages": ["en"],
  "custom": true,
  "surcharges": {"en": 1},
  "sample_url": "https://api.thunderphone.com/v1/voices/clones/cv_2f6f90b0e9a34ee8b39be7d1/preview?sig=..."
}

The surcharges object is empty when the organization has no custom voice surcharge. You can filter the catalog with ?category=custom, ?languages=en, or ?gender=female.