# Error Codes Reference of error codes, the error response format, and handling strategies for the Lesan AI API. When an API request fails, Lesan AI returns a JSON error response with a consistent format. This page covers all error types, their codes, and how to handle them programmatically. ## Error Response Format All error responses follow this structure: ```json { "error": { "type": "invalid_request_error", "code": "missing_required_field", "message": "The 'language' field is required.", "param": "language" } } ``` - **type** — The category of error (see error types below) - **code** — A machine-readable error code for programmatic handling - **message** — A human-readable description of what went wrong - **param** — (optional) The request parameter that caused the error ## HTTP Status Codes ### 400 — Bad Request The request was malformed or missing required parameters. ```json // Error type: invalid_request_error // Common codes: // missing_required_field — A required field was not provided // invalid_field_value — A field value is not valid (e.g., unsupported language) // invalid_audio_format — The audio file format is not supported // file_too_large — The audio file exceeds the maximum size limit // invalid_json — The request body is not valid JSON // invalid_url — The probe URL is malformed or uses an unsupported scheme (http/https only) // INVALID_FIELD — Invalid field combination (e.g. 'model' with language=auto) // UNSUPPORTED_LANGUAGE — Language has no models (422); message lists available languages // UNSUPPORTED_MODEL — Model name unknown for the language (422); message lists available models { "error": { "type": "invalid_request_error", "code": "UNSUPPORTED_LANGUAGE", "message": "Language 'xx' is not supported. Available: ['am', 'om', 'ti', 'auto'].", "param": "language" } } ``` ### 401 — Unauthorized Authentication failed or no API key was provided. ```json // Error type: authentication_error // Common codes: // missing_api_key — No Authorization header provided // invalid_api_key — The API key is malformed or does not exist // expired_api_key — The API key has expired // revoked_api_key — The API key has been revoked { "error": { "type": "authentication_error", "code": "invalid_api_key", "message": "The API key provided is invalid." } } ``` ### 403 — Forbidden The API key does not have permission for the requested operation. ```json // Error type: permission_error // Common codes: // insufficient_scope — The key lacks the required scope // ip_not_allowed — Request IP is not in the key's allowlist // origin_not_allowed — Request origin is not in the key's allowlist // media_unavailable — The probed media is private, region-blocked, or requires sign-in { "error": { "type": "permission_error", "code": "insufficient_scope", "message": "This API key does not have the 'write' scope." } } ``` ### 404 — Not Found The requested resource does not exist. ```json // Error type: not_found_error // Common codes: // job_not_found — The transcription job ID does not exist // webhook_not_found — The webhook ID does not exist // resource_not_found — Generic resource not found // media_not_found — The probed URL resolved but the upstream returned 404 { "error": { "type": "not_found_error", "code": "job_not_found", "message": "Job 'job_abc123' was not found." } } ``` ### 429 — Too Many Requests You have exceeded the rate limit. See the [Rate Limits](/guides/rate-limits) guide for details. ```json // Error type: rate_limit_error // Common codes: // rate_limit_exceeded — Too many requests per minute // concurrent_limit_exceeded — Too many concurrent jobs // daily_quota_exceeded — Daily usage quota reached { "error": { "type": "rate_limit_error", "code": "rate_limit_exceeded", "message": "Rate limit exceeded. Retry after 2 seconds.", "retry_after": 2 } } ``` When you receive a 429 response, check the `retry_after` field for the number of seconds to wait before retrying. ### 502 — Bad Gateway An upstream service used by the API failed. Currently emitted by the [media probe](/guides/media-probe) endpoint when yt-dlp or ffprobe exits with an unexpected error. Safe to retry with backoff. ```json // Error type: api_error // Common codes: // upstream_error — yt-dlp or ffprobe failed to resolve the URL { "error": { "type": "api_error", "code": "upstream_error", "message": "Probe failed: HTTP Error 410: Gone", "param": "url" } } ``` ### 504 — Gateway Timeout An upstream operation exceeded its timeout. Currently emitted by the [media probe](/guides/media-probe) endpoint when yt-dlp or ffprobe takes longer than the configured window (default 20 seconds). Safe to retry once. ```json // Error type: api_error // Common codes: // probe_timeout — The probe exceeded the configured timeout { "error": { "type": "api_error", "code": "probe_timeout", "message": "yt-dlp timed out", "param": "url" } } ``` ### 500 — Internal Server Error An unexpected error occurred on the server. These are rare and typically resolve on their own. ```json // Error type: server_error // Common codes: // internal_error — An unexpected internal error occurred // service_unavailable — The service is temporarily unavailable // model_error — The ML model encountered an error { "error": { "type": "server_error", "code": "internal_error", "message": "An internal error occurred. Please try again." } } ``` ## Handling Errors Always check the HTTP status code and parse the error response body to handle errors gracefully: ```python import requests import time def transcribe_with_retry(audio_url, language, max_retries=3): url = "https://asr.lesan.ai/transcribe" headers = { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" } payload = { "audio_url": audio_url, "language": language, "mode": "sync" } for attempt in range(max_retries): response = requests.post(url, headers=headers, json=payload) if response.status_code == 200: return response.json() error = response.json().get("error", {}) error_type = error.get("type") error_code = error.get("code") # Don't retry client errors (except rate limits) if response.status_code == 400: raise ValueError(f"Bad request: {error.get('message')}") elif response.status_code == 401: raise PermissionError(f"Auth failed: {error.get('message')}") elif response.status_code == 429: retry_after = error.get("retry_after", 2 ** attempt) print(f"Rate limited. Waiting {retry_after}s...") time.sleep(retry_after) continue elif response.status_code >= 500: wait = 2 ** attempt print(f"Server error. Retrying in {wait}s...") time.sleep(wait) continue else: raise Exception(f"Unexpected error: {response.status_code}") raise Exception("Max retries exceeded") ``` ```javascript async function transcribeWithRetry(audioUrl, language, maxRetries = 3) { const url = "https://asr.lesan.ai/transcribe"; for (let attempt = 0; attempt < maxRetries; attempt++) { const response = await fetch(url, { method: "POST", headers: { "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" }, body: JSON.stringify({ audio_url: audioUrl, language: language, mode: "sync" }) }); if (response.ok) { return await response.json(); } const { error } = await response.json(); // Don't retry client errors (except rate limits) if (response.status === 400) { throw new Error(`Bad request: ${error.message}`); } else if (response.status === 401) { throw new Error(`Auth failed: ${error.message}`); } else if (response.status === 429) { const retryAfter = error.retry_after || 2 ** attempt; console.log(`Rate limited. Waiting ${retryAfter}s...`); await new Promise(r => setTimeout(r, retryAfter * 1000)); continue; } else if (response.status >= 500) { const wait = 2 ** attempt; console.log(`Server error. Retrying in ${wait}s...`); await new Promise(r => setTimeout(r, wait * 1000)); continue; } else { throw new Error(`Unexpected error: ${response.status}`); } } throw new Error("Max retries exceeded"); } ``` ```curl # Check the HTTP status code in the response curl -s -w "\nHTTP_STATUS: %{http_code}" \ https://asr.lesan.ai/transcribe \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "audio_url": "https://example.com/audio.mp3", "language": "am", "mode": "sync" }' # Example error response (400): # { # "error": { # "type": "invalid_request_error", # "code": "missing_required_field", # "message": "The 'language' field is required.", # "param": "language" # } # } # HTTP_STATUS: 400 ``` ## Retry Strategy Follow these guidelines when implementing retry logic: - **429 errors** — Always retry. Use the `retry_after` value if provided, otherwise use exponential backoff. - **500, 502, 504 errors** — Retry with exponential backoff (1s, 2s, 4s). Stop after 3 attempts. - **400 errors** — Do not retry. Fix the request parameters. - **401 errors** — Do not retry. Check your API key. - **403 errors** — Do not retry. Check your API key permissions (or, for probe `media_unavailable`, ask the user for a public URL). See the [Rate Limits](/guides/rate-limits) guide for quota details, or the [Best Practices](/guides/best-practices) guide for production error handling patterns.