---
title: "Edit Video"
method: POST
path: "/v3/videos/{video_id}"
tags: ["Videos"]
---

# Edit Video

`POST /v3/videos/{video_id}`

## Request

- Base URL: `https://api.heygen.com`
- URL: `https://api.heygen.com/v3/videos/{video_id}`
- Auth: one of:
  - API key in header `x-api-key`
  - HTTP bearer

Edits a video and renders it again under the same `video_id`. Send the `edit_version` from GET /v3/videos/{video_id}/scenes; a stale version returns 409. The render is charged as an API render. `Idempotency-Key` is required; if the key store is unavailable, a retry can render, and charge, a second time. Follow the render with GET /v3/videos/{video_id} or a webhook.

## Path parameters

- `video_id` string, required

## Headers

- `Idempotency-Key` string, required

## Request body

- EditVideoRequest: Edit an existing video's scenes and re-render it, keeping its video_id.
  - `edit_version` string, required: The edit_version from the scenes read you are editing from. The edit is refused if the video changed since. The video refuses edits while it renders, and the render updates its document, so after the render finishes, read the scenes again for the next edit.
  - `scenes` ScenePatch[]: Field edits to existing scenes, one entry per scene that changes, at most 1000, and at most 10000 script entries and elements in total. Scenes not listed are unchanged. Applied before any action in 'actions'.
    - `id` string, required: The scene's id, from the scenes read.
    - `background` union: The scene's backdrop: the one colour, image or clip behind everything else, exactly what the scenes read shows as this scene's 'background'. A clip or image the read lists under 'elements' is changed through 'elements', never here, even when it fills the frame. A different 'type' from the current background replaces it; the same 'type' changes only the fields sent.
      - ColorBackgroundPatch: A solid colour background.
        - `type` 'color', required: 'color'.
        - `color` string, required: Hex colour, e.g. '#1a2b3c'.
      - ImageBackgroundPatch: A whole-frame image background.
        - `type` 'image', required: 'image'.
        - `source` union, required: The image: a URL, an uploaded asset id, or base64-encoded data.
          - AssetUrl: Asset input via publicly accessible HTTPS URL.
            - `type` 'url', required: Input type discriminator
            - `url` string, required: Publicly accessible HTTPS URL for the asset
          - AssetId: Asset input via HeyGen asset ID from the asset upload endpoint.
            - `type` 'asset_id', required: Input type discriminator
            - `asset_id` string, required: HeyGen asset ID from the asset upload endpoint
          - AssetBase64: Asset input via base64-encoded content.
            - `type` 'base64', required: Input type discriminator
            - `media_type` string, required: MIME type of the encoded content (e.g. "image/png")
            - `data` string, required: Base64-encoded file content
      - VideoBackgroundPatch: A whole-frame video background.
        - `type` 'video', required: 'video'.
        - `source` union: The clip. Required when the scene's background is not already a video; omit it to keep the current clip.
          - AssetUrl: Asset input via publicly accessible HTTPS URL.
            - `type` 'url', required: Input type discriminator
            - `url` string, required: Publicly accessible HTTPS URL for the asset
          - AssetId: Asset input via HeyGen asset ID from the asset upload endpoint.
            - `type` 'asset_id', required: Input type discriminator
            - `asset_id` string, required: HeyGen asset ID from the asset upload endpoint
          - AssetBase64: Asset input via base64-encoded content.
            - `type` 'base64', required: Input type discriminator
            - `media_type` string, required: MIME type of the encoded content (e.g. "image/png")
            - `data` string, required: Base64-encoded file content
        - `playback` 'freeze' | 'loop' | 'fit_to_scene' | 'full_video': How the clip is reconciled to the scene: 'freeze' holds the last frame, 'loop' repeats it, 'fit_to_scene' changes speed to match, 'full_video' plays it whole and drives the scene's length.
        - `trim` TrimPatch: Seconds trimmed from each end of a clip.
          - `start_offset` number, required: Seconds trimmed from the start of the clip.
          - `end_offset` number, required: Seconds trimmed from the end of the clip.
        - `volume` number: Clip audio volume. 1.0 = source level, 0.0 = silent.
        - `speed_multiplier` number: Playback speed multiplier. 2.0 = twice as fast.
        - `clear` string[]: Fields to empty: 'trim' plays the clip untrimmed.
    - `script` union[]: Changes to this scene's script entries, one per entry that changes, at most 1000. Entries not listed are unchanged.
      - union
        - TextLinePatch: Changes to one synthesized-speech entry of a scene's script.
          - `id` string, required: The id of this script entry, from the scene's 'script' in the scenes read.
          - `type` 'text', required: Must be 'text', matching the stored entry's type.
          - `text` string: The entry's new text, stored as sent apart from spacing around pause marks, inline markup included. It replaces the whole entry, so formatting the old text had is dropped; the voice and its language are kept. The scene is re-synthesized.
          - `voice_id` string: The voice to deliver this entry. Every text entry in a scene must end up with the same voice, so to change a scene's voice, set it on each of the scene's text entries.
          - `voice_settings` TextVoiceSettingsPatch: The voice settings a scene's text entries move to with their new voice. A voice change resets each entry's pitch; ``speed`` and ``volume`` keep the entry's own value when omitted. Bounds are the create API's, so an edit takes exactly the range a new video does.
            - `engine_settings` union: The engine to speak with and its tuning, discriminated by 'engine_type'. Required with 'voice_id': the server never picks an engine for an edit.
              - …
            - `locale` string: Accent for the new voice (e.g. 'zh-HK'). Accepted only where the engine and model speak it for the entry's language.
            - `speed` number: Playback speed multiplier. 0.5 (half speed) to 1.5 (1.5x speed). Omit to keep the entry's own.
            - `volume` number: Voice audio volume. 1.0 = full, 0.0 = silent. Omit to keep the entry's own.
        - AudioLinePatch: Changes to one uploaded-audio entry of a scene's script.
          - `id` string, required: The id of this script entry, from the scene's 'script' in the scenes read.
          - `type` 'audio', required: Must be 'audio', matching the stored entry's type.
          - `source` union: Replacement media: a URL, an uploaded asset id, or base64-encoded data. This is the only way to change the media. The read-back 'url' is not a replacement: send it unchanged or leave it out.
            - AssetUrl: Asset input via publicly accessible HTTPS URL.
              - …
            - AssetId: Asset input via HeyGen asset ID from the asset upload endpoint.
              - …
            - AssetBase64: Asset input via base64-encoded content.
              - …
          - `audio_url` string: The URL exactly as the scenes read returned it. Accepted only when it still names the stored media, so a copied entry can be sent back unchanged. A different URL is refused: use 'source' to replace the media.
        - SilenceLinePatch: Changes to one held silence in a scene's script.
          - `id` string, required: The id of this script entry, from the scene's 'script' in the scenes read.
          - `type` 'silence', required: Must be 'silence', matching the stored entry's type.
          - `duration` number: How long the silence is held, in seconds.
    - `elements` union[]: Changes to what the scene places in front of its backdrop: the avatars, images and clips the scenes read lists under this scene's 'elements', including those inside groups and masks, each addressed by its own id. One entry per element that changes, at most 1000; elements not listed are unchanged.
      - union
        - AvatarElementPatch: Changes to one avatar element. A change to `engine`, `expressiveness` or `motion_prompt` is refused with `edit_not_supported` when the avatar shares its scene with another avatar, or when the video would be left with avatars of different kinds (photo or video avatar) or engines.
          - `id` string, required: The id of this element, from the scenes read. An element inside a group or mask is addressed here by its own id; do not nest it under its container.
          - `type` 'avatar', required: Must be 'avatar', matching the stored element's type.
          - `avatar_id` string: The avatar look to perform this scene.
          - `engine` union: Generation engine for this avatar, as the create API takes it: {'type': 'avatar_iv'}, {'type': 'avatar_v', 'reference_look_id': ...} or {'type': 'avatar_iii'}. Moving an avatar onto or off Avatar III, `more_blinking`, and any engine, expressiveness or motion_prompt change to an expressive or Avatar VI avatar or one created with `more_blinking`, are refused with edit_not_supported. A switch without motion_prompt leaves the avatar with no prompt, as create would.
            - AvatarVEngineConfig: Avatar V engine configuration with cross-reference-driven animation.
              - …
            - AvatarIVEngineConfig: Avatar IV engine configuration (default behavior).
              - …
            - AvatarIIIEngineConfig: Avatar III engine configuration. A single engine value that resolves to the right product by the avatar's look type (mirrors how ``avatar_iv`` already serves both photo and video avatars): - video avatar looks (``digital_twin``, ``studio_avatar``) -> Digital Twin (supports 4K) - ``photo_avatar`` look -> Photo Avatar (no 4K output) Not supported for raw image input (``type: "image"``). ``motion_prompt`` and ``expressiveness`` are not supported with this engine.
              - …
          - `expressiveness` 'high' | 'medium' | 'low': Avatar expressiveness level for photo avatars.
          - `motion_prompt` string: Motion description for this avatar.
          - `clear` string[]: Fields to empty: 'expressiveness' returns it to the default, 'motion_prompt' removes the prompt.
        - ImageElementPatch: Changes to one image placed in the scene, listed under the scene's `elements` in the read. Not the scene's backdrop.
          - `id` string, required: The id of this element, from the scenes read. An element inside a group or mask is addressed here by its own id; do not nest it under its container.
          - `type` 'image', required: Must be 'image', matching the stored element's type.
          - `source` union: Replacement media: a URL, an uploaded asset id, or base64-encoded data. This is the only way to change the media. The read-back 'url' is not a replacement: send it unchanged or leave it out.
            - AssetUrl: Asset input via publicly accessible HTTPS URL.
              - …
            - AssetId: Asset input via HeyGen asset ID from the asset upload endpoint.
              - …
            - AssetBase64: Asset input via base64-encoded content.
              - …
          - `url` string: The URL exactly as the scenes read returned it. Accepted only when it still names the stored media, so a copied entry can be sent back unchanged. A different URL is refused: use 'source' to replace the media.
        - VideoElementPatch: Changes to one video clip placed in the scene, listed under the scene's `elements` in the read. Not the scene's backdrop.
          - `id` string, required: The id of this element, from the scenes read. An element inside a group or mask is addressed here by its own id; do not nest it under its container.
          - `type` 'video', required: Must be 'video', matching the stored element's type.
          - `source` union: Replacement media: a URL, an uploaded asset id, or base64-encoded data. This is the only way to change the media. The read-back 'url' is not a replacement: send it unchanged or leave it out.
            - AssetUrl: Asset input via publicly accessible HTTPS URL.
              - …
            - AssetId: Asset input via HeyGen asset ID from the asset upload endpoint.
              - …
            - AssetBase64: Asset input via base64-encoded content.
              - …
          - `url` string: The URL exactly as the scenes read returned it. Accepted only when it still names the stored media, so a copied entry can be sent back unchanged. A different URL is refused: use 'source' to replace the media.
          - `playback` 'freeze' | 'loop' | 'fit_to_scene' | 'full_video': How the clip is reconciled to the scene: 'freeze' holds the last frame, 'loop' repeats it, 'fit_to_scene' changes speed to match, 'full_video' plays it whole and drives the scene's length.
          - `trim` TrimPatch: Seconds trimmed from each end of a clip.
            - `start_offset` number, required: Seconds trimmed from the start of the clip.
            - `end_offset` number, required: Seconds trimmed from the end of the clip.
          - `volume` number: Clip audio volume. 1.0 = source level, 0.0 = silent.
          - `speed_multiplier` number: Playback speed multiplier. 2.0 = twice as fast.
          - `clear` string[]: Fields to empty: 'trim' plays the clip untrimmed.
    - `clear` string[]: Fields to empty: 'background' removes the scene's background.
  - `actions` union[]: Scene inserts, deletions and reordering, applied in order after the field edits in 'scenes' and before the render, at most 1000 actions and 50 inserts, whose image scenes' set durations total at most 1800s. The video must keep at least one scene, and an edit cannot grow it past 50 scenes: a video that already has more can be edited, but not grown. Anything the scenes read shows as a field is changed through 'scenes', never here.
    - union
      - InsertSceneAction: Add a new scene, described in the same vocabulary as a studio create request.
        - `type` 'insert_scene', required: 'insert_scene'.
        - `ref` string, required: A name you choose for the new scene, unique within this request. Later actions in the same request refer to the new scene by it, and the response maps it to the scene's id.
        - `after_scene_id` string: Insert after this scene: an existing scene id or the ref of a scene inserted earlier in this request.
        - `before_scene_id` string: Insert before this scene: an existing scene id or the ref of a scene inserted earlier in this request.
        - `scene` union, required: The new scene's content, in the same form as a scene in a studio create request.
          - AvatarVideoScene: A whole-frame speaking scene backed by an avatar.
            - `type` 'avatar_video', required: Must be 'avatar_video' for an avatar speaking scene.
            - `input` StudioAvatarInput, required: Avatar-driven source for an ``avatar_video`` studio scene. Mirrors the standalone ``avatar`` creation mode (``CreateVideoFromAvatar``) minus the output settings, which are global on the studio request. An audio source is required: exactly one of (script + voice_id), audio_url, or audio_asset_id.
              - …
          - ImageScene: A whole-frame still-image scene: either silent (held for ``duration``) or narrated. Exactly one mode must be chosen: - silent: set ``duration`` (seconds) and no audio source. - narrated: set exactly one audio source (script + voice_id, audio_url, or audio_asset_id) and omit ``duration`` — the scene length follows the audio.
            - `type` 'image', required: Must be 'image' for a still-image scene.
            - `source` union, required: Still image to display. Accepts URL, asset ID, or base64-encoded data.
              - …
            - `duration` number, nullable: Silent mode: hold the still image for this many seconds. Mutually exclusive with any audio source. Must be > 0 and <= 300.
            - `script` string, nullable: Narrated mode: text to speak over the image. Pair with voice_id. Mutually exclusive with duration/audio_url/audio_asset_id.
            - `voice_id` string, nullable: Voice ID for text-to-speech. Required when script is provided.
            - `audio_url` string, nullable: Narrated mode: public URL of an audio file to play over the image. Mutually exclusive with duration/script.
            - `audio_asset_id` string, nullable: Narrated mode: HeyGen asset ID of an uploaded audio file. Mutually exclusive with duration/script.
            - `voice_settings` VoiceSettingsInput: Voice tuning parameters for text-to-speech. Applies only when 'script' + 'voice_id' are provided — not when audio_url/audio_asset_id is used (uploaded audio bypasses TTS).
              - …
          - VideoScene: A whole-frame scene backed by an existing video clip. Optional ``playback`` exposes the audio volume / mute capability; when omitted the clip plays at its source volume. For optional voiceover / narration, supply at most one audio source (``script`` + ``voice_id``, ``audio_url``, or ``audio_asset_id``) — the *same* audio inputs a narrated ``image`` scene accepts. When present, the narration drives the scene length and ``playback.mode`` controls whether the clip freezes, loops, or changes speed to fit that duration. When omitted the clip plays full-length as before. The clip's own audio level is still governed by ``playback`` (the two compose).
            - `type` 'video', required: Must be 'video' for a video-clip scene.
            - `source` union, required: Video clip to include. Accepts URL, asset ID, or base64-encoded data.
              - …
            - `playback` PlaybackInput: Playback and audio capability for a video-clip scene. ``mode`` controls how a narrated clip is reconciled to the voiceover-driven scene duration. ``volume`` must be between 0.0 and 1.0.
              - …
            - `script` string, nullable: Optional voiceover: text to speak over the clip. Pair with voice_id. Mutually exclusive with audio_url/audio_asset_id.
            - `voice_id` string, nullable: Voice ID for text-to-speech. Required when script is provided.
            - `audio_url` string, nullable: Optional voiceover: public URL of an audio file to play over the clip. Mutually exclusive with script/audio_asset_id.
            - `audio_asset_id` string, nullable: Optional voiceover: HeyGen asset ID of an uploaded audio file. Mutually exclusive with script/audio_url.
            - `voice_settings` VoiceSettingsInput: Voice tuning parameters for text-to-speech. Applies only when 'script' + 'voice_id' are provided — not when audio_url/audio_asset_id is used (uploaded audio bypasses TTS).
              - …
      - DeleteSceneAction: Remove an existing scene.
        - `type` 'delete_scene', required: 'delete_scene'.
        - `scene_id` string, required: The id of the scene to remove, from the scenes read.
      - SetSceneOrderAction: Put the scenes in a new order.
        - `type` 'set_scene_order', required: 'set_scene_order'.
        - `scene_ids` string[], required: Every scene the video will have, in the new order: existing scene ids and refs of scenes inserted in this request, each exactly once, leaving out deleted scenes. Leaving a scene out does not delete it; it is refused.
  - `callback_url` string: Webhook URL to receive a POST notification when the video is ready.
  - `callback_id` string: Caller-defined identifier echoed back in the webhook payload.

## Response `200`

Successful response

- object
  - `error` object, nullable
  - `data` EditVideoResponse: The render this edit started.
    - `video_id` string, required: The edited video. Unchanged by the edit.
    - `status` 'pending' | 'processing' | 'completed' | 'failed', required
    - `version_id` string, required: The version this render creates, as Studio's version history lists it.
    - `inserted_scenes` InsertedScene[], nullable: The ids given to scenes this request inserted.
      - `ref` string, required: The ref the request gave the scene.
      - `scene_id` string, required: The new scene's id, as later scenes reads return it.

## Other responses

- `400`: Invalid request parameters
- `401`: Authentication failed
- `402`: Insufficient credit
- `404`: Resource not found
- `409`: Conflict — `error.code` is one of: `stale_edit_version`, `resource_not_ready`, `conflict`, `request_in_progress`.
- `429`: Rate limit exceeded
- `503`: Temporarily unavailable — the request did not complete and is safe to retry.

## Changes

- **2026-11-03** `7dfa5e59b4f5`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/heygen/apis/heygen-external-api/changes/v3/videos/:video_id/post.md)

---

[API](https://skmtc.dev/heygen/apis/heygen-external-api.md) - [All operations](https://skmtc.dev/heygen/apis/heygen-external-api/llms.txt) - [OpenAPI document](https://skmtc.dev/heygen/apis/heygen-external-api/revisions/7dfa5e59b4f5?raw)
