Skip to content
Lumean API
POST/voices/fishaudio/clones

Create Fish Audio Voice Clone

Clones a voice from a sample recording. The sample goes base64-encoded (a data URI is fine too) into the reference_audio field of the JSON body, up to 20 MB; consent_biometric: true (consent to biometric data processing) and voice_owner (self or third_party) are recorded in the consent journal; the former consent_confirmed: true is still accepted instead until consents become mandatory.

Cloning is asynchronous. The 201 response comes back at once with the clone in pending (or straight away in failed with error.reason: dispatch_failed if the queue is unavailable — then retry via POST /voices/fishaudio/clones/{voiceClone}/retry). After that it goes pending → cloning → ready (or failed). Progress arrives as the voice_clone.updated WebSocket event on private-fishaudio-voice-clones.{userId}; without WebSocket, poll GET /voices/fishaudio/clones.

Checks, in order: access to the service (404 service_not_found), subscription (402 no_subscription) — a live subscription that includes Fish Audio is required only if the service is closed to paying from the balance; otherwise cloning works without one — and the clone cap (422 voice_clone_quota_exceeded, the max field holds the cap): 2 clones without a subscription (the fallback cap), the plan's cap with one. The rules are the same as Cartesia's; the clone cap is counted separately. An exhausted token quota does not block cloning.

Required key permission: voices.write.

On this page(3)

Request body

application/jsonrequired

A new Fish Audio voice clone: a base64 sample recording plus consent to use the voice. Accepted as application/json.

Sample as plain base64
{
  "display_name": "My voice",
  "reference_audio": "UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YQAAAAA=",
  "language": "ru",
  "description": "Audiobook narrator",
  "consent_biometric": true,
  "voice_owner": "self"
}

Fields

display_namerequired

Clone name — visible only to you, up to 128 characters.

≤ 128 chars
string
Example Мой голос
reference_audiorequired

The voice sample in base64; a data URI (data:audio/wav;base64,…) works too. Up to 20 MB once decoded; any common audio format (WAV, MP3 and the like). It should be clean speech by a single person, without music or noise.

string
Example UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAIA+AAACABAAZGF0YQAAAAA=
languageoptional

A reference label for the clone, a two-letter ISO 639-1 code (ru, en, …). Fish Audio voices have no language of their own and the label is not sent to the provider.

nullablepattern: ^[a-z]{2}$
string
Example ru
descriptionoptional

Your description of the clone, up to 500 characters.

nullable≤ 500 chars
string
voice_owneroptional

Whose voice is in the sample: self — yours, third_party — someone else's, and you hold their consent.

nullable
Valuesselfthird_party
string
Example self

Possible responses

201

Created. data holds the new object in full.

Body example
{
  "success": true,
  "message": "Success",
  "data": {
    "id": "01a10e08-27fe-716f-b428-6bc739602ca7",
    "provider": "fishaudio",
    "display_name": "My voice",
    "language": "ru",
    "description": null,
    "status": "pending",
    "voice_id": null,
    "attempts": 1,
    "error": null,
    "created_at": "2026-10-14T09:15:00+00:00",
    "completed_at": null,
    "failed_at": null,
    "banned_at": null,
    "ban_reason": null
  }
}
Response fields
successrequired

Success flag. Branch on the HTTP status code instead: any 2xx response carries true here, while an error comes back in a different shape that need not contain this field at all.

boolean
messagerequired

A human-readable note in the account language. Fine to show to a user; do not parse it — the wording changes without notice and is not an error code.

string
datarequired

The new clone — in pending (or straight away in failed with error.reason: dispatch_failed if the queue is unavailable).

FishAudioVoiceCloneFishAudioVoiceClone
idrequired

Clone UUID. Use it in /voices/fishaudio/clones/{voiceClone}/… paths; it does NOT go into a template — voice_id does.

string
providerrequired

Clone provider — always fishaudio.

Valuesfishaudio
string
display_namerequired

Clone name given at creation.

string
languagerequired

Reference language label (ISO 639-1), or null if none was sent. It does not affect cloning.

string
descriptionrequired

Your description of the clone, or null.

string
statusrequired

Status: pending → cloning → ready or failed; archived — retired by the owner; banned — blocked by a moderator. Only ready can be used for narration.

string
voice_idrequired

The Fish Audio voice id (a 32-character hex string, not a UUID) — the value for tts_settings.voice_id of a template with service_key: fishaudio. Present only for ready, otherwise null.

string
attemptsrequired

Cloning attempt number: 1 on creation, +1 for every retry.

integer
errorrequired

Why the last attempt failed; null while there has been no failure.

nullable
object
reasonrequired

Machine-readable failure code: processing_error, max_retries, network, timeout, dispatch_failed, unbanned (the clone was unbanned but had no voice). Retry with POST /voices/fishaudio/clones/{voiceClone}/retry; if processing_error keeps coming back, use a different sample.

string
messagerequired

Human-readable explanation of the failure, or null. Branch on reason, not on this text.

string
created_atrequired

When the clone was created.

string
completed_atrequired

When the clone became ready; null until then.

string
failed_atrequired

When the last attempt failed; null if it has not.

string
banned_atrequired

When a moderator banned the clone (status = banned); null if it is not banned.

string
ban_reasonrequired

Reason for the ban given by the moderator, or null. A banned clone can be neither used nor archived.

string
401

The key is missing, unknown, disabled or expired.

Analyse this response
Body example
{
  "success": false,
  "message": "API key is missing. Provide it in the X-API-KEY header.",
  "data": null
}
Response fields
successrequired

Always false in error responses.

boolean
messagerequired

Error text in the key's language (set through the X-Locale header).

string
datarequired

Always null when the request is rejected before processing.

nullable
null
402

No live subscription that includes Fish Audio (reason: no_subscription). Returned only if the service is closed to paying from the balance.

Analyse this response
Body example
{
  "success": false,
  "message": "An active subscription is required.",
  "reason": "no_subscription",
  "error_code": "no_subscription"
}
Response fields
successrequired

Always false in error responses.

boolean
messagerequired

The reason for the rejection in words, in the account language. Meant for display; do not branch on it, the wording changes.

string
reasonrequired

Machine-readable reason of a business refusal (for example, input_expired, insufficient_balance). Branch on it, not on the message text.

string
error_coderequired

The same machine-readable code as in reason — a second name for the field. The values always match.

string
403

The key lacks the voices.write permission.

Analyse this response
Body example
{
  "success": false,
  "message": "This API key does not have permission to perform this action."
}
Response fields
successrequired

Always false in error responses.

boolean
messagerequired

Error text in the key's language (set through the X-Locale header).

string
404

The Fish Audio service is closed to the key owner (reason: service_not_found).

Analyse this response
Body example
{
  "success": false,
  "message": "Service not found.",
  "reason": "service_not_found",
  "error_code": "service_not_found"
}
Response fields
successrequired

Always false in error responses.

boolean
messagerequired

The reason for the rejection in words, in the account language. Meant for display; do not branch on it, the wording changes.

string
reasonrequired

Machine-readable reason of a business refusal (for example, input_expired, insufficient_balance). Branch on it, not on the message text.

string
error_coderequired

The same machine-readable code as in reason — a second name for the field. The values always match.

string
422

Two different bodies share this code. Validation error: {success: false, message, errors: {field: [text]}} with no reason (a required field is missing, no consent_biometric/consent_confirmed consent, language is not a two-letter ISO 639-1 code, the sample is not valid base64, is larger than 20 MB or is not WAV/MP3/M4A/AAC/OGG/WebM/FLAC). Clone cap of the plan: {success: false, message, reason: "voice_clone_quota_exceeded", error_code, max} — max is the cap; archive a clone (DELETE) or move to a higher plan. Tell them apart by the presence of reason.

Analyse this response
Body example
{
  "success": false,
  "message": "Validation error",
  "errors": {
    "consent_confirmed": [
      "The consent confirmed field must be accepted."
    ],
    "reference_audio": [
      "The file is too large: 20 MB at most."
    ]
  }
}
Response fields
messagerequired

Errors overview.

string
errorsrequired

A detailed description of each field that failed validation.

object
*optional
arbitrary keys
array<string>
429

The clone rate limit is exceeded (reason: rate_limit_exceeded): by default 6 attempts per minute and 60 per day per user, shared with the web cabinet and LumVoice cloning. Every attempt counts, including one rejected by validation. Wait retry_after seconds (the Retry-After header).

Analyse this response
Body example
{
  "success": false,
  "message": "Too many attempts. Please wait a moment and try again.",
  "reason": "rate_limit_exceeded",
  "error_code": "rate_limit_exceeded",
  "retry_after": 30
}
Response fields
successrequired

Always false in error responses.

boolean
messagerequired

Error text in the key's language (set through the X-Locale header).

string
reasonrequired

Machine-readable failure reason — branch on this, not on the message text.

string
error_coderequired

The same machine-readable code as in reason — a second name for the field. The values always match; branch on either.

string
retry_afteroptional

How many seconds to wait before retrying (the same value is in the Retry-After header).

nullable
integer

Feed this page to an AI

Open .mdllm.md

spec 0.0.1