Skip to content
KugelAudioDocs

Error Codes

Error codes of the KugelAudio text-to-speech API: HTTP and WebSocket error bodies, close codes, refused upgrades, request IDs and how to handle each.

On this page

KugelAudio TTS endpoints return the same error shape for HTTP responses and WebSocket error frames.

JSON
{
  "error": "Rate limit exceeded",
  "error_code": "RATE_LIMITED",
  "code": 429
}
FieldTypeDescription
errorstringSafe client-facing message. Do not parse this field for program logic.
error_codestringStable machine-readable error category.
codeintegerHTTP-style status code for the error.
context_idstringMulti-context identifier; present only on context-scoped /ws/tts/multi errors.
request_idstringCorrelation ID for this call, on WebSocket error frames. HTTP responses, including a rejected WebSocket handshake, carry it as the x-request-id header instead. See Request IDs.

HTTP and WebSocket payloads

Rows marked Retry-After also carry that response header on HTTP.

Status / payload codeerror_codeMessageMeaning
400VALIDATION_ERROR<field>: <reason> on HTTP, Invalid request on WebSocketThe request payload or WebSocket message is malformed or invalid. The HTTP message names the offending field.
400VALIDATION_ERRORmodel_id must be one of [...], got '...'Unknown model_id.
400VALIDATION_ERROR(parser message)A <prosody rate> tag is malformed.
400VALIDATION_ERRORdictionary_ids requires a project: supply project_id / Unknown dictionary_ids for this project: [...]The dictionary_ids selection is invalid.
400MISSING_VOICE_IDvoice_id is requiredNo voice_id was supplied for synthesis. There is no default voice: set a voice_id. Distinct from the 404 "voice doesn't exist" case below.
400VALIDATION_ERRORUnsupported audio format: ...Uploaded reference audio uses an unsupported file format.
400VALIDATION_ERRORInvalid voice metadataVoice creation metadata could not be parsed or validated.
400SPEAKER_CONSENT_REQUIREDspeaker_consent must be true: ...A voice was created or reference audio was uploaded without speaker_consent set to true. Nothing was stored. See speaker consent.
400VALIDATION_ERRORVoice has no reference audio. Upload at least one reference first.Sample generation for a voice without references.
401UNAUTHORIZEDe.g. Invalid API keyThe API key is missing, invalid, or not accepted for this request.
401MFA_REQUIREDTwo-factor authentication required: ...A dashboard session token was used for an account with two-factor authentication, but the session has not completed the 2FA step. Sign in again and enter your authenticator code. API keys are not affected. See Account security.
402INSUFFICIENT_CREDITSInsufficient creditsThe organization does not have enough credits for the request. See pricing. Organization owners and admins get one email when the credits run out.
403UNAUTHORIZEDForbiddenThe API key is valid, but it cannot access the requested resource (including a dictionary_ids selection in a project you cannot access).
403UNAUTHORIZEDAPI key must be associated with an organization and user / Caller is not authorized for a voice projectVoice management needs a key tied to an organization, a user, and a project.
403PERMISSION_DENIEDThis API key does not have the <resource>:<level> permissionThe API key is valid but its permissions do not allow this endpoint.
403ORG_MFA_REQUIREDOrganization N requires two-factor authenticationThe organization requires two-factor authentication and the dashboard session token has not completed the 2FA step. API keys are not affected.
404NOT_FOUNDVoice not foundThe requested voice does not exist or is not visible to the caller.
404NOT_FOUNDDictionary not foundThe requested dictionary does not exist or is not visible to the caller.
404NOT_FOUNDEntry not foundThe requested dictionary entry does not exist or is not visible to the caller.
409SPEAKER_CONSENT_MISSINGThis voice has no recorded speaker consent and cannot be published. ...The voice to publish has no recorded speaker consent. Add its reference audio again with speaker_consent set to true.
409VALIDATION_ERRORA dictionary named "..." already exists in this project / An entry for "..." already exists in this dictionaryThe dictionary name or entry word is already taken in its scope.
404NOT_FOUNDReference not foundThe requested voice reference does not exist or is not visible to the caller.
413VALIDATION_ERRORRequest of N characters exceeds the limit of MThe text is longer than the model or your plan allows for one request.
413VALIDATION_ERRORFile too large (max 50 MB) / File too large (max 10 MB)A voice-reference upload exceeds 50 MB, or an avatar upload exceeds 10 MB.
429RATE_LIMITEDRate limit exceededThe organization exceeded its rate or concurrency limit (plan-dependent, see pricing). Retry-After when known.
429TOO_MANY_CONTEXTSToo many concurrent contexts (max 20); close an existing context before opening a new one./ws/tts/multi already has 20 live contexts. The frame also carries context_id.
500INTERNAL_ERRORAudio generation failedThe generation request failed before usable audio could be returned.
500INTERNAL_ERRORSample generation failedVoice sample generation failed.
500INTERNAL_ERRORDictionary selection is unavailable on this serverThis deployment cannot resolve dictionary_ids.
501VALIDATION_ERRORVoice management is not availableThe deployment does not provide voice-management operations.
503INTERNAL_ERRORVoice lookup temporarily unavailableSynthesis could not resolve the voice. Retry-After: 5.
503INTERNAL_ERRORvoice catalog temporarily unavailableThe voice catalog dependency or management surface is unavailable.
503INTERNAL_ERRORdictionary management unavailableThe deployment does not provide dictionary management.
503MODEL_UNAVAILABLEThe requested model is temporarily unavailable. Please try again shortly.The selected model is temporarily unavailable. Retry-After: 30.

Refused WebSocket upgrades

/ws/tts, /ws/tts/stream, /ws/tts/multi and /v1/audio/enhance/stream authenticate before accepting the upgrade. A refused upgrade is an HTTP response, not a WebSocket close code: the client sees the HTTP status (for example 401 for a bad key, 429 when the organization's connection limit is reached, with Retry-After when known) and the JSON error envelope above as the body. Most WebSocket libraries expose this as a handshake or "invalid status" error.

WebSocket close codes

After the upgrade is accepted, errors arrive as JSON error frames. On the text-to-speech sockets most leave the socket open for the next turn. An error that closes the socket uses the close code for its error_code; the close code is separate from the JSON code.

WebSocket close codeRelated error_codeMeaning
4000any other, e.g. VALIDATION_ERRORThe request failed for a reason not listed below. The preceding error frame says why.
4001UNAUTHORIZED, PERMISSION_DENIEDThe API key is not accepted for this connection, or its permissions do not allow it.
4003INSUFFICIENT_CREDITSThe organization ran out of credits.
4029RATE_LIMITEDThe organization exceeded its rate limit.
4500MODEL_UNAVAILABLEThe model is temporarily unavailable or at capacity. Retry shortly.
1011(none)The server failed while handling the connection.
1012(none)The replica is restarting during a rolling deploy. Reconnect and resend the current turn.
1013(none)The replica accepted the connection only to report that it is draining. Retry against the load balancer.

Close codes 1012 and 1013 are not failures of your request. During a rolling deploy each replica finishes the turn it is generating, then closes idle sockets with 1012; a socket that was mid-turn receives its session_closed event first. Audio you already received is complete up to the last chunk. A handshake against a draining replica is accepted and immediately closed with 1013, so browser and server-side WebSocket clients receive the same portable restart signal. The SDK retry delay defaults to one second.

The Python and JavaScript SDKs absorb this for you. If no audio for the current turn has been delivered yet, they wait for the restart retry delay, reconnect to another replica, resend the turn (session config and open contexts included) and keep streaming, so a deploy is invisible to your users. Nothing is replayed once audio has started, because that would repeat what the listener already heard, and no turn is replayed twice. In those two cases the SDK raises the retryable ServerRestartingError and you decide what to resend. The JavaScript SDK also offers an onServerRestart callback for observing the silent replays; the Python SDK logs them at INFO on the kugelaudio.streaming logger. The Java SDK does not reconnect or replay: see Reconnecting after a server restart.

Request IDs

Every HTTP response carries an x-request-id header, on success and on error alike. WebSocket frames have no headers, so server-sent error frames carry the same value as a request_id field in the JSON body instead. The ID is minted when the connection opens and stays the same for every error frame on that connection.

A WebSocket connection that is refused before it opens, for example because the API key is invalid or a rate or connection limit was reached, gets an ordinary HTTP error response, and that response carries the x-request-id header too.

JSON
{
  "error": "Rate limit exceeded",
  "error_code": "RATE_LIMITED",
  "code": 429,
  "request_id": "9f2c1b7e4a6d4c8fb0e5a3d719c26f84"
}

Quote this value in a support request: it is what lets us find the matching server-side logs for your exact call.

You can look the call up yourself too: paste the ID into the search on your project's Logs page in the dashboard. Every authenticated speech-generation request is logged, whether it succeeded, failed or was cancelled; a request refused for an invalid API key is not, because it cannot be tied to your organization. On a WebSocket every request sent over one connection shares the connection's ID, so the search returns all of them. The search needs at least 8 characters, so a shorter ID you send yourself cannot be looked up there.

If you already generate correlation IDs, send your own x-request-id request header and we echo it back. It must match [A-Za-z0-9_-] and be at most 64 characters; anything else is replaced with a generated ID rather than reflected.

All three SDKs surface the value on the typed error they raise, whichever of these three places it came from:

try:
    audio = client.generate(text="Hallo", voice_id=voice_id)
except KugelAudioError as exc:
    print(exc.request_id)

Handling errors

Use error_code and code for application logic. Treat error as display text only.

JavaScript
if (message.error_code === "RATE_LIMITED") {
  // Back off and retry later.
}

if (message.error_code === "MODEL_UNAVAILABLE") {
  // The model or cluster is temporarily overloaded.
}

Find a request in your logs

The Logs page of your project in the dashboard lists the text-to-speech requests made in that project over up to the last 14 days. It covers the REST, WebSocket, ElevenLabs-compatible, Cognigy and Vapi endpoints. Each row shows:

  • when the request finished
  • whether it succeeded, failed or was cancelled, with the error_code for failures
  • the endpoint, the model, the voice ID and the API key
  • the audio duration, the character count and the cost
  • a trace ID

To report a problem with a specific request, copy its trace ID and send it to support. With it we can find the exact request. A trace ID is 32 hexadecimal characters, for example:

Text
4bf92f3577b34da6a3ce929d0e0e4736

You can also search the Logs page by trace ID. Requests rejected because the API key was missing or invalid do not appear, since they cannot be attributed to an organization.

Get an email when requests fail

Owners and admins turn on error alerts for a project with the Error alerts switch under Account settings → Email notifications, or with Alerts on the project's Logs page. Turned on, a project emails you when 5 or more requests fail within 5 minutes. Under Configure you can change:

  • how many failed requests trigger an alert, and within which window (1, 5, 15 or 60 minutes)
  • which errors count; by default every failure does
  • which members of your organization get the email

The email names the number of failed requests, breaks them down by error code and endpoint, shows the trace ID of the latest failure and links to the failed requests on the Logs page. A project sends at most one alert email every 30 minutes, however long the errors go on. Error alerts cover text-to-speech requests and are off until you turn them on.