Videos
Video edits
Change a finished video without starting over: redo one scene, rewrite a line, or replace the whole soundtrack. Every edit makes a new video — the original stays untouched, so it is always your undo.
Shared rules
- Edits work on finished videos made with the current engines (
partner_h3t_/partner_h3v_/partner_se_/partner_fs_jobs — an edit can be edited again). Older videos return409 VIDEO_NOT_EDITABLE. The job status sayseditable: truewhen a video qualifies. - Each edit is an async job with its own
workflow_id(partner_se_…for fix-scene and edit-script,partner_fs_…for fix-sound) andsource_workflow_idpointing at the original. Poll it like any video. - An
Idempotency-Keyheader (orclient_request_id) is required (400 IDEMPOTENCY_KEY_REQUIRED) unless you senddry_run: true. dry_run: truereturns the quote and changes nothing.expected_creditsworks as on/videos: a higher server quote →409 QUOTE_MISMATCH.- One open edit per source video at a time (
409 EDIT_IN_PROGRESS, within 30 minutes). - Unknown body fields are rejected with
400 INVALID_BODYon fix-scene and edit-script. fix-sound ignores them, so check the spelling ofexpected_creditsanddry_runthere. - The quote is reserved up front and is a ceiling, exactly like a new video.
See the scenes
Free. Lists the scenes you can target by scene_index.
curl https://platform.promoat.ai/api/partner/v1/videos/partner_h3t_8f2c…/scenes \
-H "Authorization: Bearer $PROMOAT_KEY"{
"workflow_id": "partner_h3t_8f2c…",
"video_type": "talking_head",
"resolution": "768P",
"editable": true,
"voice": { "source": "ai_voice" },
"scenes": [
{ "index": 0, "line": "Hi! Three quick updates from the shop.", "seconds": 8,
"start_seconds": 0, "talks": true, "clip_url": "https://…", "poster_url": "https://…" },
{ "index": 1, "line": "First, we extended our opening hours.", "seconds": 7,
"start_seconds": 8, "talks": true }
]
}video_type is talking_head or moving_scene; voice.source is ai_voice, recording or none. Errors: 404, 409 VIDEO_NOT_READY, 409 VIDEO_NOT_EDITABLE.
Fix a scene
| Field | Type | Description |
|---|---|---|
| scene_index | int 0–99 required | Which scene to redo. |
| description | string ≤500 | Talking head: omit for a new take of the same frame; describe a change for a new frame for that scene (adds an image). Scenes: required (400 FIX_TEXT_REQUIRED). |
| expected_credits | int ≥0 | Optional price lock. |
| dry_run | boolean | Quote only. |
curl -X POST https://platform.promoat.ai/api/partner/v1/videos/partner_h3t_8f2c…/fix-scene \
-H "Authorization: Bearer $PROMOAT_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: fix-4812-scene0-v1" \
-d '{ "scene_index": 0, "expected_credits": 64 }'{
"workflow_id": "partner_se_3b91…",
"status": "queued",
"credits_reserved": 64,
"credits_quoted": 64,
"job_type": "scene_edit_video",
"source_workflow_id": "partner_h3t_8f2c…",
"scene_indices": [0],
"edit_kind": "new_take",
"price_table_version": "2026-09-30",
"duplicate": false
}edit_kind is new_take, new_frame or visual_fix (scenes). A dry run returns { dry_run: true, credits_quoted, credits_required, credits_balance, sufficient_credits, lines, clips, source_workflow_id, scene_indices, edit_kind, price_table_version }. Also 400 INVALID_SCENE (no such scene) and 400 FIX_TEXT_REQUIRED.
Rewrite lines
{ "lines": [{ "scene_index": 1, "line": "…≤600 chars" }] }— 1 to 20 lines. The new lines are spoken in the video's own voice, so this works only for videos voiced by an AI voice. edit_kind is edit_script.
curl -X POST https://platform.promoat.ai/api/partner/v1/videos/partner_h3t_8f2c…/edit-script \
-H "Authorization: Bearer $PROMOAT_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: edit-4812-v2" \
-d '{ "lines": [{ "scene_index": 1, "line": "First, we now open at 7 every morning." }] }'
# → { "workflow_id": "partner_se_…", "status": "queued", "credits_reserved": …, "credits_quoted": …,
# "job_type": "scene_edit_video", "source_workflow_id": "partner_h3t_8f2c…",
# "scene_indices": [1], "edit_kind": "edit_script",
# "price_table_version": "2026-09-30", "duplicate": false }Add "dry_run": true to see credits_quoted first; only the rewritten scenes are made again.
| Error | When |
|---|---|
400 EMPTY_LINE | A line is blank. |
400 LINE_TOO_LONG | The line is too long for its scene; the body includes max_words. |
400 VOICE_EDIT_UNAVAILABLE | The video was voiced by your own recording, is silent, or its sound was replaced with fix-sound. |
400 NO_CHANGES | Every line matches what is already there. |
Replace the sound
Send exactly one new take:
{ "audio_url": "https://…" }— a recording;{ "tts_asset_id": "…" }— one of yourtts_audioassets;{ "voice_id", "script" (≤4000), "voice_settings"?, "language"? }— new speech.
If the source is a scene video in which no scene speaks on camera and the new take is within ±300 ms of its length, the sound is swapped for free (plan swap_audio). Otherwise the video is made again at the take's length (plan regenerate, paid).
For a regenerate plan you must send expected_credits at least equal to the price, or you get 409 { "error": "QUOTE_MISMATCH", "code": "quote_mismatch", credits_quoted, plan }. Call with dry_run: true first.
curl -X POST https://platform.promoat.ai/api/partner/v1/videos/partner_h3t_8f2c…/fix-sound \
-H "Authorization: Bearer $PROMOAT_KEY" \
-H "Content-Type: application/json" \
-d '{ "audio_url": "https://your-cdn.com/audio/new-take-15s.mp3", "dry_run": true }'
# → { "dry_run": true, "source_workflow_id": "partner_h3t_8f2c…", "plan": "regenerate",
# "credits_quoted": 121, "lines": [ … ], "video_ms": 15000,
# "price_table_version": "2026-09-30" }curl -X POST https://platform.promoat.ai/api/partner/v1/videos/partner_h3t_8f2c…/fix-sound \
-H "Authorization: Bearer $PROMOAT_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: fix-sound-4812-1" \
-d '{ "audio_url": "https://your-cdn.com/audio/new-take-15s.mp3", "expected_credits": 121 }'
# → { "workflow_id": "partner_fs_…", "status": "queued", "plan": "regenerate",
# "credits_reserved": 121, "credits_quoted": 121,
# "source_workflow_id": "partner_h3t_8f2c…", "job_type": "fix_sound_video",
# "price_table_version": "2026-09-30", "duplicate": false }Errors: 400 TAKE_TOO_SHORT / 400 TAKE_TOO_LONG ({ code, message, video_ms }), 422 INVALID_URL(the take can't be read).
What edits cost
| Edit | Lines | Total |
|---|---|---|
| Fix scene — new take of an 8 s talking-head scene (768P) | generation 64 | 64 |
| Fix scene — with a description (new frame) | generation 64 + first_frame 4 | 68 |
| Fix sound — new 15 s take on a talking head (768P) | generation 120 + transcription 1 | 121 |
| Fix sound — scene video, no on-camera talking, take within ±300 ms | — | free |
Errors common to all edits
| Status | error | Meaning |
|---|---|---|
| 404 | NOT_FOUND | No such video on your account. |
| 409 | VIDEO_NOT_READY | The source video has not finished. |
| 409 | VIDEO_NOT_EDITABLE | Made with the previous engines, or otherwise not editable. |
| 409 | EDIT_IN_PROGRESS | Another edit of this video is still open. |
| 409 | QUOTE_MISMATCH | The quote is above expected_credits. |
| 400 | IDEMPOTENCY_KEY_REQUIRED | No key and not a dry run. |
| 402 | INSUFFICIENT_CREDITS | Balance below the quote. |
| 429 | TOO_MANY_ACTIVE_JOBS | Concurrent job cap hit. |
| 503 | TRY_AGAIN | Briefly unavailable; nothing charged — retry with the same key. |
More on codes and retries in Errors & limits.