Errors & Warnings

Every error and warning the Falcon 2 streaming and WebSocket APIs can return, with the cause and the fix.

Overview

The Murf TTS API reports problems in two different ways depending on the transport:

  • HTTP streaming (POST /v1/speech/stream) and synthesis (POST /v1/speech/generate) return a standard HTTP status code.
  • WebSocket streaming (/v1/speech/stream-input) returns JSON error and warning frames on the open connection, and uses WebSocket close codes for connection-level failures.

How to handle errors

Every WebSocket error and warning frame carries a machine-readable code alongside the human-readable text.

  • Branch on error_code and warning_code, never on the prose. The wording can change at any time; the codes are stable.
  • "fatal": true means nothing further is coming for that context. Stop waiting for audio or final and clean up.
  • A warning is not fatal. Something was ignored or substituted, and synthesis continues.
  • trace_id is present on every frame, including audio and final, not just on errors. Quote it when you contact support.

HTTP errors

These apply to POST /v1/speech/stream and POST /v1/speech/generate.

StatusMeaningHow to resolve
400Bad RequestThe request body is malformed or a field is invalid. Check voiceId, model, format and sampleRate against the API reference.
402Expired subscription or character limit exhaustedYour plan’s character quota is used up or the subscription lapsed. Top up or upgrade in the dashboard.
403Invalid or expired token/api-key providedSend a valid key in the api-key header. Note that Murf returns 403, not 401, for authentication failures.
408Request TimeoutThe request took too long to complete or the connection stalled before the body was received. Retry, and raise your client’s timeout for long inputs.
429Too Many RequestsYou exceeded your plan’s concurrency limit. Retry with exponential backoff and cap your in-flight requests. See Rate Limits.
500Internal Server ErrorRetry with exponential backoff. If it persists, contact support with the request details.
503Service UnavailableThe service is temporarily saturated or the region is unavailable. Retry with backoff, or fall back to global.api.murf.ai from a regional host.

WebSocket errors

An error frame looks like this:

{
"error": "Invalid voice_id: totally-fake. The specified voice is not supported for the selected model (FALCON). Note: Falcon-2 and Gen2 each have their own voice catalog. Use GET /v1/speech/voices?model=FALCON to fetch the list of voices supported by this model, or browse the voice library at: https://murf.ai/api/docs/voices-styles/voice-library.",
"error_code": "INVALID_VOICE",
"fatal": true,
"context_id": "turn_1",
"trace_id": "0f8bb0ef-8dcb-4815-8b3a-1e2d3c4b5a69"
}
error_codeCauseHow to resolve
INVALID_VOICEThe requested voiceId does not exist in the Falcon catalog.Use GET /v1/speech/voices?model=FALCON to list valid voices, or browse the Voice Library. Both Gordon and en-US-gordon are accepted.
ACTIVE_CONTEXT_LIMIT_EXCEEDEDYou have more simultaneously active context_ids than your plan’s streaming concurrency allows.Send {"end": true} to close finished contexts, reuse a context_id for sequential turns, or raise your limit. See Rate Limits.
INVALID_JSONThe frame you sent is not valid JSON.Serialize each frame as a single well-formed JSON object. Do not send partial or concatenated payloads.
INTERNAL_ERRORSomething failed on our side.Retry the context. If it repeats, contact support and quote the trace_id from the frame.

WebSocket warnings

Warnings mean something in your frame was ignored or substituted. Synthesis continues, so you will still receive audio.

{
"warning": "min_buffer_size must be an integer between 0 and 1000; keeping the previous value",
"warning_code": "INVALID_BUFFER_SIZE",
"context_id": "turn_1"
}
warning_codeCauseHow to resolve
INVALID_BUFFER_SIZEmin_buffer_size was outside 0–1000 or not a number. The previous value is kept.Send an integer between 0 and 1000. Numeric strings such as "100" are accepted; "abc" is not.
INVALID_BUFFER_DELAYmax_buffer_delay_in_ms was outside 0–1000 or not a number. The previous value is kept.Send an integer between 0 and 1000.
INVALID_PREDICTIVE_CHUNKINGpredictive_chunking was not strict or lenient.Send one of the two accepted values, or omit the field to use strict.
INVALID_FIELD_TYPEA field was sent with the wrong JSON type.Check the field types in the WebSockets API reference.
NO_VOICE_CONFIGText arrived in a context that has no voice_config, so the default voice was used.Send voice_config before the first text frame. For a named context, use the same context_id on both frames; it does not inherit the default context’s configuration.
NO_VOICE_IDA voice_config was sent without a voiceId, so the default voice was used.Include voiceId in voice_config. For a named context, also include its context_id.
TOO_MANY_GENERATION_REQUESTSYou are issuing generation requests faster than your plan allows.Slow down, or batch text into fewer frames. Retry after a short backoff. See Rate Limits.
TEXT_IGNORED_WITH_CLEARA frame contained both clear and text. The text was dropped.Send clear and text as two separate frames.

Connection close codes

Close codeCauseHow to resolve
1008Invalid API key. The HTTP upgrade completes and the socket is then closed with Invalid api key used.Handle the close code, not an HTTP status. Check the api-key query parameter or header.
1000Normal close, including the automatic close after 3 minutes of inactivity.Reconnect when you next have text to send.

An invalid API key on a WebSocket connection is not reported as HTTP 401. The upgrade succeeds first, so clients that only inspect the HTTP response never see the failure. Always handle the close code.

import base64, json
async def read_stream(ws):
audio = bytearray()
while True:
msg = json.loads(await ws.recv())
if msg.get("audio"):
audio += base64.b64decode(msg["audio"])
elif msg.get("error"):
# Branch on the code, never on the prose.
print("error:", msg["error_code"], msg["error"], msg.get("trace_id"))
if msg.get("fatal"):
break
elif msg.get("warning"):
print("warning:", msg["warning_code"], msg["warning"])
elif msg.get("final"):
break
return bytes(audio)

FAQs

The HTTP endpoints return 403 for both invalid and expired keys. There is no 401 response. Over WebSockets the connection is closed with code 1008 instead, because the HTTP upgrade has already completed by the time the key is rejected.

Concurrency is counted by the number of simultaneously active context_ids, not by the number of connections. Closing each turn with {"end": true} frees its slot. See Rate Limits for per-plan numbers.

No. Warnings are informational and synthesis continues with a default or the previously accepted value. Fix the offending field in your next frame.

The trace_id from any frame in the affected context, the context_id, the endpoint host you connected to, and the approximate time of the request.