DocsGuides
Read media metadata
Read the container, duration and streams of a file with ffprobe, without encoding anything.
POST /v1/probe reads one file with ffprobe and answers its container, duration and streams:
codec, size, frame rate, rotation, bitrate, audio channels and sample rate. Nothing is encoded.
The request#
curl "$VIDEXO_API_URL/v1/probe" \
-H "Authorization: Bearer $VIDEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "https://example.com/talk.mp4"
}'Copy failed. The text is selected; copy it with your keyboard.
const response = await fetch(`${process.env.VIDEXO_API_URL}/v1/probe`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDEXO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"input": "https://example.com/talk.mp4"
}),
});
const job = await response.json();Copy failed. The text is selected; copy it with your keyboard.
The API waits up to 25 seconds for the job to end. Most probes end well within that. The answer is
the job, with what ffprobe read in probe:
{
"id": "job_3k9x0c2mfq8w1n7r5t4b6v2z",
"state": "succeeded",
"kind": "operations",
"operation": "probe",
"spec": {
"inputs": {
"source": {
"url": "https://videxo.io/samples/talk.mp4"
}
},
"outputs": [
{
"name": "metadata",
"type": "metadata",
"input": "source",
"filename": "metadata.json",
"format": "json"
}
]
},
"command": null,
"input_files": null,
"output_files": null,
"ffmpeg_command": null,
"steps": [
{
"tool": "ffprobe",
"inputs": [
"source"
],
"outputs": [
"metadata"
],
"command": null
}
],
"webhook_url": null,
"metadata": {},
"progress": null,
"error": null,
"outputs": [
{
"alias": "out_metadata",
"name": "metadata",
"filename": "metadata.json",
"size_bytes": 612,
"sha256": "9f2c4e1a7b3d5f60c8e2a4b6d8f0e1c3a5b7d9f1e3c5a7b9d1f3e5c7a9b1d3f5",
"content_type": "application/json",
"expires_at": "2026-11-05T12:01:09.000Z",
"download_url": "https://api.videxo.io/v1/files/job_3k9x0c2mfq8w1n7r5t4b6v2z/out_video?account=acc_example&expires=1793797269&signature=example",
"download_url_expires_at": "2026-11-04T13:01:09.000Z",
"deleted_at": null
}
],
"usage": {
"seconds": 1,
"input_bytes": 48000000,
"output_bytes": 612,
"credits": 1,
"bill": {
"lines": [
{
"kind": "metadata",
"credits": 0
}
],
"data": {
"input_bytes": 48000000,
"output_bytes": 612,
"credits": 0.48
},
"processing": {
"seconds": 1,
"included_seconds": 0,
"credits": 0.167
}
}
},
"created_at": "2026-11-04T12:00:00.000Z",
"started_at": "2026-11-04T12:00:01.000Z",
"finished_at": "2026-11-04T12:00:04.000Z",
"probe": {
"container": "mov,mp4,m4a,3gp,3g2,mj2",
"duration": 62.4,
"start_time": 0,
"size_bytes": 48000000,
"bit_rate": 6153846,
"tags": {
"major_brand": "isom",
"creation_time": "2026-10-30T09:12:00.000000Z"
},
"streams": [
{
"index": 0,
"type": "video",
"codec": "h264",
"profile": "High",
"duration": 62.4,
"bit_rate": 6000000,
"width": 1920,
"height": 1080,
"rotation": 0,
"fps": 29.97,
"pixel_format": "yuv420p",
"frames": 1870,
"cover_art": false,
"tags": {
"language": "eng"
}
},
{
"index": 1,
"type": "audio",
"codec": "aac",
"profile": "LC",
"duration": 62.38,
"bit_rate": 128000,
"channels": 2,
"channel_layout": "stereo",
"sample_rate": 48000,
"tags": {
"language": "eng"
}
}
]
}
}Copy failed. The text is selected; copy it with your keyboard.
200: the job ended. When it succeeded,probeholds the document.202: the job is still queued or running, andprobeisnull. Read the job withGET /v1/jobs/{id}, or wait for its webhook. The document is the job's output file,metadata.json.
Send ?wait=false to answer at once without waiting: 202, or 200 if the job has already ended. A job's input is fetched in full before it is read, so
a large file takes as long as its download.
What the document says#
| Field | Meaning |
|---|---|
container | ffprobe's name for the format, such as mov,mp4,m4a,3gp,3g2,mj2. |
duration | Seconds: the latest end among the container and its streams, from the start of the file. |
start_time | The seconds the file starts at. Most files start at 0. |
size_bytes, bit_rate | Of the whole file. |
tags | Tags from a fixed list (below). |
streams | At most 64, in file order. |
Every stream has index, type (video, audio, subtitle, data or attachment), codec,
profile, duration, bit_rate and tags.
- A video stream adds
widthandheightas stored,rotation(degrees clockwise to display it),fps(the average frame rate),pixel_format,framesandcover_art.cover_artistruefor an attached picture, such as album art, which is not a video. - An audio stream adds
channels,channel_layoutandsample_rate.
A value the file does not state, or states in a form we do not accept, is null. New fields may be
added; the ones here keep their meaning.
Tags#
Only these tags are copied: title, artist, album, album_artist, composer, genre, date,
creation_time, comment, description, copyright, encoder, language, handler_name,
major_brand, track and disc. Each value is at most 256 characters, with control characters
removed. Location and device tags are never copied.
With other outputs#
A metadata output reads an input in a job with any other outputs, and the input is fetched once
for all of them. It takes name, type, input and filename, and writes <name>.json:
curl "$VIDEXO_API_URL/v1/jobs" \
-H "Authorization: Bearer $VIDEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"inputs": {
"talk": "https://example.com/talk.mp4"
},
"outputs": [
{
"name": "web",
"preset": "web-720p"
},
{
"name": "info",
"type": "metadata"
}
]
}'Copy failed. The text is selected; copy it with your keyboard.
const response = await fetch(`${process.env.VIDEXO_API_URL}/v1/jobs`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDEXO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"inputs": {
"talk": "https://example.com/talk.mp4"
},
"outputs": [
{
"name": "web",
"preset": "web-720p"
},
{
"name": "info",
"type": "metadata"
}
]
}),
});
const job = await response.json();Copy failed. The text is selected; copy it with your keyboard.
ffmpeg \
-i https://example.com/talk.mp4 \
-map 0:v:0 \
-vf "scale='min(iw,1280)':'min(ih,720)':force_original_aspect_ratio=decrease:force_divisible_by=2" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p \
-map "0:a:0?" \
-c:a aac -b:a 128k -movflags +faststart \
web.mp4Copy failed. The text is selected; copy it with your keyboard.
The document is an output file like any other, with a download link. command and ffmpeg_command
on the job describe the ffmpeg part only. When every output is metadata, no ffmpeg runs and
command, input_files, output_files and ffmpeg_command are null.
What it refuses#
ffprobe's own options are not part of the API: no field reaches its arguments, and a field the
output does not take is 400 invalid_request. It reads the same single-file formats as ffmpeg does
here (Command rules), and never opens another file or address a file names. A
playlist, a concat list or anything else that is not one of those formats fails the job with
processing_failed and the reason media_unreadable.
Credits#
A metadata output adds no credits of its own. Its job pays for the input as fetched and the
document as stored as data, and at least 1 credit
(Limits per plan).