FFmpeg in the cloud
FFmpeg in the cloud,
for agents & humans.
The args you run locally, executed on cloud workers. Job state a machine can act on, recipes a person can reach for, and every output delivered to storage you control.
$ curl -s xora.sh/llms.txt # onboard your agent
- First job in under 2 min
- No credit card
- OpenAPI + llms.txt
Why this exists
The model already knows FFmpeg.
Every LLM has read thirty years of FFmpeg answers. Ask an agent to crop, concat, or transcode and it writes a working command on the first try — then hits the wall every agent hits: no binary, no CPU, nowhere for a 2 GB file to live.
Xora is the missing half. Sandboxed workers run the args, report state back as JSON, and put the output exactly where you said — whether the caller is a deploy script, an n8n node, or an agent in a loop.
Agents & humans
One endpoint. Two kinds of callers.
The same job contract, ergonomic for a person with curl at midnight and for an agent that has never seen your codebase.
for_agents
-
args as JSONA tool call, not a shell string. Nothing to quote, nothing to inject.
-
deterministic statequeued → transcoding → completed, progress in percent, idempotency keys, safe retries.
-
errors worth parsingFailures come back as structured JSON to branch on — not stderr soup.
-
a readable surfaceOpenAPI spec, llms.txt, plain REST. No SDK required.
You can process media with the Xora FFmpeg API.
Create a job:
POST https://api.xora.sh/v1/jobs
Authorization: Bearer YOUR_API_KEY
{
"mode": "ffmpeg",
"input_files": { "in_1": "<https url>" },
"output_files": { "out_1": "output.mp4" },
"ffmpeg": { "args": ["-i", "{{in_1}}", ...your args, "{{out_1}}"] }
}
Poll GET /v1/jobs/{id} until state is terminal
(completed | failed | rejected | cancelled), then use the
signed download URL on the job — or pass "webhookUrl"
to be called back instead.
Full API surface: https://xora.sh/llms.txt for humans
-
Recipes for the 90%
web-ready, thumbnail, trim, concat, compress — tuned so you don't relearn flag order at 1 a.m.
-
Presets
Save a configuration once, run it forever with a single ID.
-
A dashboard worth opening
Live job logs, key management, usage — when you'd rather look than curl.
-
Docs that answer
Zero to first job in under two minutes, with a recipe catalog and an interactive API reference.
No translation step
Bring the command you already have.
Wrap your local FFmpeg invocation in a JSON payload and it behaves the same way in the cloud. {{in_1}} and {{out_1}} name your files; inputs come from any HTTPS origin — S3 and R2 presigned URLs included.
On your laptop
$ ffmpeg -i raw.mov -vf "scale=1280:720" \
-c:v libx264 -crf 20 output.mp4 Same args, different machine.
curl -X POST https://api.xora.sh/v1/jobs \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"mode": "ffmpeg",
"input_files": { "in_1": "https://cdn.example.com/raw.mov" },
"output_files": { "out_1": "output.mp4" },
"ffmpeg": {
"args": ["-i", "{{in_1}}",
"-vf", "scale=1280:720",
"-c:v", "libx264", "-crf", "20",
"{{out_1}}"]
}
}' const job = await fetch('https://api.xora.sh/v1/jobs', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
mode: 'ffmpeg',
input_files: { in_1: 'https://cdn.example.com/raw.mov' },
output_files: { out_1: 'output.mp4' },
ffmpeg: {
args: ['-i', '{{in_1}}',
'-vf', 'scale=1280:720',
'-c:v', 'libx264', '-crf', 20,
'{{out_1}}']
}
})
}).then(r => r.json()); import requests
job = requests.post(
"https://api.xora.sh/v1/jobs",
headers={
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json",
},
json={
"mode": "ffmpeg",
"input_files": {"in_1": "https://cdn.example.com/raw.mov"},
"output_files": {"out_1": "output.mp4"},
"ffmpeg": {
"args": ["-i", "{{in_1}}",
"-vf", "scale=1280:720",
"-c:v", "libx264", "-crf", 20,
"{{out_1}}"]
},
},
).json() payload := `{
"mode": "ffmpeg",
"input_files": { "in_1": "https://cdn.example.com/raw.mov" },
"output_files": { "out_1": "output.mp4" },
"ffmpeg": { "args": ["-i", "{{in_1}}",
"-vf", "scale=1280:720",
"-c:v", "libx264", "-crf", "20", "{{out_1}}"] }
}`
req, _ := http.NewRequest("POST",
"https://api.xora.sh/v1/jobs", strings.NewReader(payload))
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close() let job = reqwest::Client::new()
.post("https://api.xora.sh/v1/jobs")
.bearer_auth("YOUR_API_KEY")
.json(&serde_json::json!({
"mode": "ffmpeg",
"input_files": { "in_1": "https://cdn.example.com/raw.mov" },
"output_files": { "out_1": "output.mp4" },
"ffmpeg": { "args": ["-i", "{{in_1}}",
"-vf", "scale=1280:720",
"-c:v", "libx264", "-crf", 20, "{{out_1}}"] }
}))
.send()
.await?; POST the job
A recipe or raw FFmpeg args, plus an input URL. You get a job ID back immediately.
Watch the state
Poll the job or take a webhook. Every transition is explicit — queued, transcoding, completed, failed — with progress while it runs.
Collect the output
A signed download URL — or nothing to collect at all, because it's already in your bucket.
Capabilities
Small API. No small print.
A handful of endpoints that hold up whether the job is one thumbnail or a batch of a thousand transcodes.
Your bucket, not ours
Deliver outputs straight to your Cloudflare R2 bucket — your keys, your access rules, no egress markup. Or use managed storage with signed URLs and a Files API to list and delete. You always know where media lives.
No abstraction ceiling
Filter graphs, codec parameters, multi-input args — if FFmpeg takes the flag, the API takes the flag.
"probe": {
"codec": "h264",
"width": 3840,
"duration": 127.4,
"faststart": false
} Probe before you process
mode: "probe" returns enriched ffprobe metadata, and the web-ready recipe re-encodes only when a file actually needs it.
Range: bytes=0–2097151 of 2.1 GB
Ranged reads
Byte-range staging cuts a thumbnail from a 2 GB file without moving 2 GB — and falls back to a full copy automatically when the origin can't seek.
Parallel by default
Submit the batch and let workers absorb it — no thread pools to size, no queue to nurse.
Built for retries
Idempotency keys make resubmission safe, a retry endpoint reruns terminal jobs, and webhooks fire on every terminal state. The things a workflow needs at 3 a.m., built into the contract.
Built on FFmpeg
The formats your product already ships.
Common containers, codecs, audio, and image outputs — addressed with ordinary FFmpeg arguments.
Fair questions
The things you'd ask before trusting us with media.
- Where do files live?
- Wherever you decide. Outputs go straight to your R2 bucket, or sit in managed storage behind signed URLs with a Files API to list and delete. Inputs are read from your URL — staged for the job, not hoarded.
- What does it cost?
- Right now: nothing. Xora is in private preview — free to build with, no credit card. Published rates meter compute minutes and delivery GB — what your job actually consumes, not your file sizes or your video's length. See preview pricing.
- What if the command is wrong?
-
Malformed jobs are
rejectedwith a structured error that says why — a state your code (or your agent) can branch on and fix, not a stack trace to screenshot. - Is running arbitrary FFmpeg safe?
- Args arrive as a JSON array, not a shell string — there is no shell to inject. Every job runs in an isolated worker that exists for that job.
Put FFmpeg one POST away.
Free while in private preview — for you, and for your agent.
$ curl -s xora.sh/llms.txt # the whole API, one fetch