---
title: "Create a transcode or multi-video composition"
method: POST
path: "/v1/transcodes"
---

# Create a transcode or multi-video composition

`POST /v1/transcodes`

## Request

- Base URL: `https://api.ffmpeg-micro.com`
- URL: `https://api.ffmpeg-micro.com/v1/transcodes`
- Auth: HTTP bearer

Queues a new transcode job with support for single or multiple inputs. Supports simple presets (quality/resolution) or advanced mode (raw FFmpeg options). Billing is based on total input duration (sum of all inputs). Returns a short acknowledgement with `status=pending`; poll `GET /v1/transcodes/{id}` for the full resource.
Quota and plan limits (processing minutes, max input file size) are checked asynchronously after the 201: a job that exceeds them transitions to `failed` with an explanatory `error_message` and `error_category` (`quota` or `file_size`) instead of an HTTP error.

## Request body

- CreateTranscodeRequest
  - `inputs` object[], required: Array of input videos (single for transcode, multiple for composition)
    - `url` string, required: GCS URL (gs://bucket/object) from presigned upload, or public HTTP(S) URL. Prefer gs:// to avoid egress charges.
  - `outputFormat` 'mp4' | 'webm' | 'mov' | 'avi' | 'mp3' | 'm4a' | 'aac' | 'wav' | 'ogg' | 'opus' | 'flac' | 'jpeg' | 'jpg' | 'png' | 'gif' | 'webp', required: Output container format — video (mp4, webm, mov, avi), audio (mp3, m4a, aac, wav, ogg, opus, flac) or image (jpeg/jpg, png, gif, webp). Validation is case/alias-insensitive (`jpg` = `jpeg`), but the value is STORED VERBATIM and reads back exactly as submitted. webm output automatically uses VP9/Opus codecs in preset and default modes; you only need explicit `-c:v`/`-c:a` options when supplying raw options.
  - `preset` object: Simple mode - quality and resolution presets (ignored if options provided)
    - `quality` 'high' | 'medium' | 'low': Maps to CRF values (high=18, medium=23, low=28)
    - `resolution` '480p' | '720p' | '1080p' | '4k': Output resolution preset
  - `options` union[]: Advanced mode - FFmpeg options or virtual options (overrides preset if both provided). **Virtual Options** (prefix with `@`): - `@text-overlay`: Add text overlay with auto word-wrap and styling **FFmpeg Options**: Only allowlisted flags accepted for security.
    - union
      - VirtualOption
        - `option` '@text-overlay', required: Virtual option name (prefixed with @)
        - `argument` TextOverlayConfig, required
          - `text` string, required: Text to display (use \n for manual line breaks, or use charsPerLine for auto word-wrap)
          - `style` object
            - `charsPerLine` integer: Auto word-wrap at this character width (default: no wrap)
            - `font` string: Font name (default: Arial)
            - `fontSize` integer: Font size in pixels (default: 48)
            - `lineSpacing` integer: Space between lines in pixels (default: 10)
            - `fontColor` string: Font color (default: white)
            - `x` string: X position FFmpeg expression (default: (w-text_w)/2 for center)
            - `y` string: Y position FFmpeg expression (default: (h-text_h)/2 for center)
            - `boxBorderW` integer: Box padding/border width in pixels (default: 20)
      - FFmpegOption
        - `option` string, required: FFmpeg flag (e.g., -c:v, -crf, -preset)
        - `argument` string, required: Flag value
  - `metadata` object: Optional caller-supplied metadata for cross-referencing jobs with external systems (for example an original filename or a source record ID). Must be a JSON object; serialized size is capped at 8192 bytes. Stored under the `user` key of the job's `metadata` field, so the submitted value is available at `metadata.user` when fetching or listing jobs, and is echoed as `metadata.user` in the create response.

## Response `201`

Created

- object
  - `id` string, uuid, required
  - `status` 'pending', required: New jobs always start as `pending`
  - `inputUrl` string: First input URL (echo)
  - `outputFormat` string
  - `downloadToken` string: Token for anonymous "Try It" downloads
  - `metadata` object: Present only when the request carried `metadata`; echoed as `metadata.user`
    - `user` object

## Other responses

- `400`: Invalid input
- `401`: Missing/invalid API key
- `402`: The account is fully capped: zero plan minutes remaining AND a zero token balance, so no job of any size can run until the plan renews, the plan is upgraded, or tokens are topped up. Returned synchronously instead of a `201` — no processing is attempted. A job record IS still created (visible in job listings with `status: failed`), and its id is returned so the blocked attempt can be inspected. Automations should treat `402` as terminal for the billing period and STOP retrying: the same request will keep returning `402` until the account's quota state changes. Partially-capped accounts (some minutes or tokens remaining) still return `201`, with any quota failure reported asynchronously on the job's `status`/`error_message`.
- `500`: Unexpected error

---

[API](https://skmtc.dev/ffmpeg-micro/apis/ffmpeg-micro-api.md) · [All operations](https://skmtc.dev/ffmpeg-micro/apis/ffmpeg-micro-api/llms.txt) · [OpenAPI document](https://skmtc.dev/ffmpeg-micro/apis/ffmpeg-micro-api/revisions/64bb930ff345?raw)
