# FFmpeg Merge Audio and Video (assembly) > Merge Audio and Video puts an audio track onto a finished clip. It replaces the clip's audio; it does not mix. Status: roasOS API v1.1. Catalog `model-catalog@2026-10-06.v5`, pricing `customer-pricing@2026-11-05.v15`. The endpoint `POST /v1/assembly/merge-audio-video` is live. `POST /v1/generations` has no `assembly` type, so this endpoint is the only path to this model. ## Endpoint POST https://api.roasos.com/v1/assembly/merge-audio-video Auth: Authorization: Bearer roasos_sk_… (scope api:write). Idempotency-Key required. quote_only / quote_id / webhook_url as in https://docs.roasos.com/llms.txt. ## Good at Drop a generated voice-over or a music bed onto a finished cut. `start_offset_seconds` delays the audio relative to the video, and it is in SECONDS on this operation — Compose counts milliseconds and Images to Video counts frames, and nothing converts between them for you. Use Compose instead when you need the source audio kept, or two audio sources together: this operation replaces the track. The price is one credit, flat. ## Pricing Unit: operation. Pricing key: `merge-audio-video`. | request | credits | |---|---| | `per_operation` | 1 | Credits come from the roasOS pricing table (`MODEL_COSTS`) through the catalog. No other field changes the price. ## Modes - `merge-audio-video` Provider endpoints: `fal-ai/ffmpeg-api/merge-audio-video`. ## Request fields | field | type | required | default | rule | |---|---|---|---|---| | `audio_url` | string | yes | — | https URL; ≤ 4,096 characters | | `quote_id` | string | no | — | UUID | | `quote_only` | boolean | no | `false` | — | | `start_offset_seconds` | number | no | `0` | 0–600 | | `video_url` | string | yes | — | https URL; ≤ 4,096 characters | | `wait_timeout_seconds` | integer | no | `0` | 0–60 | | `webhook_url` | string | no | — | https URL; ≤ 4,096 characters | The schema is strict: a field that is not in this table returns `400 unsupported_option`. Field names are the roasOS role names, one name per role across every family. ## Media - `video_url` — the source video - `audio_url` — the driving audio track | role | items | max size | content types | duration | |---|---|---|---|---| | `audio_url` | ≤ 1 | 15 MB | `audio/mp3`, `audio/mpeg`, `audio/wav`, `audio/wave`, `audio/x-wav` | — | | `video_url` | ≤ 1 | 100 MB | `video/mp4` | 0–180 s | - Refused: `avatar_id`, `elements`, `product_id`, `product_images`, `references`, `reference_images`, `image_url`, `end_image_url`, `scene_id`, `scene_image`, `video_urls`, `audio_urls`, `voice_id`. A refused role returns `400 unsupported_option`. - Every URL must be an immutable object on `https://cdn.roasos.com/…` that your workspace owns; any other URL is `400 reference_media_not_workspace_owned`. Upload with `POST /v1/uploads` (direct POST of the bytes to `put_url`, or a `source_url` import), then use the returned CDN URL. ## Prompt None. This operation takes no prompt. ## Not exposed The catalog records no pinned or hidden provider field for this model. ## Result Result kind `video`. Always present: `url`. ```json { "data": { "id": "gen_…", "kind": "video", "model": "merge-audio-video", "status": "completed", "result": {"url":"https://cdn.roasos.com/workspaces/ws_01/result"} } } ``` Files live on `https://cdn.roasos.com` and are immutable; a result URL can be a media input to the next call. Poll `GET /v1/jobs/{id}/status` → `{ id, status, updated_at }`, then `GET /v1/jobs/{id}/result` (`409 run_not_completed` until the job is `completed`, `failed` or `canceled`). Statuses: `queued → processing → completed | failed | canceled`. ## Example ```bash # 1. quote — nothing is spent curl -X POST https://api.roasos.com/v1/assembly/merge-audio-video \ -H "Authorization: Bearer roasos_sk_live_…" \ -H "Idempotency-Key: 0f1c9e2a-merge-audio-video-0001" \ -H "Content-Type: application/json" \ -d '{"quote_only":true,"audio_url":"https://cdn.roasos.com/workspaces/ws_01/uploads/audio_url.mp3","video_url":"https://cdn.roasos.com/workspaces/ws_01/uploads/video_url.mp4"}' # → 200 { "data": { "kind": "quote", "quote_id": "q_7Hk…", "credits": 1, "expires_at": "…", "confirmation": { "model": "merge-audio-video", "example": "per_operation" } } } # 2. run — the same body and the same Idempotency-Key, quote_id instead of quote_only curl -X POST https://api.roasos.com/v1/assembly/merge-audio-video \ -H "Authorization: Bearer roasos_sk_live_…" \ -H "Idempotency-Key: 0f1c9e2a-merge-audio-video-0001" \ -H "Content-Type: application/json" \ -d '{"quote_id":"q_7Hk…","audio_url":"https://cdn.roasos.com/workspaces/ws_01/uploads/audio_url.mp3","video_url":"https://cdn.roasos.com/workspaces/ws_01/uploads/video_url.mp4"}' # → 201 { "data": { "id": "gen_…", "kind": "video", "model": "merge-audio-video", "status": "queued", "credits_reserved": 1, "links": { "status": "/v1/jobs/gen_…/status", "result": "/v1/jobs/gen_…/result" } } } # 3. poll curl https://api.roasos.com/v1/jobs/gen_…/status -H "Authorization: Bearer roasos_sk_live_…" curl https://api.roasos.com/v1/jobs/gen_…/result -H "Authorization: Bearer roasos_sk_live_…" ``` ## Today The endpoint is `POST /v1/assembly/merge-audio-video`. It is live. ## See also https://docs.roasos.com/models/assembly/merge-audio-video · https://api.roasos.com/v1/catalog/assembly/merge-audio-video · fal-ai/ffmpeg-api/merge-audio-video