Skip to main content
POST
/
v1
/
run-ffmpeg-command
Run FFmpeg Command
curl --request POST \
  --url https://api.rendi.dev/v1/run-ffmpeg-command \
  --header 'Content-Type: application/json' \
  --header 'X-API-KEY: <api-key>' \
  --data @- <<EOF
{
  "ffmpeg_command": "-i {{in_1}} -vf \"select='lte(t,60)*gt(trunc(t/10),trunc(prev_t/10))',setpts='PTS*0.025',scale=trunc(oh*a/2)*2:320:force_original_aspect_ratio=decrease,pad=trunc(oh*a/2)*2:320:-1:-1\" -an -vsync vfr {{out_1}}",
  "input_files": {
    "in_1": "https://storage.rendi.dev/sample/sample.avi"
  },
  "max_command_run_seconds": 300,
  "metadata": {
    "key": "value"
  },
  "output_files": {
    "out_1": "output1.gif"
  },
  "vcpu_count": 8
}
EOF
import requests

url = "https://api.rendi.dev/v1/run-ffmpeg-command"

payload = {
"ffmpeg_command": "-i {{in_1}} -vf \"select='lte(t,60)*gt(trunc(t/10),trunc(prev_t/10))',setpts='PTS*0.025',scale=trunc(oh*a/2)*2:320:force_original_aspect_ratio=decrease,pad=trunc(oh*a/2)*2:320:-1:-1\" -an -vsync vfr {{out_1}}",
"input_files": { "in_1": "https://storage.rendi.dev/sample/sample.avi" },
"max_command_run_seconds": 300,
"metadata": { "key": "value" },
"output_files": { "out_1": "output1.gif" },
"vcpu_count": 8
}
headers = {
"X-API-KEY": "<api-key>",
"Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.text)
const options = {
method: 'POST',
headers: {'X-API-KEY': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
ffmpeg_command: '-i {{in_1}} -vf "select=\'lte(t,60)*gt(trunc(t/10),trunc(prev_t/10))\',setpts=\'PTS*0.025\',scale=trunc(oh*a/2)*2:320:force_original_aspect_ratio=decrease,pad=trunc(oh*a/2)*2:320:-1:-1" -an -vsync vfr {{out_1}}',
input_files: {in_1: 'https://storage.rendi.dev/sample/sample.avi'},
max_command_run_seconds: 300,
metadata: {key: 'value'},
output_files: {out_1: 'output1.gif'},
vcpu_count: 8
})
};

fetch('https://api.rendi.dev/v1/run-ffmpeg-command', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));
<?php

$curl = curl_init();

curl_setopt_array($curl, [
CURLOPT_URL => "https://api.rendi.dev/v1/run-ffmpeg-command",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'ffmpeg_command' => '-i {{in_1}} -vf "select=\'lte(t,60)*gt(trunc(t/10),trunc(prev_t/10))\',setpts=\'PTS*0.025\',scale=trunc(oh*a/2)*2:320:force_original_aspect_ratio=decrease,pad=trunc(oh*a/2)*2:320:-1:-1" -an -vsync vfr {{out_1}}',
'input_files' => [
'in_1' => 'https://storage.rendi.dev/sample/sample.avi'
],
'max_command_run_seconds' => 300,
'metadata' => [
'key' => 'value'
],
'output_files' => [
'out_1' => 'output1.gif'
],
'vcpu_count' => 8
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"X-API-KEY: <api-key>"
],
]);

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
package main

import (
"fmt"
"strings"
"net/http"
"io"
)

func main() {

url := "https://api.rendi.dev/v1/run-ffmpeg-command"

payload := strings.NewReader("{\n \"ffmpeg_command\": \"-i {{in_1}} -vf \\\"select='lte(t,60)*gt(trunc(t/10),trunc(prev_t/10))',setpts='PTS*0.025',scale=trunc(oh*a/2)*2:320:force_original_aspect_ratio=decrease,pad=trunc(oh*a/2)*2:320:-1:-1\\\" -an -vsync vfr {{out_1}}\",\n \"input_files\": {\n \"in_1\": \"https://storage.rendi.dev/sample/sample.avi\"\n },\n \"max_command_run_seconds\": 300,\n \"metadata\": {\n \"key\": \"value\"\n },\n \"output_files\": {\n \"out_1\": \"output1.gif\"\n },\n \"vcpu_count\": 8\n}")

req, _ := http.NewRequest("POST", url, payload)

req.Header.Add("X-API-KEY", "<api-key>")
req.Header.Add("Content-Type", "application/json")

res, _ := http.DefaultClient.Do(req)

defer res.Body.Close()
body, _ := io.ReadAll(res.Body)

fmt.Println(string(body))

}
HttpResponse<String> response = Unirest.post("https://api.rendi.dev/v1/run-ffmpeg-command")
.header("X-API-KEY", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"ffmpeg_command\": \"-i {{in_1}} -vf \\\"select='lte(t,60)*gt(trunc(t/10),trunc(prev_t/10))',setpts='PTS*0.025',scale=trunc(oh*a/2)*2:320:force_original_aspect_ratio=decrease,pad=trunc(oh*a/2)*2:320:-1:-1\\\" -an -vsync vfr {{out_1}}\",\n \"input_files\": {\n \"in_1\": \"https://storage.rendi.dev/sample/sample.avi\"\n },\n \"max_command_run_seconds\": 300,\n \"metadata\": {\n \"key\": \"value\"\n },\n \"output_files\": {\n \"out_1\": \"output1.gif\"\n },\n \"vcpu_count\": 8\n}")
.asString();
require 'uri'
require 'net/http'

url = URI("https://api.rendi.dev/v1/run-ffmpeg-command")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["X-API-KEY"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"ffmpeg_command\": \"-i {{in_1}} -vf \\\"select='lte(t,60)*gt(trunc(t/10),trunc(prev_t/10))',setpts='PTS*0.025',scale=trunc(oh*a/2)*2:320:force_original_aspect_ratio=decrease,pad=trunc(oh*a/2)*2:320:-1:-1\\\" -an -vsync vfr {{out_1}}\",\n \"input_files\": {\n \"in_1\": \"https://storage.rendi.dev/sample/sample.avi\"\n },\n \"max_command_run_seconds\": 300,\n \"metadata\": {\n \"key\": \"value\"\n },\n \"output_files\": {\n \"out_1\": \"output1.gif\"\n },\n \"vcpu_count\": 8\n}"

response = http.request(request)
puts response.read_body
{
  "command_id": "123e4567-e89b-12d3-a456-426614174000"
}
{
"detail": "Invalid authorization key"
}
{
"detail": [
{
"loc": [
"<string>"
],
"msg": "<string>",
"type": "<string>"
}
]
}
Key naming rules: input keys in input_files must start with in_ (e.g. in_1, in_video); output keys in output_files must start with out_ (e.g. out_1, out_gif). Reference them as {{in_1}} / {{out_1}} placeholders inside ffmpeg_command.Runtime cap: max_command_run_seconds defaults to 300s (5 minutes). It’s a safety cap that aborts runaway FFmpeg jobs — set it explicitly on longer jobs to match the expected runtime. Maximum depends on your plan.Input URLs: any publicly accessible HTTP/HTTPS URL works — Google Drive share links, Dropbox, public/signed S3/GCS, CDNs.

Authorizations

X-API-KEY
string
header
required

Body

application/json

Request model for submitting a single FFmpeg command.

This model allows submitting an FFmpeg command with input and output file specifications. Input file aliases must start with 'in_' and direct to a publicly accessible file, and output file aliases must start with 'out_' and only name a file name to be created.

If you're using an automation platform where curly brackets are used special as characters (like make.com, zapier, or other) add backslashes before the curly brackets, for example, replace {{in_1}} with backslashes and brackets \{\{in_1\}\}.

output_files
required

Dictionary mapping file aliases to their desired output file names, keys are alphanumeric, with underscore allowed, must start with 'out_'. Or, the literal string 'OUTPUT_FOLDER' indicating compressed folder output.

Example:
{
"out_1": "output_one.mp4",
"out_2": "output_two.mp4"
}
ffmpeg_command
string
required

FFmpeg command string using {{alias}} placeholders for input and output files. Input file keys should start with 'in_' prefix, for example: in_video1. Output file keys should start with 'out_' prefix, for example: out_1. If you're using an automation platform where curly brackets are used special as characters (like make.com, zapier, or other) add backslashes before the curly brackets, for example, replace {{in_1}} with backslashes and brackets \{\{in_1\}\}.

Example:

"-i {{in_1}} -i {{in_2}} -filter_complex \"[0:v][1:v]hstack=inputs=2[v]\" -map [v] {{out_1}}"

input_files
Input Files · object | null

Dictionary mapping file aliases to their publicly accessible paths, file name should appear in the end of the url, keys are alphanumeric, with underscore allowed, must start with 'in_'. You can use public file urls, google drive, dropbox, rendi stored files, s3 stored files, etc. as long as they are publicly accessible. If not available, input_compressed_folder has to be provided

Example:
{
"in_1": "https://storage.rendi.dev/sample/big_buck_bunny_720p_5sec_intro.mp4",
"in_2": "https://storage.rendi.dev/sample/big_buck_bunny_720p_5sec_outro.mp4"
}
max_command_run_seconds
number

Maximum allowed runtime in seconds for a single FFmpeg command, the default is 300 seconds

Example:

300

vcpu_count
integer

Number of virtual CPUs to use for a single ffmpeg command, the default is 8, up to your account's maximum. Multiple commands can run in parallel as long as their total vCPU usage doesn't exceed your quota.

Example:

8

metadata
Metadata · object | null

Metadata to be stored with the command, useful for webhooks and custom tracking and reporting. Up to 10 key-value pairs; each key and each string value must be at most 512 characters.

Example:
{ "key": "value" }
is_private
boolean
default:false

If true, the command's output files are placed in private storage and can only be downloaded via a presigned URL (request one by passing presigned_ttl_seconds on the poll endpoint). Not available on free plans.

Example:

false

input_compressed_folder
string | null

A single compressed zip archive containing input media files. Provide either this field or input_files, but not both. Value should be a publicly accessible URL or a rendi storage URL. This field is only available for single FFmpeg commands, not chained commands.

Example:

"https://storage.rendi.dev/sample/playlist_sample.zip"

Response

Successfully submitted FFmpeg command

Response model for command submission.

Contains the unique identifier assigned to the submitted command that can be used to poll for status and retrieve results.

command_id
string<uuid>
required

Unique identifier for the submitted command

Example:

"123e4567-e89b-12d3-a456-426614174000"