Skip to content

Error Handling

The Xora API uses consistent error responses across all endpoints. This guide covers the error format, common error codes, and best practices for handling failures.

Every error response follows this structure:

{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable description of what went wrong",
"retryable": false
}
}
FieldTypeDescription
codestringMachine-readable error code (always UPPER_SNAKE_CASE)
messagestringHuman-readable explanation
retryablebooleanWhether the request can be retried

When a job fails, the error also includes a stage field indicating where in the pipeline the failure occurred:

{
"jobId": "01JXYZ...",
"state": "failed",
"error": {
"code": "TRANSCODE_ERROR",
"message": "FFmpeg exited with code 1: Invalid data found when processing input",
"stage": "transcoding",
"retryable": true
}
}
CodeHTTP StatusMeaning
UNAUTHORIZED401Missing or invalid API key / session
NOT_FOUND404Job, preset, file, or resource not found
VALIDATION_ERROR400Request body failed schema validation
CodeMeaningRetryable
ENGINE_ERRORProcessing failureUsually yes
ENGINE_INVOCATION_ERRORProcessing timed out or crashedYes
ENGINE_INVOKE_FAILEDCould not start the jobYes
ENGINE_EMPTY_RESPONSENo result was returnedYes
ENGINE_INVALID_RESPONSEUnexpected response formatYes
CONFIG_ERRORServer misconfigurationNo
UNSUPPORTED_COMMANDFFmpeg args rejected by security validationNo
TRANSCODE_ERRORFFmpeg processing failedSometimes
ScenarioExample message
Missing input URL"input.url is required for this recipe"
Invalid output format`“Invalid enum value. Expected ‘mp4'
Bad FFmpeg args"ffmpeg.args must contain at least 1 element"
Concat with single file"concat recipe requires input_files with at least two entries"
Mixed input modes"Use either input/output or input_files/output_files, not both"
Invalid webhook URL"webhookUrl must use https://"

When retryable is true, you can retry the request. For job failures, use the retry endpoint:

Terminal window
curl -X POST https://api.xora.sh/v1/jobs/JOB_ID/retry \
-H "Authorization: Bearer YOUR_API_KEY"
{
"retried": true,
"jobId": "01JXYZ1234ABCDEF56789000"
}

For transient API errors (5xx), use exponential backoff:

async function callXoraApi(url, options, maxRetries = 3) {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await fetch(url, options);
if (response.ok) return response.json();
const body = await response.json();
// Don't retry client errors (4xx) — they won't succeed
if (response.status < 500) throw new Error(body.error.message);
// Retry server errors with exponential backoff
if (attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error('Max retries exceeded');
}
StatusCodeWhen
400VALIDATION_ERRORInvalid request body
401UNAUTHORIZEDBad or missing auth
404NOT_FOUNDPreset not found (when using presetId)
500ENGINE_ERRORServer failed to accept the job
StatusCodeWhen
401UNAUTHORIZEDBad or missing auth
404NOT_FOUNDJob doesn’t exist or belongs to another user
500ENGINE_ERRORServer unreachable
StatusCodeWhen
401UNAUTHORIZEDBad or missing auth
404NOT_FOUNDJob not found
500ENGINE_ERRORServer failed to cancel
StatusCodeWhen
401UNAUTHORIZEDBad or missing auth
404NOT_FOUNDJob or output not found

When a job fails, the stage field tells you where in the pipeline it broke:

StageWhat was happening
probingAnalyzing the input file
planningDeciding how to process
segmentingPreparing the input for processing
transcodingRunning FFmpeg
mergingAssembling the final output

This helps you diagnose whether the issue is with your input file, your FFmpeg command, or a transient server problem.