← All guides

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 with webhooks — no FFmpeg installation or server management required.

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, get results asynchronously via webhooks or polling, 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 for status or use webhooks (recommended for production).

Handle webhook callback

Configure a webhook URL in your FFmpeg Micro dashboard. When a job completes, the API sends a POST request to your endpoint with the job details.

Express.js webhook handler

import express from 'express';

const app = express();
app.use(express.json());

app.post('/webhooks/ffmpeg', async (req, res) => {
  const { id, status, outputUrl } = req.body;

  if (status === 'completed') {
    console.log('Job completed:', id);
    console.log('Download URL:', outputUrl);

    // Save to database, send to client, upload to storage, etc.
    await saveProcessedVideo(id, outputUrl);
  } else if (status === 'failed') {
    console.error('Job failed:', id, req.body.error);
  }

  res.status(200).send('OK');
});

app.listen(3000);

Next.js API route

// app/api/webhooks/ffmpeg/route.ts
import { NextRequest, NextResponse } from 'next/server';

export async function POST(request: NextRequest) {
  const payload = await request.json();
  const { id, status, outputUrl } = payload;

  if (status === 'completed') {
    // Process completed job
    console.log('Download URL:', outputUrl);
    await saveProcessedVideo(id, outputUrl);
  }

  return NextResponse.json({ received: true });
}

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', argument: '' },
    { 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. Use webhooks or 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