Skip to content
DashboardGet API Key
Core Guides

Error Handling

Understanding error responses, HTTP status codes, rate limits, and troubleshooting common issues.

The CHAMELAION API uses standard HTTP status codes and returns consistent JSON error responses. This guide covers all error scenarios, rate limiting, and how to handle them in your application.

All errors follow the same structure:

{
"error": "human-readable error message"
}

The error field always contains a descriptive message explaining what went wrong.

StatusMeaningDescription
200SuccessRequest completed successfully
400Bad RequestInvalid request body, missing fields, or wrong format
401UnauthorizedAuthentication failed or missing
404Not FoundThe requested Lip Sync or Video Translate request doesn’t exist
408Request TimeoutVideo Translate upload stalled for more than 30 seconds
412Precondition FailedToken limit reached. Upgrade your plan
413Payload Too LargeVideo Translate request exceeds the size limit
429Too Many RequestsRate limit or Video Translate concurrency limit exceeded
500Internal Server ErrorSomething went wrong on our side

These indicate problems with your request format or content.

Error MessageCause
invalid request bodyJSON is malformed or doesn’t match the schema
content-type must be application/jsonMissing or wrong Content-Type header
inputs is requiredinputs is missing or empty
expected exactly one video inputinputs doesn’t contain exactly one video item
expected exactly one audio inputinputs doesn’t contain exactly one audio item
input type must be "video" or "audio"An input has an unrecognized type value
unable to access media urlCHAMELAION could not download a media URL
concurrency limit reached for your plan: ...Too many unfinished lip sync requests for your plan

Lip Sync file upload (/v1/lipsync/generate-with-media)

Section titled “Lip Sync file upload (/v1/lipsync/generate-with-media)”
Error MessageCause
content-type must be multipart/form-dataWrong content type. Must be multipart/form-data
video file is requiredNo video field in the form data
audio file is requiredNo audio field in the form data
uploaded file is not a valid video inputThe file isn’t sent as video/* (or audio/* for the audio part)
concurrency limit reached for your plan: ...Too many unfinished lip sync requests for your plan

Video Translate checks settings, subtitle syntax, and voice IDs before creating the request. Common errors:

  • target_languages must contain at least one language: target_languages is missing or empty.
  • target_languages must not contain more than 10 languages: Too many target languages.
  • unsupported target language "xx": The code isn’t a supported language.
  • source_language must not be included in target_languages: The source language is also listed as a target.
  • video URL is not accessible: CHAMELAION could not download the video URL.
  • input must be a video file: The URL or upload isn’t a video.
  • <field> is not a valid SRT file: ...: A subtitle file has a syntax error. The message names the field (for example source_srt or target_srt.de) and the line.
  • speaker_voices references unknown or unavailable voice IDs: ...: A voice ID isn’t listed by GET /v1/video-translate/voices.

The API reference lists every validation rule.

import requests
response = requests.post(
"https://api.chamelaion.com/api/v1/lipsync/generate",
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
json={"inputs": []}, # Invalid: empty inputs
)
if response.status_code == 400:
error = response.json()
print(f"Bad request: {error['error']}")
# Bad request: inputs is required

See the Authentication guide for details. Common causes:

Error MessageFix
missing authorization headerAdd Authorization: Bearer <token> or x-api-key
invalid authorization formatUse Bearer <token> format (note the space after Bearer)
missing tokenToken value is empty
invalid tokenToken is expired, revoked, or incorrect

Returned when you request a job that doesn’t exist. The message is lipsync request not found or video translate request not found:

{
"error": "lipsync request not found"
}

Double-check the request_id or reference_id you’re using. Common causes:

  • Typo in the ID
  • The request belongs to a different account
  • Using a reference_id that was never set

Video Translate returns 413 when a JSON body exceeds 8 MiB or an upload body exceeds 2 GiB plus 9 MiB for form fields and SRT files. A video over 2 GiB returns 400 (video file exceeds the 2 GiB limit). An upload that sends no data for 30 seconds returns 408. See Limits.

Your account’s generation quota has been exhausted:

{
"error": "token limit reached"
}

Upgrade your plan from the Dashboard to increase your quota.

You’ve exceeded the request rate limit:

{
"error": "rate limit exceeded"
}

Video Translate also returns 429 when your account has too many active translations (too many active video translations for this account) or when capacity is temporarily full (video translation capacity is temporarily exhausted). Retry with backoff.

import requests
import time
def api_request_with_retry(method, url, max_retries=3, **kwargs):
"""Make an API request with automatic retry on rate limits."""
for attempt in range(max_retries + 1):
response = requests.request(method, url, **kwargs)
if response.status_code == 429:
wait_time = 2 ** attempt # 1s, 2s, 4s
print(f"Rate limited. Retrying in {wait_time}s...")
time.sleep(wait_time)
continue
return response
raise Exception("Max retries exceeded due to rate limiting")
async function apiRequestWithRetry(
url: string,
options: RequestInit,
maxRetries = 3
): Promise<Response> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.status === 429) {
const waitTime = 2 ** attempt * 1000;
console.log(`Rate limited. Retrying in ${waitTime}ms...`);
await new Promise((r) => setTimeout(r, waitTime));
continue;
}
return response;
}
throw new Error("Max retries exceeded due to rate limiting");
}

These indicate a problem on our side:

{
"error": "internal error"
}

If you encounter 500 errors:

  1. Retry the request after a short delay
  2. If the error persists, check the status page
  3. Contact support at support@chamelaion.com with the request details

Here’s a robust Python function that handles all error types:

import requests
import time
import os
class CHAMELAIONError(Exception):
def __init__(self, status_code: int, message: str):
self.status_code = status_code
self.message = message
super().__init__(f"[{status_code}] {message}")
class RateLimitError(CHAMELAIONError):
pass
class AuthenticationError(CHAMELAIONError):
pass
class QuotaExceededError(CHAMELAIONError):
pass
def chamelaion_request(method: str, path: str, max_retries: int = 3, **kwargs):
"""Make a request to the CHAMELAION API with full error handling."""
base_url = "https://api.chamelaion.com/api"
headers = {"Authorization": f"Bearer {os.environ['CHAMELAION_API_KEY']}"}
kwargs.setdefault("headers", {}).update(headers)
for attempt in range(max_retries + 1):
response = requests.request(method, f"{base_url}{path}", **kwargs)
if response.status_code == 200:
return response.json()
error_msg = response.json().get("error", "unknown error")
if response.status_code == 429:
if attempt < max_retries:
wait = 2 ** attempt
time.sleep(wait)
continue
raise RateLimitError(429, error_msg)
if response.status_code == 401:
raise AuthenticationError(401, error_msg)
if response.status_code == 412:
raise QuotaExceededError(412, error_msg)
if response.status_code == 500 and attempt < max_retries:
time.sleep(2 ** attempt)
continue
raise CHAMELAIONError(response.status_code, error_msg)
My generation is stuck in “queued” status

Jobs may stay in queued status during high-traffic periods. If a job remains queued for more than 10 minutes, contact support. Make sure your polling timeout is at least 10 minutes for production workloads.

The output video has no lip movement

This can happen if CHAMELAION cannot detect a face in the video. Ensure your video has a clearly visible, front-facing speaker. For multi-person videos, make sure disable_active_speaker_detection is false so the correct speaker is identified.

My media URLs are rejected as not accessible

Lip Sync returns unable to access media url and Video Translate returns video URL is not accessible when CHAMELAION’s servers can’t download your media. Ensure:

  • The URLs are publicly accessible (no authentication required)
  • The URLs don’t have IP restrictions or geo-blocking
  • The URLs return the actual media content (not an HTML page)
  • Pre-signed URLs haven’t expired
My video translation failed after it was accepted

Some checks run only after the video is processed. Read error_message on the request, then:

  • If the source language couldn’t be detected, set source_language explicitly
  • If the detected source language is also a target, remove it from target_languages
  • If SRT validation failed, make sure source and target files have the same number of cues and speakers, and that timings fit within the video
  • If speaker_voices failed, make sure every key matches a Speaker N: number in your source_srt
I keep getting 401 errors
  • Verify your API key in the Dashboard
  • Check that you’re using the correct header format (Bearer <token> or x-api-key: <token>)
  • The Bearer prefix is case-sensitive
  • Confirm the key hasn’t been revoked