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.
{
"error": "Rate limit exceeded",
"error_code": "RATE_LIMITED",
"code": 429
}| Field | Type | Description |
|---|---|---|
error | string | Safe client-facing message. Do not parse this field for program logic. |
error_code | string | Stable machine-readable error category. |
code | integer | HTTP-style status code for the error. |
context_id | string | Multi-context identifier; present only on context-scoped /ws/tts/multi errors. |
request_id | string | Correlation 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 code | error_code | Message | Meaning |
|---|---|---|---|
400 | VALIDATION_ERROR | <field>: <reason> on HTTP, Invalid request on WebSocket | The request payload or WebSocket message is malformed or invalid. The HTTP message names the offending field. |
400 | VALIDATION_ERROR | model_id must be one of [...], got '...' | Unknown model_id. |
400 | VALIDATION_ERROR | (parser message) | A <prosody rate> tag is malformed. |
400 | VALIDATION_ERROR | dictionary_ids requires a project: supply project_id / Unknown dictionary_ids for this project: [...] | The dictionary_ids selection is invalid. |
400 | MISSING_VOICE_ID | voice_id is required | No 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. |
400 | VALIDATION_ERROR | Unsupported audio format: ... | Uploaded reference audio uses an unsupported file format. |
400 | VALIDATION_ERROR | Invalid voice metadata | Voice creation metadata could not be parsed or validated. |
400 | SPEAKER_CONSENT_REQUIRED | speaker_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. |
400 | VALIDATION_ERROR | Voice has no reference audio. Upload at least one reference first. | Sample generation for a voice without references. |
401 | UNAUTHORIZED | e.g. Invalid API key | The API key is missing, invalid, or not accepted for this request. |
401 | MFA_REQUIRED | Two-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. |
402 | INSUFFICIENT_CREDITS | Insufficient credits | The organization does not have enough credits for the request. See pricing. Organization owners and admins get one email when the credits run out. |
403 | UNAUTHORIZED | Forbidden | The API key is valid, but it cannot access the requested resource (including a dictionary_ids selection in a project you cannot access). |
403 | UNAUTHORIZED | API key must be associated with an organization and user / Caller is not authorized for a voice project | Voice management needs a key tied to an organization, a user, and a project. |
403 | PERMISSION_DENIED | This API key does not have the <resource>:<level> permission | The API key is valid but its permissions do not allow this endpoint. |
403 | ORG_MFA_REQUIRED | Organization N requires two-factor authentication | The organization requires two-factor authentication and the dashboard session token has not completed the 2FA step. API keys are not affected. |
404 | NOT_FOUND | Voice not found | The requested voice does not exist or is not visible to the caller. |
404 | NOT_FOUND | Dictionary not found | The requested dictionary does not exist or is not visible to the caller. |
404 | NOT_FOUND | Entry not found | The requested dictionary entry does not exist or is not visible to the caller. |
409 | SPEAKER_CONSENT_MISSING | This 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. |
409 | VALIDATION_ERROR | A dictionary named "..." already exists in this project / An entry for "..." already exists in this dictionary | The dictionary name or entry word is already taken in its scope. |
404 | NOT_FOUND | Reference not found | The requested voice reference does not exist or is not visible to the caller. |
413 | VALIDATION_ERROR | Request of N characters exceeds the limit of M | The text is longer than the model or your plan allows for one request. |
413 | VALIDATION_ERROR | File 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. |
429 | RATE_LIMITED | Rate limit exceeded | The organization exceeded its rate or concurrency limit (plan-dependent, see pricing). Retry-After when known. |
429 | TOO_MANY_CONTEXTS | Too 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. |
500 | INTERNAL_ERROR | Audio generation failed | The generation request failed before usable audio could be returned. |
500 | INTERNAL_ERROR | Sample generation failed | Voice sample generation failed. |
500 | INTERNAL_ERROR | Dictionary selection is unavailable on this server | This deployment cannot resolve dictionary_ids. |
501 | VALIDATION_ERROR | Voice management is not available | The deployment does not provide voice-management operations. |
503 | INTERNAL_ERROR | Voice lookup temporarily unavailable | Synthesis could not resolve the voice. Retry-After: 5. |
503 | INTERNAL_ERROR | voice catalog temporarily unavailable | The voice catalog dependency or management surface is unavailable. |
503 | INTERNAL_ERROR | dictionary management unavailable | The deployment does not provide dictionary management. |
503 | MODEL_UNAVAILABLE | The 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 code | Related error_code | Meaning |
|---|---|---|
4000 | any other, e.g. VALIDATION_ERROR | The request failed for a reason not listed below. The preceding error frame says why. |
4001 | UNAUTHORIZED, PERMISSION_DENIED | The API key is not accepted for this connection, or its permissions do not allow it. |
4003 | INSUFFICIENT_CREDITS | The organization ran out of credits. |
4029 | RATE_LIMITED | The organization exceeded its rate limit. |
4500 | MODEL_UNAVAILABLE | The 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.
{
"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)try {
await client.generate({ text: 'Hallo', voiceId });
} catch (err) {
console.log((err as KugelAudioError).requestId);
}try {
client.tts().generate(request);
} catch (KugelAudioException e) {
System.out.println(e.getRequestId());
}Handling errors
Use error_code and code for application logic. Treat error as display
text only.
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_codefor 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:
4bf92f3577b34da6a3ce929d0e0e4736You 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.