Compress, convert and trim video with one API call
Need full control? Run any FFmpeg command. Each job runs in its own sandbox, and you can pay only for the compute you use.
ffmpeg -i {{in}} -vf scale=1280:-2 -crf 26 {{out}}
{{in}} and {{out}} stand in for the files you declare.Send the command, get a link to the file
With ?wait=25 a short job comes back finished in the same response. Leave it off and you get a job id to check on with GET /v1/jobs/:id.
Bring your own command
Name your files, then write the FFmpeg command you would run on your own machine.
curl "https://api.mediatofu.com/v1/run?wait=25" \
-H "Authorization: Bearer $MEDIATOFU_KEY" \
-d '{
"input_files": { "in": "https://example.com/clip.mov" },
"output_files": { "out": "small.mp4" },
"ffmpeg_command": "-i {{in}} -vf scale=1280:-2 -crf 26 {{out}}"
}'Inputs are URLs or uploaded file ids. A job can have up to 10 inputs and 10 outputs, and every one you declare has to appear in the command.
{
"id": "job_7c41e09ab2d35f68104bc7ae",
"tool": "ffmpeg",
"status": "succeeded",
"compute_seconds": 7,
"compute_ms": 6412,
"outputs": [
{
"name": "small.mp4",
"size": 18350412,
"url": "https://api.mediatofu.com/o/e2a94c17d05b36f8a1c7904ed2b6735f/job_7c41e09ab2d35f68104bc7ae/small.mp4"
}
],
"exit_code": 0,
"created_at": "2026-11-09T10:42:03Z",
"finished_at": "2026-11-09T10:42:13Z"
}Or skip the flags
The tools build the command for you and run on the same engine.
curl "https://api.mediatofu.com/v1/tools/compress?wait=25" \
-H "Authorization: Bearer $MEDIATOFU_KEY" \
-d '{ "input": "https://example.com/clip.mov", "crf": 28 }'A lower crf means higher quality and a larger file. Compress accepts 18 to 40 and uses 28 if you leave it out.
{
"id": "job_b80f3d16c94e27a5013fd86c",
"tool": "compress",
"status": "succeeded",
"compute_seconds": 6,
"compute_ms": 5238,
"outputs": [
{
"name": "out.mp4",
"size": 15204877,
"url": "https://api.mediatofu.com/o/59d3b07ae18c4f26b7a0d31c86ef2490/job_b80f3d16c94e27a5013fd86c/out.mp4"
}
],
"exit_code": 0,
"created_at": "2026-11-09T10:44:51Z",
"finished_at": "2026-11-09T10:45:00Z"
}Every job gets its own container, and only that job
Step 1: A new container starts for this job.
Step 2: Your input files are copied in.
Step 3: FFmpeg runs with no internet and no secrets.
Step 4: Your output files are saved.
Step 5: The container is destroyed.
Commands stay inside the job
Your filters, codecs and flags work as usual, and there is never a shell. A command can only touch the files you declared. URLs inside the command and options that read or write other files are rejected before anything runs.
If a job fails on our side, you aren't charged.
-i {{in}} -i other.mp4 {{out}}Every -i must be an {{input}} placeholder, or follow -f lavfi.
-i {{in}} -vf movie=http://example.com/logo.png {{out}}URLs and protocols can't be used inside ffmpeg_command. Declare them in input_files.
-i {{in}} -vf movie=/etc/passwd {{out}}Paths outside the job aren't allowed: movie=/etc/passwd
-i {{in}} -filter_script filters.txt {{out}}The option -filter_script is not allowed.
Eight jobs you can run without learning FFmpeg
Post to /v1/tools/<name> with an input, which is a URL or a file id, plus any options below.
probeRead a file's format and streams as JSON.
No options
compressShrink a video to an H.264 MP4.
crf, max_height
convertChange format: mp4, webm, mov, mkv, mp3, m4a, wav, ogg or flac.
format
resizeScale a video to a width, a height, or both.
width, height
trimCut a section out of a video or audio file without re-encoding.
start, end or duration
thumbnailSave one frame as a JPEG.
at, width
extract-audioPull the audio track out, as MP3 unless you pick another format.
format
gifTurn a section of video into an animated GIF.
start, duration, width, fps
Hear back when it's done
On paid plans, add webhook_url to any job and stop polling. Useful for long jobs and automations.
- When
- One POST when the job succeeds or fails. The body is the same JSON as the job response.
- Signature
- Each POST carries an HMAC-SHA256 of the raw body in X-MediaTofu-Signature.
- Delivery
- At least once, with up to 5 attempts until your endpoint answers with a 2xx. Duplicates are possible, so key on the job id.
import { createHash, createHmac } from "node:crypto";
const secret = createHash("sha256")
.update(process.env.MEDIATOFU_KEY)
.digest("hex");
const expected = "sha256=" + createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
// Compare with the X-MediaTofu-Signature header.Pay for compute, by the second
Compute is the time FFmpeg spends running your job, rounded up to a whole second. Waiting in the queue and moving files around don't count.
Free
$0
No card needed
- Compute
- 5 minutes a month
- Job length
- Up to 1 minute
- Input size
- Up to 50 MB
- Concurrency
- 1 job at a time
- Webhooks
- Not included
- Outputs kept
- 24 hours
Pay per use
$0.024/ minute
No monthly fee
- Compute
- Billed by the second
- Job length
- Up to 5 minutes
- Input size
- Up to 2 GB
- Concurrency
- 3 jobs at once
- Webhooks
- Included
- Outputs kept
- 7 days
Starter
$9/ month
Billed monthly
- Compute
- 600 minutes a month, then $0.015 a minute
- Job length
- Up to 1 minute
- Input size
- Up to 500 MB
- Concurrency
- 2 jobs at once
- Webhooks
- Included
- Outputs kept
- 7 days
Pro
$19/ month
Billed monthly
- Compute
- 1,500 minutes a month, then $0.0127 a minute
- Job length
- Up to 10 minutes
- Input size
- Up to 2 GB
- Concurrency
- 5 jobs at once
- Webhooks
- Included
- Outputs kept
- 7 days
Metered usage is capped at $25 per billing period, rising to $250 after your first paid renewal. Input size limits apply to files passed by URL. Direct uploads are limited to 100 MB, or 50 MB on Free.
Questions
Do I need to know FFmpeg?
No. The eight tools take plain options like a format or a width. If you do know FFmpeg, POST /v1/run takes your command with placeholders where the file names go.
How do I get a file in?
Pass a public http or https URL, or upload the file to POST /v1/files and use the file id it returns. URL inputs can be as large as your plan allows. Direct uploads stop at 100 MB, or 50 MB on Free.
How do I get the result?
Add ?wait=25 and a short job returns its result in the same response. 25 seconds is the longest wait. Otherwise you get a job id to check with GET /v1/jobs/:id, or on a paid plan you can pass webhook_url and we POST the result to you.
Do failed jobs cost anything?
A failure on our side is never charged, and neither is an input we couldn't download or one over your plan's size limit. If your command runs and FFmpeg exits with an error, or the job reaches your plan's length limit, the seconds it used count.
What happens when I reach a limit?
On Free, jobs stop after 5 compute minutes until the next calendar month or until you upgrade. On paid plans, usage past your allowance is billed by the second, and jobs stop if you reach your spending cap for the period.
Who can download my output files?
Anyone who has the output URL. Each URL contains a long random token, so treat it like a password. Outputs are kept for 24 hours on Free and 7 days on paid plans.