Video processing API. For files, not live streams.
Transcode, compress, trim, join, make thumbnails, GIFs, audio and transcripts. Send one JSON request per job from curl, the TypeScript SDK or any HTTP client; Videxo runs it, stores the files and calls your webhook.
- Processes files
- A video file in, new files out, each with a download link.
- Does not stream
- No live video, player or playback hosting.
curl "$VIDEXO_API_URL/v1/transcode" \
-H "Authorization: Bearer $VIDEXO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "https://example.com/upload.mov",
"filename": "clip-720p.mp4",
"trim": {
"start": 5,
"duration": 30
},
"resize": {
"width": 1280
},
"video": {
"codec": "h264",
"crf": 26
},
"webhook_url": "https://example.com/webhooks/videxo"
}' import { Videxo } from '@videxo/sdk';
const videxo = new Videxo({ apiKey: process.env.VIDEXO_API_KEY, baseUrl: process.env.VIDEXO_API_URL });
const job = await videxo.transcode({
"input": "https://example.com/upload.mov",
"filename": "clip-720p.mp4",
"trim": {
"start": 5,
"duration": 30
},
"resize": {
"width": 1280
},
"video": {
"codec": "h264",
"crf": 26
},
"webhook_url": "https://example.com/webhooks/videxo"
});
const done = await videxo.jobs.wait(job.id); Install with npm i @videxo/sdk, or send the same request with curl or JavaScript. About the SDK
const response = await fetch(`${process.env.VIDEXO_API_URL}/v1/transcode`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VIDEXO_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"input": "https://example.com/upload.mov",
"filename": "clip-720p.mp4",
"trim": {
"start": 5,
"duration": 30
},
"resize": {
"width": 1280
},
"video": {
"codec": "h264",
"crf": 26
},
"webhook_url": "https://example.com/webhooks/videxo"
}),
});
const job = await response.json(); Operations
11 endpoints. One JSON request each.
- 01
Transcode
Convert, compress, resize, trim, crop, watermark and more, one video in, one out.
Writes
- clip-720p.mp4
POST /v1/transcode
{ "input": "https://example.com/upload.mov", "filename": "clip-720p.mp4", "trim": { "start": 5, "duration": 30 }, "resize": { "width": 1280 }, "video": { "codec": "h264", "crf": 26 }, "webhook_url": "https://example.com/webhooks/videxo" } - 02
Thumbnails
Frames as JPEG or PNG images, at the times you name or the most typical one.
Writes
- thumbnail_1.jpg
- thumbnail_2.jpg
- thumbnail_3.jpg
POST /v1/thumbnails
{ "input": "https://example.com/upload.mov", "times": [1, 15, 30], "resize": { "width": 640 } } - 03
Storyboard
Frames at an interval laid out in a grid on one image.
Writes
- storyboard.jpg
POST /v1/storyboard
{ "input": "https://example.com/upload.mov", "interval": 10, "columns": 5, "rows": 4, "tile_width": 320 } - 04
GIF
A few seconds as an animated GIF with a palette made for its frames.
Writes
- preview.gif
POST /v1/gif
{ "input": "https://example.com/upload.mov", "filename": "preview.gif", "trim": { "start": 5, "duration": 4 }, "fps": 12, "resize": { "width": 480 } } - 05
Audio
The sound as MP3, AAC, Opus, Ogg, FLAC or WAV, normalised or mixed.
Writes
- voice.mp3
POST /v1/audio
{ "input": "https://example.com/upload.mov", "filename": "voice.mp3", "audio": { "normalize": true, "bitrate": "192k" } } - 06
Join
Clips and parts of clips one after another in one file.
Writes
- with-intro.mp4
POST /v1/concat
{ "inputs": [ { "source": "https://example.com/intro.mp4", "start": 0, "end": 5 }, { "source": "https://example.com/upload.mov", "start": 10, "end": 40, "transition": { "type": "fade", "duration": 1 } } ], "resize": { "width": 1280, "height": 720 }, "filename": "with-intro.mp4" } - 07
Slideshow
Still images as one video, each held for a time, with a slow zoom or pan and music.
Writes
- slideshow.mp4
POST /v1/slideshow
{ "images": [ "https://example.com/photo-1.jpg", "https://example.com/photo-2.jpg" ], "duration": 4, "motion": "zoom-in", "resize": { "width": 1280, "height": 720 }, "extra_inputs": { "music": "https://example.com/music.mp3" }, "audio": { "input": "music" }, "filename": "slideshow.mp4" } - 08
Scenes
A JSON list of the scenes in a video: where each starts and ends, and how sharp the change was.
Writes
- scenes.json
POST /v1/scenes
{ "input": "https://example.com/upload.mov", "threshold": 12 } - 09
Transcribe
What is said in English or Polish, as JSON with times, SubRip and WebVTT.
Writes
- transcript.json
- transcript.srt
- transcript.vtt
POST /v1/transcribe
{ "input": "https://example.com/upload.mov", "language": "en" } - 10
Probe
The container, duration and streams of a file as JSON, read with ffprobe. Nothing is encoded.
Writes
- metadata.json
POST /v1/probe
{ "input": "https://example.com/upload.mov" } - 11
Several outputs
Many outputs from the same inputs in one job: renditions, a poster and a preview.
Writes
- hd.mp4
- sd.mp4
- poster.jpg
- preview.gif
POST /v1/jobs
{ "inputs": { "upload": "https://example.com/upload.mov" }, "outputs": [ { "name": "hd", "preset": "web-1080p" }, { "name": "sd", "preset": "web-720p" }, { "name": "poster", "preset": "poster" }, { "name": "preview", "preset": "gif-preview" } ], "webhook_url": "https://example.com/webhooks/videxo" }
Each body is compiled at build time as the API compiles it; the files are what its job writes. Several outputs from one upload? The last request does that in one job.
Webhooks
No polling loop. Your route hears when the job ends.
- 1
POST /v1/transcode with webhook_url
Your backend sends the JSON request with its API key. The webhook_url is a public HTTPS endpoint of yours on the default port, on your own domain.
- 2
202: the job, queued
The answer is the job with its id. Keep the id next to your own record; the request does not wait for the video.
- 3
Fetch, run ffmpeg, store
The job reads its inputs, runs and stores each output with its size, SHA-256 and expiry.
- 4
job.succeeded, signed
Videxo POSTs the whole job to your route, signed in the Standard Webhooks format. A failed job sends job.failed with an error code, a cancelled one job.cancelled.
- 5
Verify, answer 2xx
Check the signature over the raw body and answer within 10 seconds. Without a 2xx the delivery is sent again, 8 attempts in all over about a day.
- 6
Download the outputs
Each output carries a signed download URL that lasts up to one hour. Read the job again for a fresh one.
- Events
- job.succeeded job.failed job.cancelled
- Signature
- HMAC-SHA256 in
webhook-signature, withwebhook-idandwebhook-timestamp. - Retries
- 8 attempts over about a day, the same
webhook-idon each.
TypeScript SDK
Typed requests. Webhooks checked for you.
@videxo/sdk has a method per endpoint, with types generated from the API's OpenAPI
document. It runs on Node 20 or newer, Bun, Deno and Cloudflare Workers, retries what is safe to retry and verifies webhook signatures.
npm i @videxo/sdk import { Videxo } from '@videxo/sdk';
const videxo = new Videxo({ apiKey: process.env.VIDEXO_API_KEY, baseUrl: process.env.VIDEXO_API_URL });
const job = await videxo.transcode({
"input": "https://example.com/upload.mov",
"filename": "clip-720p.mp4",
"trim": {
"start": 5,
"duration": 30
},
"resize": {
"width": 1280
},
"video": {
"codec": "h264",
"crf": 26
},
"webhook_url": "https://example.com/webhooks/videxo"
});
const done = await videxo.jobs.wait(job.id); import { Videxo, VidexoWebhookError } from '@videxo/sdk';
const videxo = new Videxo({ apiKey: process.env.VIDEXO_API_KEY });
export async function handleWebhook(request) {
try {
// The raw body as it arrived: parsing and serializing it again breaks the signature.
const event = await videxo.webhooks.verify(await request.text(), request.headers, process.env.VIDEXO_WEBHOOK_SECRET ?? '');
if (event.type === 'job.succeeded') {
for (const output of event.data.outputs) console.log(output.filename, output.download_url);
}
return new Response(null, { status: 204 });
} catch (error) {
if (error instanceof VidexoWebhookError) return new Response(error.code, { status: 400 });
throw error;
}
} The same transcode as at the top. jobs.wait polls until it ends, for scripts without a webhook.
Files in, files out
It makes files. You decide where they play.
Videxo is for the work between an upload and your storage. It does not run live streams, a player or playback hosting.
Adaptive streaming (HLS) and a player Soon
Playback that adapts to the connection, with an embeddable player.
Signed playback Soon
Playback links that only work for the viewers you sign them for.
- Inputs
- Public http or https URLs, or private uploads of up to 100 MB. Up to 1 GB to 10 GB of inputs a job, by plan.
- Outputs
- Stored with their size, SHA-256 and expiry, kept 7 days to 30 days by plan. Download links last up to one hour; delete outputs sooner with one call.
- Longest job
- 10 min to 60 min of processing, by plan.
- Price
- Per minute of output by quality, plus 10 credits a GB of inputs and outputs. At the base rate, video in H.264
at up to 30
fps:
- Video up to 720p 6 credits, $0.006 a minute
- Video 1080p 12 credits, $0.012 a minute
- Audio 2 credits, $0.002 a minute
- GIF 30 credits, $0.03 a minute
- Image: JPEG, PNG or WebP (thumbnail, frame) 1 credit, $0.001 an image
- Transcript, of the media transcribed 8 credits, $0.008 a minute
Questions, answered.
Does it stream video or host a player?
No. Videxo processes files: a video file goes in, new files come out, and your app serves them from wherever it keeps them. There is no live streaming, player or playback hosting today.
Adaptive streaming (HLS) and a player Soon and signed playback Soon are planned.
Which formats can it read and write?
It runs ffmpeg 6.1.1. Outputs can be .mp4 .mov .m4a .mkv .webm .ts .mp3 .aac .wav .flac .ogg .opus .gif .png .jpg .jpeg, with the codecs libx264, libx265, libvpx, libvpx-vp9, libaom-av1, mpeg4, mjpeg, png, gif, ffv1, aac, libmp3lame, libopus, libvorbis, flac, pcm_s16le, pcm_s24le, pcm_f32le, alac. It reads MP4, MOV, MKV, WebM, AVI and MPEG-TS video, MP3, AAC, WAV, FLAC and Ogg audio, and GIF, PNG, JPEG, WebP and BMP images.
Do I need to know ffmpeg?
No. Each job is JSON: a preset such as web-720p, or options such as trim, resize and crf. A job that runs ffmpeg returns its command as ffmpeg_command; steps lists every tool a job runs, such as ffprobe for a probe or whisper for a transcript, where ffmpeg_command is null. If you already have ffmpeg commands, POST /v1/jobs takes them as they are.
Which languages can call it?
Any language with an HTTP client. The TypeScript SDK is on npm as @videxo/sdk, a Swift package covers iOS and macOS apps, and the docs have complete examples for Express, Next.js, SvelteKit, FastAPI, Go, PHP, Laravel, Rails, Swift and Kotlin.
What happens when a job fails?
It ends failed with a documented error code and a sentence that says what to change, and your webhook gets job.failed. A request the API cannot run is refused at once: 400 invalid_request, with each field at fault.
What does it cost to try?
Pay as you go needs no card, and your personal organization gets 500 free credits once, valid 30 days. A minute of 1080p H.264 with 150 MB in and out is 13.5 credits, $0.0135 at the base rate, when ffmpeg runs no longer than 3 times the output's length; time past that costs 10 credits a minute.
Run your first job. Free, no card.
500 free credits to start, no card.