DocsAPI referenceOperations
Join files
Plays the inputs one after another, each trimmed with start and end if you like, as one video or sound file. A part of a video may cross into the one before it with transition. Creates the same Job as POST /v1/jobs, with operation set.
POST /v1/concat
Needs an API key: Authorization: Bearer $VIDEXO_API_KEY.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idempotency-key | header | string, up to 255 characters | Makes retries safe for 24 hours. Up to 255 printable ASCII characters. One key space per account across every endpoint that creates a job. |
Body ConcatRequest
Send the fields of one of these bodies, never both. Each has its own required fields.
ConcatVideoRequest
A video concatenation: every part fitted into a resize box.
| Name | Type | Description |
|---|---|---|
inputs required | array of string or object or object | |
extra_inputs | map of string to string or object | |
webhook_url | string, URL, up to 2,048 characters or null | An https URL that gets the job when it ends (M4). |
metadata | map of string to string, up to 500 characters | Your own string pairs, returned with the job and its webhook: at most 20 keys of up to 40 characters, values up to 500. |
filename | string, up to 105 characters | The stored file name, <name>.<format> by default; one extension, matching format. |
format | one of mp4, mov, webm, mkv, ts, mp3, m4a, aac, wav, flac, ogg, opus, jpg, png, gif, json | The container or image format; from filename, or the type's default. |
trim | object | The part of the input to use: start, and end or duration. |
crop | object | A rectangle of the input frame, in input pixels. |
rotate | number | Degrees clockwise. |
flip | one of horizontal, vertical | Mirror the picture. |
resize | object | A box: width and height, or one of them and aspect; every part is fitted into it. |
color | object | Brightness, contrast, saturation, gamma and hue. |
sharpen | object | An unsharp mask. |
blur | object | A box blur of the whole frame, or of up to 4 regions. |
overlays | array of object | Images or videos drawn over the picture, in order. |
text | array of TextLayer | Up to 8 text layers, drawn after overlays in order. |
subtitles | Subtitles | Captions from an SRT or WebVTT input, burned into the picture at their cue times. |
speed | number, 0.25 to 4 | How fast it plays, 0.25 to 4; the sound keeps its pitch. |
fps | number, 1 to 120 | A constant frame rate. |
fade | object | Fade in at the start and out at the end; out needs trim.duration or trim.end. |
video | object | The video encoder: codec, quality or CRF, preset, maximum bitrate, pixel format. |
audio | boolean or object | false for no sound, or the audio encoder and filters. |
visualize | object | A waveform or spectrum video drawn from the input's sound. |
strip_metadata | boolean | Drop the input's global and stream metadata (GPS, device tags). |
preset | one of web-720p, web-1080p, social-vertical, square | A video preset (GET /v1/presets); its options are the defaults and the output's own fields win. |
type | string | video by default. |
ConcatAudioRequest
An audio concatenation ("type": "audio"): sound only.
| Name | Type | Description |
|---|---|---|
inputs required | array of string or object or object | |
extra_inputs | map of string to string or object | |
webhook_url | string, URL, up to 2,048 characters or null | An https URL that gets the job when it ends (M4). |
metadata | map of string to string, up to 500 characters | Your own string pairs, returned with the job and its webhook: at most 20 keys of up to 40 characters, values up to 500. |
filename | string, up to 105 characters | The stored file name, <name>.<format> by default; one extension, matching format. |
format | one of mp4, mov, webm, mkv, ts, mp3, m4a, aac, wav, flac, ogg, opus, jpg, png, gif, json | The container or image format; from filename, or the type's default. |
trim | object | The part of the input to use: start, and end or duration. |
speed | number, 0.25 to 4 | How fast it plays, 0.25 to 4; the sound keeps its pitch. |
fade | object | Fade in at the start and out at the end; out needs trim.duration or trim.end. |
audio | boolean or object | false for no sound, or the audio encoder and filters. |
strip_metadata | boolean | Drop the input's global and stream metadata (GPS, device tags). |
preset | one of podcast-audio | An audio preset (GET /v1/presets); its options are the defaults and the output's own fields win. |
type required | string |
Request
bash
curl "$VIDEXO_API_URL/v1/concat" \
-H "Authorization: Bearer $VIDEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": [
{
"source": "https://videxo.io/samples/talk.mp4",
"start": 0,
"end": 5
},
{
"source": "https://videxo.io/samples/talk.mp4",
"start": 10,
"end": 15
}
],
"resize": {
"width": 1920,
"height": 1080,
"fit": "pad"
}
}'Copy failed. The text is selected; copy it with your keyboard.
JavaScript
const response = await fetch(`${process.env.VIDEXO_API_URL}/v1/concat`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDEXO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"inputs": [
{
"source": "https://videxo.io/samples/talk.mp4",
"start": 0,
"end": 5
},
{
"source": "https://videxo.io/samples/talk.mp4",
"start": 10,
"end": 15
}
],
"resize": {
"width": 1920,
"height": 1080,
"fit": "pad"
}
}),
});
const job = await response.json();Copy failed. The text is selected; copy it with your keyboard.
Response
202 JSON
{
"id": "job_3k9x0c2mfq8w1n7r5t4b6v2z",
"state": "queued",
"kind": "operations",
"operation": "concat",
"spec": {
"inputs": {
"part_1": {
"url": "https://videxo.io/samples/talk.mp4"
}
},
"outputs": [
{
"name": "video",
"type": "video",
"concat": [
{
"input": "part_1",
"start": 0,
"end": 5
},
{
"input": "part_1",
"start": 10,
"end": 15
}
],
"filename": "video.mp4",
"format": "mp4",
"resize": {
"width": 1920,
"height": 1080,
"fit": "pad"
},
"video": {
"codec": "h264",
"crf": 23,
"encoder_preset": "veryfast",
"pixel_format": "yuv420p",
"faststart": true
},
"audio": {
"codec": "aac",
"bitrate": "128k"
}
}
]
},
"command": "-t 5 -i {{in_part_1}} -ss 10 -t 5 -i {{in_part_1}} -filter_complex \"[0:v]setpts=PTS-STARTPTS,scale=1920:1080:force_original_aspect_ratio=decrease:force_divisible_by=2,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=0x000000,setsar=1,fps=30[o0c0v];[0:a]asetpts=PTS-STARTPTS[o0c0a];[1:v]setpts=PTS-STARTPTS,scale=1920:1080:force_original_aspect_ratio=decrease:force_divisible_by=2,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=0x000000,setsar=1,fps=30[o0c1v];[1:a]asetpts=PTS-STARTPTS[o0c1a];[o0c0v][o0c0a][o0c1v][o0c1a]concat=n=2:v=1:a=1[o0cv][o0ca]\" -map [o0cv] -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -map [o0ca] -c:a aac -b:a 128k -movflags +faststart {{out_video}}",
"input_files": {
"in_part_1": "https://videxo.io/samples/talk.mp4"
},
"output_files": {
"out_video": "video.mp4"
},
"ffmpeg_command": "ffmpeg -t 5 -i https://videxo.io/samples/talk.mp4 -ss 10 -t 5 -i https://videxo.io/samples/talk.mp4 -filter_complex '[0:v]setpts=PTS-STARTPTS,scale=1920:1080:force_original_aspect_ratio=decrease:force_divisible_by=2,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=0x000000,setsar=1,fps=30[o0c0v];[0:a]asetpts=PTS-STARTPTS[o0c0a];[1:v]setpts=PTS-STARTPTS,scale=1920:1080:force_original_aspect_ratio=decrease:force_divisible_by=2,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=0x000000,setsar=1,fps=30[o0c1v];[1:a]asetpts=PTS-STARTPTS[o0c1a];[o0c0v][o0c0a][o0c1v][o0c1a]concat=n=2:v=1:a=1[o0cv][o0ca]' -map '[o0cv]' -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -map '[o0ca]' -c:a aac -b:a 128k -movflags +faststart video.mp4",
"steps": [
{
"tool": "ffmpeg",
"inputs": [
"part_1"
],
"outputs": [
"video"
],
"command": "ffmpeg -t 5 -i https://videxo.io/samples/talk.mp4 -ss 10 -t 5 -i https://videxo.io/samples/talk.mp4 -filter_complex '[0:v]setpts=PTS-STARTPTS,scale=1920:1080:force_original_aspect_ratio=decrease:force_divisible_by=2,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=0x000000,setsar=1,fps=30[o0c0v];[0:a]asetpts=PTS-STARTPTS[o0c0a];[1:v]setpts=PTS-STARTPTS,scale=1920:1080:force_original_aspect_ratio=decrease:force_divisible_by=2,pad=1920:1080:(ow-iw)/2:(oh-ih)/2:color=0x000000,setsar=1,fps=30[o0c1v];[1:a]asetpts=PTS-STARTPTS[o0c1a];[o0c0v][o0c0a][o0c1v][o0c1a]concat=n=2:v=1:a=1[o0cv][o0ca]' -map '[o0cv]' -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -map '[o0ca]' -c:a aac -b:a 128k -movflags +faststart video.mp4"
}
],
"webhook_url": null,
"metadata": {},
"progress": null,
"error": null,
"outputs": [],
"usage": null,
"created_at": "2026-11-04T12:00:00.000Z",
"started_at": null,
"finished_at": null
}Copy failed. The text is selected; copy it with your keyboard.
Responses
| Status | Body | When |
|---|---|---|
| 200 | Job | The job this Idempotency-Key made earlier (same endpoint and body). |
| 202 | Job | The job, queued. |
| 400 | Problem | invalid_request (with errors) or command_invalid (with problems) |
| 401 | Problem | authentication_failed (missing_api_key, invalid_api_key, api_key_revoked, api_key_expired) |
| 402 | Problem | usage_cap_reached or out_of_credits |
| 403 | Problem | permission_denied (account_disabled, token_scope) |
| 404 | Problem | not_found (upload) |
| 409 | Problem | conflict (idempotency_key_reused) |
| 413 | Problem | limit_exceeded (request_body_size): the body is larger than 512 KB |
| 415 | Problem | invalid_request (not_json): the body is not sent as application/json |
| 429 | Problem | rate_limited, with Retry-After in seconds |
| 500 | Problem | internal_error |
| 503 | Problem | service_unavailable (temporary): the account's free credits are still being granted, or a part of the service did not answer |