Errors
Understanding API error responses, WebSocket close codes, and how to handle them
REST API Error Format (HTTP)
When an error occurs on a standard HTTP endpoint, the API returns a JSON response with the following structure:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error description",
"details": {
// Additional context (varies by error type)
}
}
}
WebSocket Error Format (WSS)
WebSockets handle errors differently. Errors can occur during the initial connection handshake, or mid-stream while audio is generating.
- Fatal Errors: The connection is dropped with a specific Close Code (e.g., 4290).
- Mid-Stream Errors: The server sends a flat JSON error frame before closing:
{"error": "Insufficient credits during stream"}
Status Codes & Close Codes
HTTP Status Codes (REST)
| Status | Meaning | Description |
|---|---|---|
200 |
OK | Request succeeded |
400 |
Bad Request | Invalid request parameters or malformed request |
401 |
Unauthorized | Missing or invalid API key |
402 |
Payment Required | Not enough credits to complete the request |
403 |
Forbidden | API key doesn't have permission for this action |
404 |
Not Found | Requested resource doesn't exist |
409 |
Conflict | Request conflicts with current state (e.g., concurrent HTTP request) |
429 |
Too Many Requests | Rate limit exceeded |
500 |
Internal Server Error | Something went wrong on our end |
WebSocket Close Codes (WSS)
| Code | Type | Description |
|---|---|---|
1000 |
Normal Closure | Stream completed successfully. |
3011 |
Generation Error | Audio generation failed on the upstream API. Retry the request. |
4001 |
Missing Auth | No API key provided in the Authorization header during handshake. |
4002 |
Auth Failed | Invalid key, inactive account, insufficient initial credits, or IP restriction. |
4290 |
Concurrent Limit | You already have an active TTS WebSocket connection. Limit is 1 per user. |
Common Error Scenarios
1. Unauthorized / Authentication Failed
Authentication failed due to missing or invalid API key, or IP whitelist restrictions.
Common causes:
- Missing Authorization: Bearer header
- Invalid API key format or key has been revoked
- Connecting from an IP address not in your key's whitelist
2. Insufficient Credits
Your account doesn't have enough credits to complete this request.
- HTTP: Fails immediately before generating audio.
- WebSocket: If you run out of credits mid-stream, the server sends
{"error": "Insufficient credits during stream"}and cleanly closes the connection. Audio generated prior to running out of credits will still be delivered.
3. Concurrency & Rate Limits
Moknah processes one request per user at a time to ensure fair usage.
- HTTP 409: Another request from your account is already being processed. Wait for it to finish.
- HTTP 429: You exceeded the 60 Requests Per Minute (RPM) limit.
- WSS 4290: You attempted to open a second WebSocket connection while your first one was still active.
4. Invalid Request / Bad Data
The request parameters are invalid, or you exceeded text chunk limits.
- HTTP: Missing required fields (text, voice_id).
- WebSocket: Sending a text chunk larger than 2,000 characters will result in
{"error": "Chunk too large. Maximum 2000 characters per chunk."}.
Handling Errors in Code
import requests
import time
def generate_speech(text, voice_id, api_key):
url = "https://moknah.io/api/v1/tts/generate"
headers = {"Authorization": f"Bearer {api_key}"}
response = requests.post(url, json={"text": text, "voice_id": voice_id}, headers=headers)
if response.status_code == 200:
return response.content # Audio bytes
elif response.status_code == 402:
raise Exception("Insufficient credits")
elif response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 5))
time.sleep(retry_after)
return generate_speech(text, voice_id, api_key) # Retry
elif response.status_code == 409:
time.sleep(5)
return generate_speech(text, voice_id, api_key) # Retry
else:
raise Exception(f"API error: {response.status_code}")
async function generateSpeech(text, voiceId, apiKey) {
const response = await fetch("https://moknah.io/api/v1/tts/generate", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({ text: text, voice_id: voiceId })
});
if (response.ok) return await response.blob();
if (response.status === 429) {
const retryAfter = response.headers.get("Retry-After") || 5;
await new Promise(r => setTimeout(r, retryAfter * 1000));
return generateSpeech(text, voiceId, apiKey);
}
if (response.status === 409) {
await new Promise(r => setTimeout(r, 5000));
return generateSpeech(text, voiceId, apiKey);
}
const error = await response.json();
throw new Error(error.error?.message || "API Error");
}
const ws = new WebSocket('wss://moknah.io/api/v1/api/v1/tts/ws/', {
headers: { "Authorization": `Bearer ${apiKey}` }
});
// Handle Mid-Stream JSON Errors
ws.on('message', (data, isBinary) => {
if (!isBinary) {
const msg = JSON.parse(data.toString());
if (msg.error) {
console.error("Stream Error:", msg.error);
// e.g., "Insufficient credits during stream"
}
}
});
// Handle Fatal Close Codes
ws.on('close', (code, reason) => {
switch(code) {
case 1000:
console.log("Stream finished successfully.");
break;
case 4001:
case 4002:
console.error("Authentication failed. Check your API key.");
break;
case 4290:
console.error("Concurrency limit reached. Close other active streams.");
// Wait a few seconds before attempting to reconnect
setTimeout(reconnect, 5000);
break;
case 3011:
console.error("Upstream generation failed. Please try again.");
break;
default:
console.error(`Disconnected with code ${code}`);
}
});
For 429 (HTTP), 409 (HTTP), and 4290 (WSS) errors, implement exponential backoff: wait 1s, then 2s, then 4s, etc. This prevents overwhelming the API and guarantees your request will succeed once the lock clears.
For API-related questions or issues, contact us at api@moknah.io.