Request
Path { projectId: UUID }; JSON body { "editor_state": EditorState, "base_version"?: integer }. The body is parsed strictly: field names are snake_case, and a key outside this shape, or a wrong type, is a 400 that does not name the key.
EditorState {
main_video_track: [MainTrackEntry] required: the ordered timeline of source clips
overlay_tracks?: [OverlayTrack]
markers?: [{ id, time, color?, label? }]
main_track_magnet?: boolean false lets main-track entries carry gap_before
media_pool?: [{ src, type: image | video | audio, label?, aspect?, source_duration? }] the project's uploaded library; UI only, the render never reads it
version?, legacy_overlays_materialized?, auto_color_adjust_created?, removed_source_clips?: [{ clip_id, video_url? }], storyboard? Studio bookkeeping: send back what you read, never author (storyboard is opaque JSON)
}
MainTrackEntry {
clip_id: UUID required
in_point (0), out_point? seconds into the source clip
type ("video"), speed?, volume?, color?
filter?: none | warm | cool | mono | vivid | film
transform?: { scale (required), x (0), y (0) }
crop?: { x, y, w, h (required, fractions of the rotated source), rotation (0) }
transition_after?: { type (required): none | fade | dipblack | crossfade | dipwhite | whiteflash | slideleft | slideup | spin | zoompunch | whipleft | whipup | roll | shake, duration (required, seconds) }
gap_before?, fade_in?, fade_out? seconds (gap_before needs main_track_magnet false)
use_original_voice?: boolean
voice_effect?: deep | reverb | room | phone | cheap_mic | no_bass | crisp | bass_boost | podcast | full | voice_focus (null or "original" = none), voice_strength? 0..100 (null = 100)
}
OverlayTrack { id (required), kind (required): overlay | audio, name (""), muted?, hidden?, items: [OverlayItem] }
OverlayItem, every type: id, type: text | image | video | audio | caption | adjust, start, duration all required; start and duration are timeline seconds
text: text, x, y, w (fractions of the frame, 0..1: x 0.5 is centered; y is the vertical CENTER, 0 is the top edge, a top hook sits at y 0.12 to 0.2; always set x and y, null renders center-frame), style_preset (an id from GET /editor/text-style-presets; the save expands it into style, so prefer it over a hand-built style) or style: EditorTextStyle, text_wrap? (true makes w the box width the text wraps inside; otherwise break lines with \n in text), animation?: { in: none | fade | pop | slide_up | slide_down, out: none | fade | pop | slide_down | slide_up, duration (0.3) }, rotation?, opacity?
caption: words (required): [{ text, start, end }], caption_style: bold-pop | box-highlight | caps-box | karaoke | modern | modern-box | modern-word | one-word | pill | yellow-box | yellow-classic, x, y, w, style?
adjust: adjust: { temperature?, tint?, saturation?, brightness?, contrast? } each -100..100 with 0 neutral; lut? ("pamba_v1" is the default grade)
image, video: src (a durable public URL; a signed asset URL expires), x, y, w (the width as a fraction of the frame: 1.0 fullscreen, about 0.6 a floating insert, about 0.4 a logo flash; the aspect ratio keeps itself), media_in? (source trim start, seconds), speed? (0.5 to 2.0; the item consumes duration x speed source seconds), volume? (0 keeps an inserted clip from talking over the presenter), fade_in?, fade_out?, opacity?, rotation?, crop? (as on MainTrackEntry), aspect?, source_duration?, label?
audio: src, volume?, fade_in?, fade_out?, media_in?, speed?, voice_effect?, voice_strength? (as on MainTrackEntry)
EditorTextStyle {
font_family (""), font_size (64; px on the 1080x1920 frame, 24 is the readable minimum), color ("#FFFFFF"), background ("transparent")
background_radius? (0 sharp to 0.5 capsule), background_mode?: block | line, background_height? (0..1, default 0.20), background_width? (0..1, default 0.28)
bold (false), italic (false), align (center): left | center | right, outline (false), shadow (false), underline?, text_case?: upper | lower | title, letter_spacing?
outline_color?, outline_width?, shadow_color?, shadow_opacity?, shadow_blur?, shadow_distance?
box_color?, box_radius?, box_pad? the caption active-word highlight box
}
Read-back only, omit on write: wrap_lines, background_box, line_boxes and words[].box are measurement caches the Studio writes. Reserved: origin values auto_color and media_scene, asset_id, bullet_index.