Errors
Understanding API error responses and how to handle them
Error Response Format
When an error occurs, 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)
}
}
}
HTTP Status Codes
| 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 request) |
429 |
Too Many Requests | Rate limit exceeded |
500 |
Internal Server Error | Something went wrong on our end |
Error Codes
INVALID_REQUEST
The request parameters are invalid or missing required fields.
{
"error": {
"code": "INVALID_REQUEST",
"message": "Invalid request parameters",
"details": {
"text": ["This field is required."],
"voice_id": ["Invalid voice ID."]
}
}
}
Common causes:
- Missing required fields (
text,voice_id) - Invalid field types (e.g., string instead of number)
- Text exceeds maximum character limit
- Invalid voice ID format
UNAUTHORIZED
Authentication failed due to missing or invalid API key.
{
"error": {
"code": "UNAUTHORIZED",
"message": "Invalid or missing API key"
}
}
Common causes:
- Missing
Authorizationheader - Invalid API key format
- API key has been revoked or deleted
- API key has expired
INSUFFICIENT_CREDITS
Your account doesn't have enough credits to complete this request.
{
"error": {
"code": "INSUFFICIENT_CREDITS",
"message": "You do not have enough credits to generate speech.",
"details": {
"credits_remaining": 50,
"credits_required": 150
}
}
}
Response Headers:
| Header | Description |
|---|---|
Moknah-Credits-Remaining |
Credits left in your account |
Moknah-Credits-Used |
Credits required for this request |
Solution: Purchase more credits or reduce the request size (shorter text).
FORBIDDEN
Your API key doesn't have permission to access this resource.
{
"error": {
"code": "FORBIDDEN",
"message": "You don't have permission to access this voice"
}
}
Common causes:
- Trying to use a voice you don't have access to
- API key permissions are restricted
- Accessing a feature not included in your plan
NOT_FOUND
The requested resource doesn't exist.
{
"error": {
"code": "NOT_FOUND",
"message": "Voice not found",
"details": {
"voice_id": "abc123"
}
}
}
Common causes:
- Invalid voice ID
- Resource has been deleted
- Typo in the endpoint URL
CONCURRENT_REQUEST
Another request from your account is already being processed. Moknah processes one request per user at a time.
{
"error": {
"code": "CONCURRENT_REQUEST",
"message": "Another request is already in progress. Please wait for it to complete."
}
}
Solution: Wait for the current request to complete before sending another. Implement a queue system on your end if you need to process multiple requests.
RATE_LIMIT_EXCEEDED
You've exceeded the rate limit for API requests.
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Contact sales@moknah.io to increase limits.",
"details": {
"credits_remaining": 0,
"reset_after": 45
}
}
}
Response Headers:
| Header | Description |
|---|---|
Retry-After |
Seconds until you can retry |
RateLimit-Reset |
Unix timestamp when the rate limit resets |
Solution: Wait for the reset period, then retry. Implement exponential backoff in your client. See Rate Limits for more details.
INTERNAL_ERROR
An unexpected error occurred on our servers.
{
"error": {
"code": "INTERNAL_ERROR",
"message": "An unexpected error occurred. Please try again later."
}
}
Solution: Retry the request after a short delay. If the problem persists, contact support@moknah.io.
Handling Errors
Here's an example of how to handle errors in your 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}",
"Content-Type": "application/json"
}
payload = {
"text": text,
"voice_id": voice_id
}
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 200:
return response.content # Audio bytes
elif response.status_code == 400:
error = response.json()["error"]
raise ValueError(f"Invalid request: {error['details']}")
elif response.status_code == 401:
raise PermissionError("Invalid API key")
elif response.status_code == 402:
error = response.json()["error"]
raise Exception(f"Insufficient credits: {error['details']['credits_remaining']} remaining")
elif response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
print(f"Rate limited. Retrying in {retry_after} seconds...")
time.sleep(retry_after)
return generate_speech(text, voice_id, api_key) # Retry
elif response.status_code == 409:
print("Concurrent request in progress. Waiting...")
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 url = "https://moknah.io/api/v1/tts/generate";
const response = await fetch(url, {
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(); // Audio blob
}
const error = await response.json();
switch (response.status) {
case 400:
throw new Error(`Invalid request: ${JSON.stringify(error.error.details)}`);
case 401:
throw new Error("Invalid API key");
case 402:
throw new Error(`Insufficient credits: ${error.error.details.credits_remaining} remaining`);
case 429:
const retryAfter = response.headers.get("Retry-After") || 60;
console.log(`Rate limited.Retrying in ${retryAfter}s...`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
return generateSpeech(text, voiceId, apiKey); // Retry
case 409:
console.log("Concurrent request in progress. Waiting...");
await new Promise(r => setTimeout(r, 5000));
return generateSpeech(text, voiceId, apiKey); // Retry
default:
throw new Error(`API error: ${response.status}`);
}
}
# Check response status and handle errors
response=$(curl -s -w "\n%{http_code}" \
-X POST "https://moknah.io/api/v1/tts/generate" \
-H "Authorization: Bearer your_api_key" \
-H "Content-Type: application/json" \
-d '{"text": "Hello world", "voice_id": "voice_123"}')
# Extract status code (last line)
http_code=$(echo "$response" | tail -n1)
# Extract body (all but last line)
body=$(echo "$response" | sed '$d')
case $http_code in
200)
echo "Success!"
;;
400)
echo "Invalid request: $body"
;;
401)
echo "Invalid API key"
;;
402)
echo "Insufficient credits"
;;
429)
echo "Rate limited. Check Retry-After header"
;;
409)
echo "Concurrent request in progress"
;;
*)
echo "Error: $http_code - $body"
;;
esac
For 429 and 5xx errors, implement exponential backoff: wait 1s, then 2s, then 4s, etc. This prevents overwhelming the API during outages.
For API-related questions or issues, contact us at api@moknah.io.