Quickstart
This guide takes you from zero to a completed transcoding job. You’ll generate a thumbnail from a sample video using a single API call.
Prerequisites
Section titled “Prerequisites”- A Xora account — sign up here (free)
- An API key — create one in the dashboard
Your first job
Section titled “Your first job”-
Create a thumbnail job
Send a
POSTto/api/v1/jobswith thethumbnailrecipe. This captures a single frame from a video at the 1-second mark:Terminal window 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://samplelib.com/lib/preview/mp4/sample-5s.mp4"},"output": {"format": "jpg"},"recipe": {"name": "thumbnail","seekSeconds": 1}}'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://samplelib.com/lib/preview/mp4/sample-5s.mp4'},output: {format: 'jpg'},recipe: {name: 'thumbnail',seekSeconds: 1}})});const data = await response.json();console.log(data);import requestsresponse = requests.post("https://api.xora.sh/v1/jobs",headers={"Authorization": "Bearer YOUR_API_KEY","Content-Type": "application/json"},json={"mode": "recipe","input": {"url": "https://samplelib.com/lib/preview/mp4/sample-5s.mp4"},"output": {"format": "jpg"},"recipe": {"name": "thumbnail","seekSeconds": 1}})data = response.json()print(data)You’ll get a
202 Acceptedresponse with the job ID:{"jobId": "01JXYZ1234ABCDEF56789000","state": "queued"} -
Check job status
Poll the job to see its progress. Replace
JOB_IDwith thejobIdfrom step 1:Terminal window curl https://api.xora.sh/v1/jobs/JOB_ID \-H "Authorization: Bearer YOUR_API_KEY"const response = await fetch('https://api.xora.sh/v1/jobs/JOB_ID', {headers: {'Authorization': 'Bearer YOUR_API_KEY'}});const data = await response.json();console.log(data);import requestsresponse = requests.get("https://api.xora.sh/v1/jobs/JOB_ID",headers={"Authorization": "Bearer YOUR_API_KEY"})data = response.json()print(data)While processing, you’ll see states like
probing,transcoding, etc. with aprogresspercentage:{"jobId": "01JXYZ1234ABCDEF56789000","state": "transcoding","progress": 50} -
Download the output
When
stateis"completed", the response includes a signed download URL:{"jobId": "01JXYZ1234ABCDEF56789000","state": "completed","progress": 100,"output": {"signedUrl": "https://cdn.xora.sh/...signed-url..."},"output_files": {"output": {"filename": "output.jpg","file_type": "image","file_format": "jpg","mime_type": "image/jpeg","storage_url": "https://cdn.xora.sh/...signed-url..."}}}Open the
signedUrlin your browser or download it with curl:Terminal window curl -o thumbnail.jpg "SIGNED_URL_HERE"import fs from 'fs';import { pipeline } from 'stream/promises';const response = await fetch('SIGNED_URL_HERE');if (!response.ok) throw new Error(`Unexpected response: ${response.statusText}`);await pipeline(response.body, fs.createWriteStream('thumbnail.jpg'));import requestsresponse = requests.get("SIGNED_URL_HERE")with open("thumbnail.jpg", "wb") as f:f.write(response.content)
Using a preset instead
Section titled “Using a preset instead”If you’ve saved a preset in the dashboard, you can run a job with just its ID:
curl -X POST https://api.xora.sh/v1/jobs \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "presetId": "YOUR_PRESET_ID", "input": { "url": "https://example.com/video.mp4" } }'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({ presetId: 'YOUR_PRESET_ID', input: { url: 'https://example.com/video.mp4' } })});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={ "presetId": "YOUR_PRESET_ID", "input": { "url": "https://example.com/video.mp4" } })data = response.json()print(data)Receiving webhooks instead of polling
Section titled “Receiving webhooks instead of polling”Instead of polling, you can pass a webhookUrl to get notified when the job finishes:
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": "jpg" }, "recipe": { "name": "thumbnail", "seekSeconds": 1 }, "webhookUrl": "https://your-server.com/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: 'jpg' }, recipe: { name: 'thumbnail', seekSeconds: 1 }, webhookUrl: 'https://your-server.com/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": "jpg" }, "recipe": { "name": "thumbnail", "seekSeconds": 1 }, "webhookUrl": "https://your-server.com/webhook" })data = response.json()print(data)Xora will POST the job result to your URL when it reaches a terminal state. See Webhooks.
What’s next?
Section titled “What’s next?”- Recipes → — web-ready video, edit proxy, compress, transcode, resize, trim, extract audio, thumbnails, concat
- Custom FFmpeg → — full control with your own FFmpeg arguments
- Presets → — save and reuse job configurations
- Input Staging → — skip copying inputs when your URL supports byte ranges
- Storage Providers → — route outputs directly to your own Cloudflare R2 bucket
- Job lifecycle → — understand job states and progress
- API Reference → — full interactive API docs