WideCast.ai

Edit one scene — POST /v1/modify_scene

Synchronous for most branches (some upload branches are async — see below), no credit charged until /v1/export_video re-renders the final MP4. After the user reviews scenes (via result.review_url from /v1/create_video) and asks to edit a specific scene, call this endpoint to apply the change in place. Successful edits publish MQTT realtime to every open scene editor, so the user sees the change live.

Agent rule — data-first. Call /v1/video_data first and use voice_file (the stable per-scene UID, also the base of {voice_file}_spec.json) as the selector. For layout edits, also call /v1/scene_geometry to read narrator / caption / Remotion object boxes in 280×498 editor-preview coords without rendering anything. segment.id is only current display/order metadata and may change after reorder/add/delete.

The endpoint supports thirteen edit branches, grouped by family. Pick exactly one family per call — the only intentional multi-family call is layout.batch (G), which composes layout-only children.

Branch Use it when Field family
(A) Background media swap Replace the background image/video on a scene. mediaUrl (+ optional mediaType)
(B) Upload Overlay Drop in an agent-supplied asset (raster image or SVG) and recompute the Remotion overlay spec. SVG with <g data-wc-object> groups = pixel-perfect deterministic decomposition. NOT Regenerate Overlay (paid). remotion.upload_overlay
(C) Object-layer rect (preferred overlay layout) Move / resize a Remotion overlay object — read boxes.remotion.object_layer.objects from /v1/scene_geometry first. remotion.object.rect
(D) Storyboard group rect (low-level wrapper edit) Move / resize the entire Storyboard group; prefer (C) for visible overlay layout. remotion.group.rect
(E) Narrator rect Move / resize the narrator box in legacy 280×498 editor coords. overlay.narrator.rect / overlay.narrator.x|y|w|h
(F) Caption Y Vertical-only caption placement in 280×498 coords. overlay.caption.y
(G) Layout batch Atomic narrator + caption + Remotion layout changes (one persist + one MQTT). layout.batch (or multiple layout fields directly)
(H) Upload Voice (async) User-supplied narration audio (transcribed + retimed). voice.upload
(I) Upload Narrator Video (async) User-supplied A-roll narrator video. narrator.upload_video
(J) A/B-roll switch Toggle the active roll without uploading anything. roll.active / roll.switch
(K) Segment text Correct narration/caption text while keeping audio timing. segment.text
(L) Scene metadata Update scene planning metadata (pattern, type, …). metadata fields
(M) Remotion add element (add-only) Append a new text / stat / label / callout / image object to the overlay; follow up with (C) layout.batch for placement. remotion.add_element
(N) Disable overlay Hide the Remotion overlay on one scene by setting remotion_spec="none" (spec/poster files preserved). disable_overlay (aliases: remotion.disable_overlay / overlay.disable / remotion_spec.none)
(O) Overlay text edit (0-LLM, SVG posters) Retype the words and/or recolor an existing SVG-sourced overlay object in place (value={wc_object, lines?, fill?}) — SVG + spec patched, layout preserved; poster copy rules returned as warnings. overlay.text_edit
POST/v1/modify_scene

(A) Background swap — image, by voice_file

Most reliable. Roll-aware — B-roll updates mediaUrl/brollUrl; A-roll registers as brollUrl/user_asset_url without disturbing the narrator.

Loading…

(A) Background swap — match by narration text, video

Fuzzy match on the narration. May return a clarification — pick a voice_file and retry.

Loading…

(B) Upload Overlay — agent-supplied PNG → Remotion spec (raster path, FREE)

NOT Regenerate Overlay (paid). Raster path: classifier-driven graphic-vs-realistic decomposition with strict no-AI fallback. Prefer a portrait 720×1280 transparent PNG; ground the image in scene context (text/talking_point/visual/quote/keyword/type).

Loading…

(B) Upload Overlay — agent-authored SVG → svg2spec (PREFERRED, FREE)

URL ending .svg triggers svg2spec. Required: viewBox 0 0 720 1280. Each `` group becomes one Storyboard object with pixel-perfect placement (NO auto-fit). Per-group attrs: data-wc-object (REQUIRED stable id; unmarked groups become background), data-wc-kind ∈ {object,text,bar,mark,callout}, data-wc-anim ∈ {slide-up,slide-down,slide-left,slide-right,pop,grow-x,grow-y,fade,…}, data-wc-z (paint order). Deterministic — no classifier. SVG errors → 422 svg2spec_failed.

Loading…

(O) Overlay text edit — retype a poster line (0-LLM, SVG-sourced)

Changes ONLY the words of one overlay object. Server rewrites {voice_file}_overlay.svg, re-rasterizes only the touched group, patches the spec in place (layout preserved). Requires spec._source=svg2spec (endpoint posters / uploaded SVG overlays); typography/image scenes return 422 no_svg_overlay_source. Poster copy rules (≤3 lines, ≤4 words/line, uppercase) come back as applied.warnings only. Thumbnail clone is not auto-synced — repeat the edit on the thumbnail voice_file.

Loading…

(C) Object-layer rect — preferred overlay layout (one_by_one)

Read boxes.remotion.object_layer.objects from /v1/scene_geometry; pass the logical *.one_by_one layout_id to transform all timed sequence items together. WideCast keeps the group wrapper unchanged.

Loading…

(C) Object-layer rect — move a single overlay text object

Per-object layout_id (e.g. main.obj_03_text). Server maps the preview rect onto raw Storyboard offsets/sizes.

Loading…

(D) Group rect — move whole Storyboard group up by 120px (canvas)

Low-level wrapper edit — child objects untouched. Canvas (720×1280) is NOT clamped; negative y is allowed.

Loading…

(D) Group rect — resize + scale children

Resize triggers `scale_children`: wrapper + every child object + root background.bbox scale together. `wrapper_only` is advanced.

Loading…

(E) Narrator layout rect — 280×498 preview coords

Preserves narrator metadata + source-space segment.narrator_face. Marks overlay.narrator.touched=true so auto-fit doesn't overwrite. No media/audio/timeline/Remotion-spec changes.

Loading…

(F) Caption Y — vertical placement only (280×498)

ONLY Y — does not change x/w/h, visibility, config, segment.text, segment.words, audio, duration, or Remotion specs.

Loading…

(G) Layout batch — multiple layout fields directly

One persist, one MQTT scene_modified. Allowed children: overlay.narrator.*, overlay.caption.y, remotion.object.rect, remotion.group.rect.

Loading…

(G) Layout batch — via the explicit `layout.batch` envelope

Same composer, alternate request shape. Useful when the orchestrator already knows it's batching.

Loading…

(H) Upload Voice — user-supplied audio (ASYNC; queued)

Free; not Use-AI-Voice. Returns scene_voice_upload_queued + queue_id. Wait for MQTT scene_voice_upload_applied, then call /v1/video_data to verify.

Loading…

(I) Upload Narrator Video — user-supplied A-roll (ASYNC; queued)

Free; user-supplied video. If target is B-roll, server preserves the previous background and switches to A-roll on completion.

Loading…

(J) A/B-roll switch — show narrator A-roll

Sync data switch. Preserves both lanes; swaps the active runtime fields. A→B mirrors the editor's _switched file-family protection.

Loading…

(K) Segment text correction

Rebuilds word timings over existing audio; no duration/timeline change.

Loading…

(L) Scene metadata — validated pattern + quote

pattern is validated against ai_segment_text canonical values; type ∈ {HOOK, STAT, KEY POINT, DATA, FACT, CALL TO ACTION}. pattern='typography_only' auto-clears visual.

Loading…

(M) Remotion add element — bare-string shortcut for kind=text

Add-only: appends a new Storyboard text object. Returns a new layout_id; follow up with layout.batch + remotion.object.rect to land it inside safe zones.

Loading…

(M) Remotion add element — stat with position hint

kind=stat draws value + label. position picks a server-side default rect on the named side (top/bottom/left/right/center).

Loading…

(M) Remotion add element — image with explicit 280×498 preview rect

kind=image places the URL inline. Use rect to pin the position; coordinate_space defaults to 'preview' (280×498).

Loading…

(N) Disable overlay — hide Remotion overlay without deleting the spec

Sets segment.remotion_spec='none' (opt-out sentinel). Does NOT delete {voice_file}_spec.json or the overlay poster. Must be sent ALONE. Re-enable later via (B) Upload Overlay.

Loading…

segment.remotion_spec == "none" means the user intentionally disabled the overlay on that scene (via Branch (N) or UI). Layout edits return remotion_spec_disabled. Do not auto-enable — restore the overlay only with Upload Overlay (B) if the user explicitly asks.


Common request envelope

POST /v1/modify_scene
Authorization: Bearer wc_live_REPLACE_ME
Content-Type: application/json

{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [ /* one family — see branches below */ ]
}

Common field reference

Field Type Required Description
id string yes topic_id from /v1/create_video / /v1/video_data. Accepts widecast..., gubo..., or current ids. The video must have reached scenes_ready_for_review (HTTP 409 otherwise).
by string enum yes How to pick the scene. Prefer "voice_file" — the stable per-scene UID. "id" matches the current segment.id order/display (UNSTABLE). "text" fuzzy-matches segment.text; multi-match returns a clarification.
value string | number yes The voice_file / id / narration snippet to match.
fields array yes Non-empty list of { field_name, value } edits — one family per call. See branch sections below.
op string no Reserved for future generic ops. Defaults to "set".
min_score number no Only for by="text". Fuzzy threshold 0..1 (default 0.5). Lower cautiously for paraphrases; raising past 0.9 usually causes false negatives.

Coordinate spaces

Two spaces appear in modify_scene — they are not interchangeable:

Space Width × Height Used by
Remotion canvas 720 × 1280 remotion.group.rect (default), spec content
Legacy editor preview 280 × 498 overlay.narrator.*, overlay.caption.y, remotion.object.rect, remotion.group.rect with coordinate_space:"preview"

When in doubt, ask /v1/scene_geometry for the box you want to move — it returns everything in 280×498 preview coords plus a rect_canvas mirror for the object layer.


Branch (A) — Background media swap

Roll-aware: B-roll scenes get the asset as the background; A-roll scenes register it as the overlay without touching the narrator.

{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [
    { "field_name": "mediaUrl",  "value": "https://cdn.example.com/coast-sunset.jpg" },
    { "field_name": "mediaType", "value": "image" }
  ]
}
field_name Value Notes
mediaUrl http(s) URL Image OR video.
mediaType "image" | "video" Optional; auto-detected from URL extension.
Scene roll Behaviour
B-roll Asset IS the background. mediaUrl, thumbnailUrl, brollUrl, brollThumbnailUrl synced (brollUrl === mediaUrl).
A-roll Narrator + grid untouched. Asset becomes brollUrl / user_asset_url; next scene-spec gen turns it into the overlay (image → chest-card; video → brollUrl backdrop).

Response includes applied, media_type, media_url, and the full updated segment.


Branch (B) — Upload Overlay

Free, agent-supplied asset → Remotion overlay spec. This is not Regenerate Overlay (which is paid because WideCast calls image generation).

Two upload formats are supported, chosen by the URL extension:

Format Trigger Pipeline Decomposition When to use
SVG URL ends .svg svg2spec (each <g data-wc-object> group → one object) Deterministic, pixel-perfect (NO auto-fit; max diff 2/255 vs original) Agent authors the overlay programmatically and wants guaranteed decomposition + animation hints in one file.
Raster PNG / JPG / WebP / GIF / etc detect.py classifier → graphic decomposition OR full-frame realistic fallback Classifier-driven (heuristic) Existing image file, scanned graphics, designer-provided assets.
{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [
    { "field_name": "remotion.upload_overlay", "value": "https://cdn.example.com/overlay-720x1280.png" }
  ]
}

You can also pass value: { "url": "https://..." } if you need to attach future options.

Grounding rule. Whichever format you choose, the asset MUST be grounded in scene context (text, talking_point, visual, quote, keyword, type) — do not invent unrelated visuals.

📐 SVG path (PREFERRED for agent-authored overlays)

When the URL ends .svg (case-insensitive), WideCast routes through svg2spec:

  1. Parses the SVG.
  2. Finds every <g data-wc-object="name"> group (document order).
  3. Rasterizes each group alone on a transparent 720×1280 canvas.
  4. Crops to the alpha bbox → base64 PNG → one Storyboard object per group.
  5. Each object is placed at its authored bbox with NO auto-fit (pixel-perfect; verified max diff 2/255 vs the original SVG).

Required: viewBox="0 0 720 1280" (portrait — matches the Remotion canvas).

Per-group attributes:

Attribute Required? Description
data-wc-object="name" yes Stable id/name. Groups WITHOUT this are rasterized into the background instead of becoming separate objects.
data-wc-kind no "object" | "text" | "bar" | "mark" | "callout" — inferred from content if missing.
data-wc-anim no Entry animation: "slide-up" | "slide-down" | "slide-left" | "slide-right" | "pop" | "grow-x" | "grow-y" | "fade" (default "fade"; bar shapes get growRight / growUp automatically).
data-wc-z no Paint/stack order (default = document order).

Authoring example (a stat callout next to an icon):

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 1280">
  <g data-wc-object="bg-card" data-wc-kind="object" data-wc-anim="fade">
    <rect x="60" y="380" width="600" height="240" rx="32" fill="#1e293b"/>
  </g>
  <g data-wc-object="icon" data-wc-kind="mark" data-wc-anim="pop" data-wc-z="2">
    <circle cx="160" cy="500" r="60" fill="#a78bfa"/>
    <path d="M130 500 l25 25 l45 -50" stroke="#fff" stroke-width="8" fill="none"/>
  </g>
  <g data-wc-object="stat-number" data-wc-kind="text" data-wc-anim="slide-up" data-wc-z="3">
    <text x="260" y="490" font-family="Inter" font-size="64" font-weight="800" fill="#fff">$14.3B</text>
  </g>
  <g data-wc-object="stat-label" data-wc-kind="text" data-wc-anim="slide-up" data-wc-z="4">
    <text x="260" y="560" font-family="Inter" font-size="28" fill="#cbd5e1">Meta  Scale AI</text>
  </g>
</svg>
{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [
    { "field_name": "remotion.upload_overlay", "value": "https://cdn.example.com/scene-overlay.svg" }
  ]
}

SVG-specific errors:

HTTP error.code When
422 svg2spec_failed Parse error / missing viewBox / no data-wc-object groups / structural issue (the message carries the underlying svg2spec reason).
502 (network) SVG URL couldn't be downloaded.

The SVG path is strict: a bad authored file produces a clean 422 with a structural reason, not a silent fallback into another pipeline.

🖼 Raster path (default for non-SVG URLs)

Image guidance for best object decomposition:

When in doubt, author SVG — the SVG path has no classifier in the loop, so you get the exact decomposition you authored.

Upload Overlay (either format) is an explicit opt-in to recreate an overlay even if the scene previously had remotion_spec="none".

The success response includes remotion_spec_updated: true, remotion_spec_file, a cache-busted remotion_spec_url, remotion_spec_version, remotion_spec_state: "ready", plus the agent-supplied uploaded_overlay_url, cost: 0, and remotion_poster_url (a static fallback poster used by /v1/scene_inspector when no live browser is available).


Branch (C) — Remotion Storyboard object-layer rect (preferred overlay layout)

Free layout edit. This is the API you want for visible overlay layout. First call /v1/scene_geometry and read boxes.remotion.object_layer.objects — each item carries:

Field Meaning
layout_id Stable id, e.g. main.one_by_one, main.obj_03_text. Pass this back as the selector.
rect Box in 280×498 editor-preview coords.
rect_canvas Same box in 720×1280 Remotion canvas coords.
temporal_policy simultaneous_static / one_by_one.
update_field "remotion.object.rect" — the modify_scene field name.

Then send:

{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [
    {
      "field_name": "remotion.object.rect",
      "value": {
        "layout_id": "main.one_by_one",
        "x": 24,
        "y": 140,
        "w": 232,
        "h": 42,
        "coordinate_space": "preview"
      }
    }
  ]
}

Response includes remotion_object_updated: true, remotion_spec_updated: true, a cache-busted remotion_spec_url, remotion_poster_url (refreshed), and the updated segment.


Branch (D) — Remotion Storyboard group rect (low-level wrapper edit)

Free layout edit. Prefer (C) for visible overlay layout — this branch is the low-level wrapper edit used when you want to translate or resize the whole group without re-laying out the children.

{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [
    {
      "field_name": "remotion.group.rect",
      "value": {
        "element_id": "main",
        "x": 111,
        "y": -120,
        "w": 498,
        "h": 174,
        "coordinate_space": "canvas",
        "resize_mode": "scale_children"
      }
    }
  ]
}
Key Description
element_id Optional. Group id. Omit when the spec has a single Storyboard group or a group with id="main".
x, y Wrapper top-left position. Move-only when w/h omitted. Canvas coords are not clampedy: -120 legitimately translates a full-canvas group upward.
w, h Wrapper width/height. Triggers resize.
coordinate_space "canvas" (720×1280) or "preview" (280×498 editor coords — server converts).
resize_mode "scale_children" (default) — wrapper + every child object + root background.bbox scale together. "wrapper_only" is advanced and rarely correct for agents.

Branch (E) — Narrator layout rect

Free layout edit in legacy 280×498 editor preview coords. Send the whole rect, or field-by-field.

{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [
    {
      "field_name": "overlay.narrator.rect",
      "value": {
        "x": 35,
        "y": 124,
        "w": 210,
        "h": 374,
        "visible": true
      }
    }
  ]
}

You can also send overlay.narrator.x, overlay.narrator.y, overlay.narrator.w, overlay.narrator.h individually.

Important:

Response: narrator_layout_updated: true, message: "Narrator layout updated.", plus the updated segment.


Branch (F) — Caption Y layout

Vertical-only caption placement in 280×498 editor preview coords. Caption participates in scene layout as a vertical knob — nothing else.

{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [
    { "field_name": "overlay.caption.y", "value": 408 }
  ]
}

This edit updates ONLY overlay.caption.y plus touched=true. It does not change x / w / h, visibility, config / style, segment.text, segment.words, audio, duration, or Remotion specs. For combined layout changes, prefer layout.batch (G).

Response: caption_layout_updated: true, message: "Caption layout updated.", plus the updated segment.


Branch (G) — Layout batch (one persist, one MQTT)

Compose narrator + caption + Remotion object/group layout in a single atomic call. Either send the layout fields directly (the server detects 2+ layout families and routes through the batch path) or wrap them in layout.batch:

{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [
    { "field_name": "overlay.narrator.rect", "value": { "x": 35, "y": 124, "w": 210, "h": 374, "visible": true } },
    { "field_name": "overlay.caption.y", "value": 408 },
    {
      "field_name": "remotion.object.rect",
      "value": { "layout_id": "main.one_by_one", "x": 24, "y": 200, "coordinate_space": "preview" }
    }
  ]
}

Or via the explicit envelope:

{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [
    {
      "field_name": "layout.batch",
      "value": {
        "fields": [
          { "field_name": "overlay.narrator.rect", "value": { "x": 35, "y": 124, "w": 210, "h": 374 } },
          { "field_name": "overlay.caption.y", "value": 408 }
        ]
      }
    }
  ]
}

Semantics:

Response: layout_batch_updated: true, plus per-family flags (narrator_layout_updated, caption_layout_updated, remotion_spec_updated), the cache-busted remotion_spec_url / remotion_poster_url, and the updated segment.


Branch (H) — Upload Voice (async)

User-supplied audio. Not Use-AI-Voice — the caller supplies the file, so no voice/model credit is spent.

{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [
    { "field_name": "voice.upload", "value": "https://cdn.example.com/voice.mp3" }
  ]
}

value may also be { "url": "...", "filename": "voice.mp3", "content_type": "audio/mpeg" }.

The endpoint is asynchronous because WideCast must transcode/transcribe the audio. Response:

{
  "object":     "scene_voice_upload_queued",
  "id":         "widecast7c0d4f8a9b1e2d3f",
  "scene_id":   3,
  "voice_file": "XcR0k",
  "queue_id":   "gs_widecast7c0d4f8a9b1e2d3f_XcR0k",
  "status":     "queued",
  "media_type": "audio",
  "applied":    { "field_name": "voice.upload", "audio_url": "...", "queue_id": "...", "bytes": 12345, "extension": "mp3", "cost": "free_user_supplied_audio", "async": true },
  "segment":    { /* snapshot at queue time */ }
}

When the transcoder finishes, the server applies words, text, duration, following-scene timeline shifts, inactive caption-language timelines, narrator_face / chroma key, and recorded=true to generated_video_script and emits MQTT event="scene_voice_upload_applied" with the updated segment. Verify with /v1/video_data after completion.


Branch (I) — Upload Narrator Video (async)

User-supplied A-roll narrator video. Reuses the same single-scene A-roll upload pipeline as the editor's Upload button.

{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [
    { "field_name": "narrator.upload_video", "value": "https://cdn.example.com/narrator.mp4" }
  ]
}

If the target scene is currently B-roll, the server preserves the previous mediaUrl / thumbnailUrl as brollUrl / brollThumbnailUrl, processes the new video as A-roll, then applies the same server roll-switch helper as roll.active="A" on completion.

Response shape: object: "scene_narrator_upload_queued", same queue_id family; completion emits MQTT event="scene_narrator_upload_applied".


Branch (J) — A/B-roll switch (sync data switch)

Free synchronous data switch — no upload, transcode, or generation. Preserves both lanes (arollUrl/brollUrl) and swaps active runtime fields.

{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [
    { "field_name": "roll.active", "value": "A" }
  ]
}

Values: "A" (show/use narrator A-roll) or "B" (hide narrator/use B-roll). Or use { "field_name": "roll.switch", "value": "toggle" } to flip the current state.

The server mirrors the editor's Show/Hide Narrator data path — A→B protects the A-roll file family by renaming {voice_file}.mp4 / thumb_{voice_file}.jpg to _switched when those files exist; B→A may ensure the preserved B-roll exists locally.

Response: roll_switched: true, active_roll: "A"|"B", show_narrator: bool, media_type, plus the updated segment.


Branch (K) — Segment text correction

Free, synchronous, distinct from Upload Voice. Updates segment.text and rebuilds segment.words over existing audio timings — does NOT change audio, duration, or following-scene timelines.

{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [
    { "field_name": "segment.text", "value": "Meta spent 14.3 billion to acquire Scale AI." }
  ]
}

value may also be { "text": "..." }. The server also syncs the current caption-language entry in video_script.captions by voice_file.


Branch (L) — Scene metadata

Update one or more scene planning fields from the same family. Validated:

Field Allowed values
pattern single_metric, bar_chart, proportion_chart, trend_chart, structural_diagram, illustration, hybrid_vertical, typography_only, map_chart, comparison_table, timeline_events, checklist_tips, quote_card, narration_only, real_entity
type HOOK, STAT, KEY POINT, DATA, FACT, CALL TO ACTION
sub_mode (only with pattern="illustration") photo_with_people, photo_no_people, document, digital_ui
visual, keyword, quote, talking_point free text

Auto-clears: pattern="narration_only" clears quote + visual; pattern="typography_only" clears visual.

segment.<name> / scene.<name> namespaced forms are accepted (e.g. segment.pattern). Metadata edits sync to the current caption-language segment by voice_file. No audio / words / duration / media / spec changes.

{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [
    { "field_name": "pattern", "value": "typography_only" },
    { "field_name": "quote",   "value": "Short headline that lands" }
  ]
}

Branch (M) — Remotion add element

Free, synchronous, add-only. Appends a new Storyboard group/object to the existing Remotion spec. Does NOT auto-fit, replace, move, or modify existing objects. Returns a new layout_id so you can land the element precisely with a follow-up layout.batch carrying a remotion.object.rect.

{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [
    {
      "field_name": "remotion.add_element",
      "value": {
        "kind":     "stat",
        "value":    "$14.3B",
        "label":    "Meta → Scale AI",
        "position": "top"
      }
    }
  ]
}

You can also pass a bare string for the common kind="text" case:

{
  "fields": [
    { "field_name": "remotion.add_element", "value": "+14.3B in 12 months" }
  ]
}

Value reference

Key Required Description
kind yes (or derive) "text" | "stat" | "label" | "callout" | "image". Aliases: metric/numberstat, badge/captionlabel. Defaults to image when url/image/src is set, else text.
value yes for text/stat/callout Primary text content.
label optional Secondary text (used by stat/label).
url (image, src aliases) yes for kind="image" http(s) image URL.
position optional "top" | "bottom" | "left" | "right" | "center" — placement hint; server picks a sane default rect on that side.
rect optional Pin an explicit rect: {x, y, w, h, coordinate_space: "preview" \| "canvas"} (default "preview" = 280×498). Top-level shorthand x/y/w/h on the value object is also accepted.
style_token optional Visual token name (e.g. brand styling slot).
emphasis optional Style intensity hint.
entry optional Entry animation (default "fade").
delay_sec optional Delay before the element appears (default 0).
z_index optional Per-object z order inside the new Storyboard group (default 1).
element_z_index optional Z order of the new Storyboard group itself (default 900).

Rules

Response

{
  "object":                  "scene_modified",
  "id":                      "widecast7c0d4f8a9b1e2d3f",
  "scene_id":                3,
  "voice_file":              "XcR0k",
  "applied": {
    "field_name":  "remotion.add_element",
    "kind":        "stat",
    "element_id":  "agent_add_01",
    "object_id":   "add_stat_1782700000000",
    "layout_id":   "agent_add_01.add_stat_1782700000000",
    "rect":        { "x": 24, "y": 60,  "w": 232, "h": 64,  "coordinate_space": "preview" },
    "rect_canvas": { "x": 61.7, "y": 154.4, "w": 596.6, "h": 164.6, "coordinate_space": "canvas" },
    "cost":        "free_spec_append",
    "async":       false,
    "auto_fit":    false,
    "follow_up":   "Use layout.batch with remotion.object.rect and the returned layout_id to position this object."
  },
  "remotion_element_added": true,
  "remotion_spec_updated":  true,
  "remotion_spec_url":      "https://widecast.ai/downloads/<company_id>/<topic_id>/XcR0k_spec.json?v=1782700000",
  "remotion_poster_url":    "https://widecast.ai/downloads/<company_id>/<topic_id>/XcR0k_overlay_poster.png?v=1782700000",
  "segment":                { /* updated segment */ }
}
# 1. Add the element (default placement)
result = client.modify_scene(
    video_id,
    by="voice_file", value="XcR0k",
    fields=[{
        "field_name": "remotion.add_element",
        "value": {"kind": "stat", "value": "$14.3B", "label": "Meta → Scale AI", "position": "top"},
    }],
)
layout_id = result["applied"]["layout_id"]

# 2. Read the displayed rect + collision violations
geom = client.scene_geometry(video_id, voice_file="XcR0k", include_diagnostics=True)

# 3. Refine placement with layout.batch (and any sibling layout edits)
client.modify_scene(
    video_id,
    by="voice_file", value="XcR0k",
    fields=[{
        "field_name": "remotion.object.rect",
        "value": {"layout_id": layout_id, "x": 24, "y": 80, "w": 232, "h": 56,
                  "coordinate_space": "preview"},
    }],
)

Error codes

error.code HTTP When
invalid_remotion_add_element 400 value is neither a dict nor a non-empty string.
invalid_remotion_add_element_kind 400 kind is not in the allowed set.
mixed_remotion_fields 400 Sent alongside other Remotion fields (or inside layout.batch).
remotion_spec_not_found 404 The scene's spec file is missing on disk.
remotion_spec_disabled 409 segment.remotion_spec="none" — use Upload Overlay first.
missing_voice_file 409 Scene has no voice_file; can't resolve the spec filename.
remotion_spec_parse_failed / remotion_spec_invalid 409 Spec file present but unreadable / not a JSON object.

Branch (N) — Disable overlay (free, sync data edit)

Hide the Remotion overlay on one scene by setting segment.remotion_spec="none". The spec and overlay-poster files on disk are NOT deleted — this is just an opt-out pointer flip. Re-enable later via Branch (B) Upload Overlay (or the manual regenerate-overlay UI flow), which writes a fresh {voice_file}_spec.json and stamps remotion_spec back to {voice_file}_spec.json?v={mtime} so CDN / browser caches fetch the new file.

{
  "id":    "widecast7c0d4f8a9b1e2d3f",
  "by":    "voice_file",
  "value": "XcR0k",
  "fields": [
    { "field_name": "disable_overlay", "value": true }
  ]
}

Field reference

Field Type Required Description
field_name string yes disable_overlay (canonical). Aliases accepted: remotion.disable_overlay, overlay.disable, remotion_spec.none.
value boolean | string | object yes Truthy disables the overlay. Accepted forms: true, the string "none", or {enabled: false} (also {enable: false}, {disabled: true}, {disable: true}).

What this branch does NOT do

Constraints

Response highlights

Re-enable later

Run Branch (B) Upload Overlay with a fresh image / SVG, OR the UI's manual regenerate-overlay flow. Either path writes the spec back to disk and stamps remotion_spec to the cache-busted {voice_file}_spec.json?v={mtime} so the editor / preview / /v1/scene_geometry pick it up immediately.

Errors

Code HTTP When
invalid_disable_overlay_value 400 value is not in {true, "none", {enabled:false}, …}.
mixed_remotion_fields 400 Sent alongside other fields.
missing_voice_file 409 Scene has no voice_file; can't resolve the spec filename.

Responses

200 OKscene_modified (sync branches)

{
  "object":     "scene_modified",
  "id":         "widecast7c0d4f8a9b1e2d3f",
  "scene_id":   3,
  "voice_file": "XcR0k",
  "score":      1.0,
  "applied":    { /* family-specific details */ },
  "segment":    { /* full updated segment */ },

  "layout_batch_updated":     true,
  "narrator_layout_updated":  true,
  "caption_layout_updated":   true,
  "remotion_object_updated":  true,
  "remotion_spec_updated":    true,
  "remotion_spec_file":       "XcR0k_spec.json",
  "remotion_spec_url":        "https://widecast.ai/downloads/<company_id>/<topic_id>/XcR0k_spec.json?v=1748293012",
  "remotion_spec_version":    "1748293012",
  "remotion_spec_state":      "ready",
  "remotion_spec_exists":     true,
  "remotion_poster_file":     "XcR0k_overlay_poster.png",
  "remotion_poster_url":      "https://widecast.ai/downloads/<company_id>/<topic_id>/XcR0k_overlay_poster.png?v=1748293012",
  "remotion_poster_version":  "1748293012",
  "remotion_poster_state":    "ready",
  "remotion_poster_exists":   true,
  "remotion_poster_warnings": []
}

Fields not relevant to the branch are omitted. score is 1.0 for by="id"/by="voice_file"; the fuzzy score for by="text". Branches (A) also carry media_type / media_url; (B) also carries uploaded_overlay_url + cost: 0; (J) also carries roll_switched / active_roll / show_narrator.

200 OKscene_voice_upload_queued / scene_narrator_upload_queued (async branches)

Shown above for branch (H). The edit is not yet visible — wait for MQTT scene_voice_upload_applied / scene_narrator_upload_applied, then call /v1/video_data to verify.

200 OKclarification (ambiguous by="text")

{
  "object":      "clarification",
  "needs_input": "value",
  "message":     "More than one scene matches that text closely. Pick one and call again with by='voice_file' and value set to the chosen scene's voice_file.",
  "candidates":  [
    { "segment_id": 3, "voice_file": "XcR0k", "score": 0.92, "text": "Meta đã chi 14.3 tỷ đô để mua Scale AI…" },
    { "segment_id": 7, "voice_file": "Z9p2m", "score": 0.88, "text": "Meta chi 14.3 tỷ USD đầu tư vào AI training data…" }
  ]
}

No edit was applied. Show the text previews to the user, get a pick, retry with by="voice_file".

404 Not Found

409 Precondition Failed

400 Bad Request

500 / 502


SDK examples

Python — data-first audit → object-layer rect via layout.batch

from widecast import Widecast

client = Widecast()

# 1) Read scenes
data = client.video_data("widecast7c0d4f8a9b1e2d3f")
target = next(s for s in data["segments"] if "Scale AI" in s.get("text", ""))

# 2) Read geometry (no screenshots, cheap)
geom = client.scene_geometry(data["id"], voice_file=target["voice_file"])
oneby = next(o for o in geom["boxes"]["remotion"]["object_layer"]["objects"]
             if o["layout_id"].endswith(".one_by_one"))

# 3) Move the overlay and the narrator+caption in ONE call
result = client.modify_scene(
    data["id"],
    by="voice_file", value=target["voice_file"],
    fields=[
        {"field_name": "overlay.narrator.rect",
         "value": {"x": 35, "y": 124, "w": 210, "h": 374, "visible": True}},
        {"field_name": "overlay.caption.y", "value": 408},
        {"field_name": "remotion.object.rect",
         "value": {"layout_id": oneby["layout_id"], "x": 24, "y": 200,
                   "coordinate_space": "preview"}},
    ],
)
print("new spec:", result["remotion_spec_url"])

TypeScript — upload narrator video (async)

import Widecast from "@widecast/sdk";

const client = new Widecast();
const data = await client.video_data("widecast7c0d4f8a9b1e2d3f");
const target = data.segments.find(s => /Scale AI/i.test(s.text ?? "")) ?? data.segments[0];

const result = await client.modify_scene({
  id: data.id,
  by: "voice_file",
  value: target.voice_file,
  fields: [
    { field_name: "narrator.upload_video",
      value: "https://cdn.example.com/narrator.mp4" },
  ],
});

if (result.object === "scene_narrator_upload_queued") {
  console.log("queued:", result.queue_id);
  // Wait for MQTT "scene_narrator_upload_applied", then call video_data.
}

Manual (curl) — caption Y only

curl -sS -X POST ".../v1/modify_scene" \
  -H "Authorization: Bearer wc_live_REPLACE_ME" \
  -H "Content-Type: application/json" \
  -d '{
        "id":    "widecast7c0d4f8a9b1e2d3f",
        "by":    "voice_file",
        "value": "XcR0k",
        "fields": [
          { "field_name": "overlay.caption.y", "value": 408 }
        ]
      }' | jq

Cost & ordering


When to use by="text"

Prefer by="voice_file" whenever you have it from a previous /v1/video_data / /v1/status call. Use by="text" only when the user names a scene by what's said in it ("change the picture on the scene about Meta and Scale AI"). The endpoint is ambiguity-safe — it returns a clarification rather than guess. When the user paraphrases, lower min_score cautiously to 0.40.45; do not raise past 0.9.