---
title: "Apply targeted edits"
method: POST
path: "/v1/projects/{project_id}/ops"
tags: ["projects"]
---

# Apply targeted edits

`POST /v1/projects/{project_id}/ops`

## Request

- Base URL: `https://api.framelane.io`
- URL: `https://api.framelane.io/v1/projects/{project_id}/ops`
- Auth: HTTP bearer

Apply an atomic batch of ops to the project head. Returns the new version, the ids touched, a compact diff, and validation that rides along for free. Pass `if_version` for optimistic concurrency (409 on mismatch). `apply_template` replaces the composition with a template's and records the template as the project's source.

## Path parameters

- `project_id` string, required

## Request body

- ApplyOpsRequest
  - `ops` union[], required: Targeted edits applied in order; the batch is atomic.
    - union
      - ShiftElementOp
        - `op` 'shift_element', required
        - `id` string, required: Target element id.
        - `by_seconds` number, nullable: Shift the element's start time by this many seconds (may be negative). The element's animations and keyframes travel with it, rigidly. A backward shift is clamped so nothing lands before 0 — the whole bundle stops together, keeping its internal spacing intact, which means the element itself may end up later than 0.
        - `by_frames` integer, nullable: Shift by this many frames (converted via frame_rate). Use instead of by_seconds.
      - SetFieldsOp
        - `op` 'set_fields', required
        - `id` string, required
        - `fields` object, required: Element fields to patch; re-validated against the element type. Patching anything that moves the element's window (`time`, `duration`, `in_point`/`out_point`, `speed`) carries its animations and keyframes with it; a field you pass explicitly is used verbatim. On an instance, `properties` and `slots` merge by key, and a key set to null is removed (the property takes its default, the slot its own content).
        - `component` string, nullable: Edit the element `id` in this component's definition instead (its body, and every variant body that holds it), which every instance shows. Omitted: the composition's own element.
      - TrimElementOp
        - `op` 'trim_element', required
        - `id` string, required
        - `in_point` number, nullable
        - `out_point` number, nullable: New source out-point. Video and audio carry no `duration` — their on-timeline footprint *is* (out_point − in_point)/speed — so a trim is a resize and the element's exit animations re-anchor to the new end.
      - SwapSourceOp
        - `op` 'swap_source', required
        - `id` string, required
        - `source_url` string, required: New source URL for a video/audio/image element.
      - AddElementOp
        - `op` 'add_element', required
        - `element` object, required: A full element object; validated as a timeline element, or with `parent` as a frame child (design px from the frame's top-left, and an `id`).
        - `index` integer, nullable: Insert position among the parent's children; appends when omitted.
        - `parent` string, nullable: A frame id: insert into its `children`. Omitted: the root `elements`.
      - RemoveElementOp
        - `op` 'remove_element', required
        - `id` string, required
      - ReorderOp
        - `op` 'reorder', required
        - `id` string, required
        - `to_index` integer, required: New position among its siblings.
      - SetTransitionOp
        - `op` 'set_transition', required
        - `transition` Transition, required: A transition between two **video** elements. The engine links transitions to video clips only — referencing an image, text or other element type is rejected (it would crash the render node).
          - `type` 'cross_dissolve' | 'dip_to_black' | 'dip_to_white' | 'wipe_left' | 'wipe_right' | 'wipe_up' | 'wipe_down' | 'diagonal_wipe' | 'barn_doors_horizontal' | 'barn_doors_vertical' | 'iris' | 'page_turn' | 'cross_zoom' | 'gradient_wipe' | 'band_wipe' | 'box_wipe' | 'swirl' | 'glitch_memories' | 'window_slice' | 'cube' | 'doorway' | 'pinwheel' | 'water_drop' | 'crosshatch' | 'dreamy' | 'angular' | 'burn' | 'heart' | 'circle_open' | 'color_phase' | 'squares_wire' | 'whip_pan' | 'cinematic_zoom' | 'burn_edge' | 'shatter' | 'domain_warp' | 'circle_crop' | 'cross_warp' | 'fold_horizontal' | 'fold_vertical' | 'linear_blur' | 'minimise_bottom_left' | 'minimise_bottom_right' | 'minimise_top_left' | 'minimise_top_right' | 'ripple' | 'rotate' | 'splice' | 'three_stripes', required: Values: - `cross_dissolve` — Cross-dissolve — fades one clip into the next. - `dip_to_black` — Dips through black between clips. - `dip_to_white` — Dips through white between clips. - `wipe_left` — Wipes the next clip in from the right to the left. - `wipe_right` — Wipes the next clip in from the left to the right. - `wipe_up` — Wipes the next clip in from the bottom upward. - `wipe_down` — Wipes the next clip in from the top downward. - `diagonal_wipe` — Wipes across on a diagonal split. - `barn_doors_horizontal` — Two halves part horizontally like barn doors. - `barn_doors_vertical` — Two halves part vertically like barn doors. - `iris` — Circular iris (bullseye) open/close between clips. - `page_turn` — Page-flip / page-curl reveal. - `cross_zoom` — Zoom-blur push from one clip into the next. - `gradient_wipe` — Soft gradient-driven dissolve wipe. - `band_wipe` — Interleaved stripes wipe the next clip in. - `box_wipe` — Rectangular box grows to reveal the next clip. - `swirl` — Swirls the outgoing clip away in a spiral. - `glitch_memories` — Glitchy, memory-like distortion between clips. - `window_slice` — Slices the frame into sliding window strips. - `cube` — 3D cube rotation between the two clips. - `doorway` — Clips part like opening doors to reveal the next. - `pinwheel` — Pinwheel rotation wipe between clips. - `water_drop` — Rippling water-drop distortion reveals the next clip. - `crosshatch` — Crosshatch pattern dissolve between clips. - `dreamy` — Soft, dreamy blur-dissolve between clips. - `angular` — Angular wedge sweep between clips. - `burn` — Burning-film dissolve between clips. - `heart` — Heart-shaped iris reveal of the next clip. - `circle_open` — Expanding circle opens onto the next clip. - `color_phase` — Color-phase shift blends between clips. - `squares_wire` — Wireframe squares wipe between clips. - `whip_pan` — Directional motion-blur whip pan. - `cinematic_zoom` — Punch zoom with radial blur + chromatic fringing. - `burn_edge` — Noise-ridged burn front with ember glow and sparks. - `shatter` — Jittered shards fly apart revealing the incoming clip. - `domain_warp` — Organic noise-warped dissolve with an emissive frontier. - `circle_crop` — Both clips crop to a shrinking/growing circle through the cut. - `cross_warp` — Clips warp into each other along a dissolving threshold. - `fold_horizontal` — Outgoing clip folds away horizontally, revealing the incoming. - `fold_vertical` — Outgoing clip folds away vertically, revealing the incoming. - `linear_blur` — Cross-dissolve through a heavy directional blur peak. - `minimise_bottom_left` — Outgoing clip shrinks away into the bottom-left corner. - `minimise_bottom_right` — Outgoing clip shrinks away into the bottom-right corner. - `minimise_top_left` — Outgoing clip shrinks away into the top-left corner. - `minimise_top_right` — Outgoing clip shrinks away into the top-right corner. - `ripple` — Water-ripple distortion sweeps the cut across the frame. - `rotate` — Both clips rotate about the frame centre through the cut. - `splice` — Angled splice slides the incoming clip over the outgoing. - `three_stripes` — Three offset stripes sweep the incoming clip in.
          - `duration` number, required: Transition duration in seconds.
          - `from_id` string, nullable: ID of the outgoing video element (omit for the very first transition).
          - `to_id` string, nullable: ID of the incoming video element (omit for the very last transition).
          - `z_index` integer, nullable: **Deprecated — ignored by the renderer.** The transition draws at its linked videos' position; the engine reads no z key on a transition. Accepted for backward compatibility only.
      - RemoveTransitionOp
        - `op` 'remove_transition', required
        - `from_id` string, nullable
        - `to_id` string, nullable
        - `index` integer, nullable: Remove the transition at this index (alternative to from_id/to_id).
      - SetRequestFieldsOp
        - `op` 'set_request_fields', required
        - `fields` object, required: Top-level RenderRequest fields to patch in place (groups, custom_animations, motion_blur, background_gradient/background_color, width/height/duration/frame_rate, output_format, watermark_url, alpha, metadata, …), re-validated against RenderRequest. `elements`/`transitions` are rejected — the element and transition ops own those. Patching only top-level fields touches no element, so `changed_ids` comes back empty; the version still bumps.
      - ReplaceRequestOp
        - `op` 'replace_request', required
        - `render_request` RenderRequest, required: Body for ``POST /v1/renders``. Defines the composition to render: dimensions, output format, timeline elements, and optional transitions. The render engine produces a single video artifact.
          - `version` 'v4' | 'v5', nullable: Wire version of this request. Omit it: the render task is v5 exactly when the request carries a v5 block, and v4 otherwise. `v5` accepts the v5 blocks (a shape's `geometry`, `frame` and `instance` elements, `operations`, `components`); `v4` refuses them.
          - `schema_version` 'v5', nullable: The document format an editor wrote this request in. Not sent to the renderer; `version` is the wire's.
          - `width` integer, nullable: Output width in pixels. Must be set together with `height` or both omitted.
          - `height` integer, nullable: Output height in pixels. Must be set together with `width` or both omitted.
          - `duration` number, nullable: Total composition duration in seconds. Inferred from elements when omitted.
          - `frame_rate` integer, nullable: Frames per second. When set, the output encodes at exactly this rate. Defaults to 30 when omitted.
          - `output_format` 'mp4' | 'webm' | 'mov' | 'gif' | 'png' | 'jpg': Values: - `mp4` — H.264 MP4 video — the default, widely compatible delivery format. - `webm` — VP9 WebM video — supports a transparent (alpha) background. - `mov` — QuickTime MOV — ProRes 4444 with a transparent (alpha) background; large files. - `gif` — Animated GIF — roadmap; not yet emitted by the renderer. - `png` — Single-frame PNG still — roadmap; not yet emitted by the renderer. - `jpg` — Single-frame JPEG still — roadmap; not yet emitted by the renderer.
          - `output_filename` string, nullable: Custom filename for the artifact (without extension). Auto-generated when omitted.
          - `background_color` string: RGBA background color in hex format (#RRGGBBAA).
          - `background_image_url` string, uri, nullable: URL of an image to use as the composition background.
          - `alpha` boolean: When true, produce an alpha-channel (transparent background) output. Requires `output_format=webm` or `output_format=mov`.
          - `elements` union[]: Ordered list of timeline elements (video, image, text, audio, etc.).
            - union
              - …
          - `transitions` Transition[]: Transition effects applied between consecutive elements.
            - `type` 'cross_dissolve' | 'dip_to_black' | 'dip_to_white' | 'wipe_left' | 'wipe_right' | 'wipe_up' | 'wipe_down' | 'diagonal_wipe' | 'barn_doors_horizontal' | 'barn_doors_vertical' | 'iris' | 'page_turn' | 'cross_zoom' | 'gradient_wipe' | 'band_wipe' | 'box_wipe' | 'swirl' | 'glitch_memories' | 'window_slice' | 'cube' | 'doorway' | 'pinwheel' | 'water_drop' | 'crosshatch' | 'dreamy' | 'angular' | 'burn' | 'heart' | 'circle_open' | 'color_phase' | 'squares_wire' | 'whip_pan' | 'cinematic_zoom' | 'burn_edge' | 'shatter' | 'domain_warp' | 'circle_crop' | 'cross_warp' | 'fold_horizontal' | 'fold_vertical' | 'linear_blur' | 'minimise_bottom_left' | 'minimise_bottom_right' | 'minimise_top_left' | 'minimise_top_right' | 'ripple' | 'rotate' | 'splice' | 'three_stripes', required: Values: - `cross_dissolve` — Cross-dissolve — fades one clip into the next. - `dip_to_black` — Dips through black between clips. - `dip_to_white` — Dips through white between clips. - `wipe_left` — Wipes the next clip in from the right to the left. - `wipe_right` — Wipes the next clip in from the left to the right. - `wipe_up` — Wipes the next clip in from the bottom upward. - `wipe_down` — Wipes the next clip in from the top downward. - `diagonal_wipe` — Wipes across on a diagonal split. - `barn_doors_horizontal` — Two halves part horizontally like barn doors. - `barn_doors_vertical` — Two halves part vertically like barn doors. - `iris` — Circular iris (bullseye) open/close between clips. - `page_turn` — Page-flip / page-curl reveal. - `cross_zoom` — Zoom-blur push from one clip into the next. - `gradient_wipe` — Soft gradient-driven dissolve wipe. - `band_wipe` — Interleaved stripes wipe the next clip in. - `box_wipe` — Rectangular box grows to reveal the next clip. - `swirl` — Swirls the outgoing clip away in a spiral. - `glitch_memories` — Glitchy, memory-like distortion between clips. - `window_slice` — Slices the frame into sliding window strips. - `cube` — 3D cube rotation between the two clips. - `doorway` — Clips part like opening doors to reveal the next. - `pinwheel` — Pinwheel rotation wipe between clips. - `water_drop` — Rippling water-drop distortion reveals the next clip. - `crosshatch` — Crosshatch pattern dissolve between clips. - `dreamy` — Soft, dreamy blur-dissolve between clips. - `angular` — Angular wedge sweep between clips. - `burn` — Burning-film dissolve between clips. - `heart` — Heart-shaped iris reveal of the next clip. - `circle_open` — Expanding circle opens onto the next clip. - `color_phase` — Color-phase shift blends between clips. - `squares_wire` — Wireframe squares wipe between clips. - `whip_pan` — Directional motion-blur whip pan. - `cinematic_zoom` — Punch zoom with radial blur + chromatic fringing. - `burn_edge` — Noise-ridged burn front with ember glow and sparks. - `shatter` — Jittered shards fly apart revealing the incoming clip. - `domain_warp` — Organic noise-warped dissolve with an emissive frontier. - `circle_crop` — Both clips crop to a shrinking/growing circle through the cut. - `cross_warp` — Clips warp into each other along a dissolving threshold. - `fold_horizontal` — Outgoing clip folds away horizontally, revealing the incoming. - `fold_vertical` — Outgoing clip folds away vertically, revealing the incoming. - `linear_blur` — Cross-dissolve through a heavy directional blur peak. - `minimise_bottom_left` — Outgoing clip shrinks away into the bottom-left corner. - `minimise_bottom_right` — Outgoing clip shrinks away into the bottom-right corner. - `minimise_top_left` — Outgoing clip shrinks away into the top-left corner. - `minimise_top_right` — Outgoing clip shrinks away into the top-right corner. - `ripple` — Water-ripple distortion sweeps the cut across the frame. - `rotate` — Both clips rotate about the frame centre through the cut. - `splice` — Angled splice slides the incoming clip over the outgoing. - `three_stripes` — Three offset stripes sweep the incoming clip in.
            - `duration` number, required: Transition duration in seconds.
            - `from_id` string, nullable: ID of the outgoing video element (omit for the very first transition).
            - `to_id` string, nullable: ID of the incoming video element (omit for the very last transition).
            - `z_index` integer, nullable: **Deprecated — ignored by the renderer.** The transition draws at its linked videos' position; the engine reads no z key on a transition. Accepted for backward compatibility only.
          - `groups` Group[]: Transform groups over member elements (compose member→group→parent; optional stack layout), the alias of frames (§5). A request with `frame` or `instance` elements carries none.
            - `id` string, required: **Required.** Unique group id.
            - `parent` string, nullable: Parent group id for nesting.
            - `pivot` Point
              - …
            - `translate` Point
              - …
            - `scale` number: Group scale.
            - `rotation_degrees` number: In-plane (z) rotation of the group.
            - `x_rotation` union: Perspective tilt (X axis) applied to each member's orientation about its own centre — the tilted card-gallery look.
              - …
            - `y_rotation` union: Perspective tilt (Y axis). See `x_rotation`.
              - …
            - `opacity` number: Group opacity.
            - `members` string[]: Member element uuids.
            - `animations` Motion[]: Animations driving the whole group about `pivot`. Translate, scale, rotation and colour all compose — that is how you move or resize a whole scene as one unit. Two channels do NOT: a preset that animates **clip** (`wipe_left/right/up/down`) or **crop** (`ken_burns_*`) is a silent no-op on a group, doing nothing at all rather than erroring — put those on a member element instead. Non-uniform scale is also flattened here: only the x component of a `custom_animations` `scale` keyframe is used, so `{x:2,y:1}` scales uniformly. Note `zoom_out` barely moves (scale 1.0->0.9 under a full fade), so it reads as a pure fade; for a real shrink use a `custom` entry with the scale travel you want, or the group's static `scale`.
              - …
            - `layout` StackLayout: Resolve members into a row/column stack (§5, A2): the groups alias's stack, placed as before. A `frame` has no layout of its own (the host's, render-node O27).
              - …
          - `design_size` DesignSize: The logical root (render-node AEC §6): the box frames and instances at the root are placed in, in design px, scaled uniformly to the output, whose aspect it must have.
            - `width` number, required: Width in design px.
            - `height` number, required: Height in design px.
          - `operations` OperationsBlock: The `operations` block (render-node AEC §3; GVR §4.3): element properties driven over time and evaluated by the engine every frame. Each group counts from its anchor.
            - `markers` OperationMarker[]: Named times `marker` anchors read.
              - …
            - `groups` OperationGroup[]: At most 1000 groups and 10000 operations.
              - …
          - `components` Component[]: Component definitions, which `instance` elements place. Schema of one: `blocks.components` in GET /v1/capabilities.
            - `id` string, required: What an instance's `component` names.
            - `width` number, required: Box width in design px.
            - `height` number, required: Box height in design px.
            - `clip` boolean, nullable: Its instances clip to the box unless they say otherwise.
            - `corner_radius` union: Its instances' clip corner radius in design px.
              - …
            - `properties` ComponentProperty[]: What an instance can set.
              - …
            - `children` union[], nullable: The body of a single-variant component, in design px from the box's top-left corner. Give this or `variants`.
              - …
            - `operations` OperationsBlock: The `operations` block (render-node AEC §3; GVR §4.3): element properties driven over time and evaluated by the engine every frame. Each group counts from its anchor.
              - …
            - `variants` ComponentVariant[], nullable: Named bodies, the first the default. Give this or `children`.
              - …
          - `motion_blur` MotionBlur: Shutter-based temporal supersampling of the whole composition (§5c). Opt-in and costly: every frame renders `samples` times, and accumulation is CPU-side, so the render falls back to **software encoding** — expect roughly `samples`× render time.
            - `samples` integer, required: Temporal samples per frame (2–32).
            - `shutter` number: Shutter fraction of the frame interval, (0–1]. (Exactly 0 is rejected: it renders N identical samples — full cost, zero blur.)
          - `background_gradient` union: Canvas-sized gradient fill behind every layer (§6). Rendered for the full output slice. Accepts a Gradient object or a CSS `linear-gradient(...)` string.
            - string: CSS gradient shorthand — `linear-gradient(...)` or `radial-gradient(...)`. Normalised to the Gradient object during validation; the renderer never sees a CSS string, and reading the composition back returns the object.
            - Gradient
              - …
          - `watermark_url` string, uri, nullable: Watermark image overlaid on the output (§6).
          - `custom_animations` CustomAnimationRegistry: Inline custom-animation registries for element and text animations (§3). `element` entries are bindable from any video/image/shape/group animation via `motion[].custom` (or `animations[].custom` on groups). `text` entries bind from a text element's `animation_preset` or `motion[].custom`. Names that shadow a catalog preset — or the reserved static keys `none`/ `overlay`/`difference` — are rejected here: the engine warn-and-*ignores* such a definition (renderer_node.cpp:1500-1516) and the reference then silently resolves to the catalog preset instead of yours.
            - `element` CustomAnimation[]
              - …
            - `text` CustomAnimation[]
              - …
          - `metadata` object: Arbitrary key-value pairs echoed back in all webhook payloads.
          - `webhook_url` string, uri, nullable: Per-request webhook URL. Overrides workspace-level webhooks for this job only. Receives `render.completed`, `render.failed`, and progress events.
          - `ingest_external` boolean, nullable: Controls handling of video/audio source URLs that are not hosted by Framelane. When `true` (the default), each such file is probed for its duration before rendering: the job starts in the `ingesting` state and moves to `queued` once the probe answers, so elements may omit `out_point`. The file itself is fetched by the renderer at render time, so the URL must be a direct, public https link to the media (not a share page) that supports range requests. When `false`, the URL is passed straight through and video/audio require an explicit `out_point`.
      - MoveElementOp
        - `op` 'move_element', required
        - `id` string, required: The element to move, at any depth. It keeps its id and every field.
        - `parent` string, nullable, required: The frame to move it into, or null for the root `elements`. Box values (`x`, `y`, `width`, `height`, `time`) are carried verbatim, never converted between the root's spelling (canvas percent or px, centred) and a frame's (design px from its top-left): convert them yourself, e.g. with set_fields in the same batch.
        - `index` integer, nullable: Position among the new parent's children; appends when omitted.
      - ApplyTemplateOp
        - `op` 'apply_template', required
        - `template_id` string, required: A template you can read: one of yours, or a published one from GET /v1/templates.
        - `keep_output_settings` boolean: Replace the composition with the template's. True keeps this project's frame_rate, output_format, output_filename, alpha, webhook_url and metadata; everything else (canvas, duration, background, elements, transitions, groups, animations) comes from the template. False takes the template's head whole.
  - `if_version` integer, nullable: Optimistic concurrency: reject with 409 if the head version differs.
  - `author` 'user' | 'agent': Who authored a version -- the human owner or an agent acting on their behalf.
  - `run_id` string, nullable: Agent run correlation id, stamped on the new version.

## Response `200`

Successful Response

- ApplyOpsResult: The result of an ops batch: never echoes the whole request.
  - `version` integer, required: The new head version.
  - `editor_url` string, nullable: Open the edited project in the authenticated console.
  - `ok` boolean, required: True when the new head has no error-severity violations.
  - `changed_ids` string[]: Element ids the ops touched.
  - `diff` ProjectDiff, required: A structural delta between the previous head and the new head.
    - `added_ids` string[]: Element ids, at any depth, present only after the edit.
    - `removed_ids` string[]: Element ids, at any depth, present only before the edit.
    - `moved_ids` string[]: Element ids whose parent changed: a frame, an instance's slot or the root.
    - `transitions_changed` boolean: True if the transition list changed.
    - `operations_changed` boolean: True if the `operations` block changed.
    - `components_changed` boolean: True if a component definition changed.
  - `violations` Violation[]: Validation riding along free: cross-element and whole-timeline invariant checks. Truncated to the first 36 findings (errors first) on very large compositions, with a VIOLATIONS_TRUNCATED marker carrying the full count; the free dry run is never truncated.
    - `code` string, required: Stable machine code, e.g. 'DANGLING_TRANSITION'.
    - `message` string, required: Human-readable explanation.
    - `severity` 'error' | 'warning'
    - `path` string, nullable: Where it happened, e.g. 'elements[2]' or 'transitions[0].from_id'.
    - `pointer` string, nullable: The same place as an RFC 6901 JSON pointer into the request, e.g. '/elements/2/children/0/text_color'.
    - `target_id` string, nullable: Id of the element, frame, instance or group the finding is about; inside an instance, its expanded path (e.g. 'card1/title'). Null when it names none.
    - `bbox` number[], nullable: Canvas-space box [x, y, w, h] in pixels for layout findings (B1); null otherwise.
    - `time` number, nullable: Timeline second at which the finding applies; null for whole-timeline findings.

## Other responses

- `401`: Missing or invalid API key.
- `402`: plan_required: custom templates need a higher plan (only when the gate is on).
- `404`: Project not found, or an apply_template names a template you cannot read.
- `409`: if_version did not match the current head version.
- `422`: A structurally impossible op (unknown id, invalid patch).

---

[API](https://skmtc.dev/framelane/apis/framelane-api.md) · [All operations](https://skmtc.dev/framelane/apis/framelane-api/llms.txt) · [OpenAPI document](https://skmtc.dev/framelane/apis/framelane-api/revisions/a223001ca8e5?raw)
