Job Lifecycle
Every transcoding job moves through a series of states from creation to completion (or failure). Understanding these states helps you build robust integrations.
Job states
Section titled “Job states”When you create a job, it begins as queued and progresses through these states:
queued → probing → planning → transcoding → merging → completed ↘ (optional segmenting on legacy paths) ↘ failed ↘ rejected ↘ cancelled| State | Description |
|---|---|
queued | Job accepted, waiting to be picked up |
probing | Analyzing the input file (codec, duration, resolution, byte-range support) |
planning | Determining the processing strategy |
segmenting | Legacy state — not used on current engine paths (inputs are range-read or staged once) |
transcoding | Actively processing media (longest phase) |
merging | Assembling chunk outputs into the final file (parallel jobs only) |
completed | Done — output is ready for download |
failed | Something went wrong — check error for details |
rejected | Input was rejected before processing (e.g., unsupported format or unsafe command) |
cancelled | You cancelled the job before it finished |
Terminal states
Section titled “Terminal states”A job is terminal when it reaches one of: completed, failed, rejected, or cancelled. Terminal jobs cannot transition to another state (except via retry).
Tracking progress
Section titled “Tracking progress”The progress field is a number from 0 to 100 that updates as the job runs:
{ "jobId": "01JXYZ...", "state": "transcoding", "progress": 65}0— job is queued or just started1–99— actively processing100— completed
Polling for completion
Section titled “Polling for completion”The simplest way to track a job is to poll GET /api/v1/jobs/:id until the state is terminal:
# Poll every 2 secondswhile true; do RESPONSE=$(curl -s https://api.xora.sh/v1/jobs/JOB_ID \ -H "Authorization: Bearer YOUR_API_KEY")
STATE=$(echo "$RESPONSE" | jq -r '.state') PROGRESS=$(echo "$RESPONSE" | jq -r '.progress')
echo "State: $STATE, Progress: $PROGRESS%"
if [ "$STATE" = "completed" ] || [ "$STATE" = "failed" ] || \ [ "$STATE" = "rejected" ] || [ "$STATE" = "cancelled" ]; then echo "$RESPONSE" | jq . break fi
sleep 2doneconst pollJob = async (jobId) => { const terminalStates = ['completed', 'failed', 'rejected', 'cancelled'];
while (true) { const response = await fetch(`https://api.xora.sh/v1/jobs/${jobId}`, { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }); const data = await response.json();
console.log(`State: ${data.state}, Progress: ${data.progress}%`);
if (terminalStates.includes(data.state)) { console.log('Final job details:', data); break; }
// Wait 2 seconds before polling again await new Promise(resolve => setTimeout(resolve, 2000)); }};
pollJob('JOB_ID');import timeimport requests
def poll_job(job_id): terminal_states = {"completed", "failed", "rejected", "cancelled"}
while True: response = requests.get( f"https://api.xora.sh/v1/jobs/{job_id}", headers={ "Authorization": "Bearer YOUR_API_KEY" } ) data = response.json()
print(f"State: {data['state']}, Progress: {data['progress']}%")
if data["state"] in terminal_states: print("Final job details:", data) break
# Wait 2 seconds before polling again time.sleep(2)
poll_job("JOB_ID")Using webhooks
Section titled “Using webhooks”Instead of polling, pass a webhookUrl when creating a job. Xora will POST the result to your URL when the job reaches a terminal state:
curl -X POST https://api.xora.sh/v1/jobs \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "mode": "recipe", "input": { "url": "https://example.com/video.mp4" }, "output": { "format": "mp4" }, "recipe": { "name": "compress", "crf": 28 }, "webhookUrl": "https://your-server.com/xora-webhook" }'const response = await fetch('https://api.xora.sh/v1/jobs', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY', 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: 'recipe', input: { url: 'https://example.com/video.mp4' }, output: { format: 'mp4' }, recipe: { name: 'compress', crf: 28 }, webhookUrl: 'https://your-server.com/xora-webhook' })});const data = await response.json();console.log(data);import requests
response = requests.post( "https://api.xora.sh/v1/jobs", headers={ "Authorization": "Bearer YOUR_API_KEY", "Content-Type": "application/json" }, json={ "mode": "recipe", "input": { "url": "https://example.com/video.mp4" }, "output": { "format": "mp4" }, "recipe": { "name": "compress", "crf": 28 }, "webhookUrl": "https://your-server.com/xora-webhook" })data = response.json()print(data)See the Webhooks guide for payload format and configuration.
Job actions
Section titled “Job actions”Cancel a job
Section titled “Cancel a job”Stop a running job before it completes:
curl -X POST https://api.xora.sh/v1/jobs/JOB_ID/cancel \ -H "Authorization: Bearer YOUR_API_KEY"const response = await fetch('https://api.xora.sh/v1/jobs/JOB_ID/cancel', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY' }});const data = await response.json();console.log(data);import requests
response = requests.post( "https://api.xora.sh/v1/jobs/JOB_ID/cancel", headers={ "Authorization": "Bearer YOUR_API_KEY" })data = response.json()print(data){ "cancelled": true}Retry a failed job
Section titled “Retry a failed job”Retry a job that ended in failed, rejected, or cancelled state. The retry uses the same input parameters:
curl -X POST https://api.xora.sh/v1/jobs/JOB_ID/retry \ -H "Authorization: Bearer YOUR_API_KEY"const response = await fetch('https://api.xora.sh/v1/jobs/JOB_ID/retry', { method: 'POST', headers: { 'Authorization': 'Bearer YOUR_API_KEY' }});const data = await response.json();console.log(data);import requests
response = requests.post( "https://api.xora.sh/v1/jobs/JOB_ID/retry", headers={ "Authorization": "Bearer YOUR_API_KEY" })data = response.json()print(data){ "retried": true, "jobId": "01JXYZ1234ABCDEF56789000"}Delete job output
Section titled “Delete job output”Remove the hosted output files from storage. This is permanent:
curl -X DELETE https://api.xora.sh/v1/jobs/JOB_ID/output \ -H "Authorization: Bearer YOUR_API_KEY"const response = await fetch('https://api.xora.sh/v1/jobs/JOB_ID/output', { method: 'DELETE', headers: { 'Authorization': 'Bearer YOUR_API_KEY' }});const data = await response.json();console.log(data);import requests
response = requests.delete( "https://api.xora.sh/v1/jobs/JOB_ID/output", headers={ "Authorization": "Bearer YOUR_API_KEY" })data = response.json()print(data){ "jobId": "01JXYZ1234ABCDEF56789000", "deleted": true}Listing jobs
Section titled “Listing jobs”Fetch a paginated list of your jobs, optionally filtered by state:
# List recent jobscurl "https://api.xora.sh/v1/jobs?limit=20&offset=0" \ -H "Authorization: Bearer YOUR_API_KEY"
# Filter by statecurl "https://api.xora.sh/v1/jobs?state=completed&limit=10" \ -H "Authorization: Bearer YOUR_API_KEY"// List recent jobsconst responseRecent = await fetch('https://api.xora.sh/v1/jobs?limit=20&offset=0', { headers: { 'Authorization': 'Bearer YOUR_API_KEY' }});console.log(await responseRecent.json());
// Filter by stateconst responseState = await fetch('https://api.xora.sh/v1/jobs?state=completed&limit=10', { headers: { 'Authorization': 'Bearer YOUR_API_KEY' }});console.log(await responseState.json());import requests
# List recent jobsresponse_recent = requests.get( "https://api.xora.sh/v1/jobs?limit=20&offset=0", headers={"Authorization": "Bearer YOUR_API_KEY"})print(response_recent.json())
# Filter by stateresponse_state = requests.get( "https://api.xora.sh/v1/jobs?state=completed&limit=10", headers={"Authorization": "Bearer YOUR_API_KEY"})print(response_state.json()){ "jobs": [ { "id": "01JXYZ...", "mode": "recipe", "recipe_name": "compress", "state": "completed", "created_at": "2026-06-04T10:00:00.000Z", "completed_at": "2026-06-04T10:00:45.000Z", "duration_seconds": 120.5 } ], "limit": 20, "offset": 0}| Parameter | Type | Default | Description |
|---|---|---|---|
limit | number | 20 | Results per page (max 100) |
offset | number | 0 | Skip this many results |
state | string | — | Filter by state: queued, probing, planning, segmenting, transcoding, merging, completed, failed, rejected, cancelled |