Use FFmpeg API in Node.js
Add FFmpeg video processing to your Node.js application with a simple REST API. Transcode videos, generate thumbnails, extract audio, and handle async jobs by polling for status. No FFmpeg installation or server management required.
FFmpeg with Node.js
Learn the whole workflow by building it: uploads, polling, format conversion, audio and frame extraction, batch processing, and production error handling. Ten hands-on lessons.
Why use an API instead of installing FFmpeg in Node
Installing FFmpeg locally (via fluent-ffmpeg or @ffmpeg-installer/ffmpeg) works for development, but creates deployment and scaling challenges in production:
- FFmpeg binaries add 50–100 MB to your Docker image or deployment bundle
- You need to manage FFmpeg versions, codec support, and OS-specific builds across environments
- Video processing is CPU-heavy — running it in your Node.js process blocks the event loop or requires worker threads
- Scaling means provisioning larger instances or managing a separate media-processing queue
With the FFmpeg API, you submit jobs via HTTP, poll for results asynchronously, and pay only for processing time used. No binaries, no queue management, no scaling headaches.
Setup
1. Get Your API Key
Sign up at FFmpeg Micro (free tier includes 100 minutes). Copy your API key from the dashboard.
2. Set Environment Variable
Store your API key in .env:
FFMPEG_API_KEY=your_api_key_here
3. Install Dependencies (Optional)
The API uses standard HTTP — no SDK required. Use Node's built-in fetch (Node 18+) or install axios:
npm install axios
Submit a processing job
Send a POST request to https://api.ffmpeg-micro.com/v1/transcodes with your input file URL and FFmpeg options:
Example: Transcode video to 720p
const response = await fetch('https://api.ffmpeg-micro.com/v1/transcodes', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.FFMPEG_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
inputs: [
{ url: 'https://example.com/input-video.mp4' }
],
outputFormat: 'mp4',
options: [
{ option: '-s', argument: '1280x720' },
{ option: '-c:v', argument: 'libx264' },
{ option: '-preset', argument: 'fast' },
{ option: '-crf', argument: '23' }
]
})
});
const job = await response.json();
console.log('Job ID:', job.id);
console.log('Status:', job.status); // "pending"Response
{
"id": "abc123-def456",
"status": "pending",
"inputUrl": "https://example.com/input-video.mp4",
"outputFormat": "mp4",
"downloadToken": "xyz789",
"createdAt": "2026-05-08T10:30:00Z"
}The job processes asynchronously. Poll the job until its status is completed.
Common workflows
Generate video thumbnail
{
inputs: [{ url: 'https://example.com/video.mp4' }],
outputFormat: 'jpg',
options: [
{ option: '-ss', argument: '00:00:05' },
{ option: '-vframes', argument: '1' },
{ option: '-s', argument: '1280x720' }
]
}Extract audio from video
{
inputs: [{ url: 'https://example.com/video.mp4' }],
outputFormat: 'mp3',
options: [
{ option: '-vn' },
{ option: '-c:a', argument: 'libmp3lame' },
{ option: '-b:a', argument: '192k' }
]
}Trim video clip
{
inputs: [{ url: 'https://example.com/video.mp4' }],
outputFormat: 'mp4',
options: [
{ option: '-ss', argument: '00:00:10' },
{ option: '-t', argument: '30' },
{ option: '-c', argument: 'copy' }
]
}Error and retry handling
Check job status
Poll GET /v1/transcodes/{id} to check job progress:
const statusResponse = await fetch(
`https://api.ffmpeg-micro.com/v1/transcodes/${jobId}`,
{
headers: {
'Authorization': `Bearer ${process.env.FFMPEG_API_KEY}`
}
}
);
const job = await statusResponse.json();
if (job.status === 'completed') {
console.log('Download:', job.outputUrl);
} else if (job.status === 'failed') {
console.error('Error:', job.error);
}Handle API errors
try {
const response = await fetch('https://api.ffmpeg-micro.com/v1/transcodes', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.FFMPEG_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(jobPayload)
});
if (!response.ok) {
const error = await response.json();
throw new Error(`API error: ${error.message}`);
}
const job = await response.json();
return job;
} catch (error) {
console.error('Failed to submit job:', error);
// Retry logic, alert monitoring, etc.
}Production considerations
Idempotency
Include an idempotencyKey in your request to prevent duplicate processing if your client retries:
{
idempotencyKey: 'user-upload-abc123',
inputs: [{ url: 'https://example.com/video.mp4' }],
...
}File size limits
- Input files: 5 GB max per file
- Processing time: 2 hours max per job
- For larger files, consider splitting or compressing before upload
Timeouts
Video processing is asynchronous — the POST request returns immediately with a job ID. Don't set long HTTP timeouts expecting the video to finish inline. Poll at intervals (30s, 1m, etc.) based on your typical job duration.
Download URLs expire
Output URLs are signed and expire after 24 hours. Download the file to your own storage (S3, Cloudflare R2, etc.) as soon as the job completes.
Why FFmpeg API vs self-hosting?
- No FFmpeg binary installation or version management
- No worker threads, job queues, or background process orchestration
- Automatic scaling — handles 1 video or 1,000 videos without infrastructure changes
- Pay only for processing time used (free tier: 100 minutes)
- Same FFmpeg command-line power, accessible via HTTP
Node.js guides
In-depth, code-first walkthroughs for Node.js.