> ## Documentation Index
> Fetch the complete documentation index at: https://www.tella.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Tella Motion commands

> Every command and op that apply_motion_editor_commands and POST /v1/motion/documents/{id}/commands accept: 38 editor commands and 19 ops, each with its fields, limits and a working example.

A [Tella Motion](https://www.tella.com/docs/mcp-tools/motion.md) batch is a list of up to 256 items, each an editor command (a camelCase `type` such as `addTitle`) or an op (a dotted `type` such as `node.patch`). This page lists all 38 editor commands and 19 ops with every field they take.

## Send a batch

The same items work over MCP and the REST API. With the MCP server, pass them to `apply_motion_editor_commands`:

```json theme={"system"}
{
  "documentId": "ffd_abc123",
  "baseRevision": 12,
  "commands": [
    {
      "type": "addTitle",
      "nodeId": "title",
      "start": 0,
      "duration": 3,
      "text": "Quarterly update"
    },
    {
      "type": "animate",
      "node": "title",
      "in": {
        "kind": "fade",
        "duration": 0.5
      }
    }
  ]
}
```

With the REST API, send the same `commands` (and optional `baseRevision` and `dryRun`) to `POST /v1/motion/documents/{id}/commands`:

```bash theme={"system"}
curl -X POST https://api.tella.com/v1/motion/documents/ffd_abc123/commands \
  -H "Authorization: Bearer $TELLA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"baseRevision": 12, "commands": [{"type": "addTitle", "nodeId": "title", "start": 0, "duration": 3, "text": "Quarterly update"}]}'
```

Items apply in order as one transaction: if any fails, nothing applies, and the error names the failing item's index. For one item's exact JSON Schema, call `get_motion_schema` with `schema: "edits"` and its `type` (REST: `GET /v1/motion/schema?schema=edits&type=addTitle`).

## Conventions

* **Nodes** are named by ID (`node`, `nodes`, `parent`, fields typed node id). IDs are unique in the document, inside compositions too; `get_motion_document` lists them. A path through instances (`scene.1/title`) works too.
* **Commands** take times in seconds on the clock of the composition the node sits in, and property values in the units [Properties, easing and fonts](https://www.tella.com/docs/mcp-tools/motion-values.md) lists (pixels, degrees, decibels, and fractions for percentages: opacity `0.5` is half).
* **Ops** take raw render timeline values: times as ticks or `{"seconds": 1.5}`, gains as linear numbers or `{"db": -6}`.
* **New IDs**: leave `nodeId`, `effectId` or `newNodeId` out to have one generated. The batch result lists created nodes in `affected.created`.
* **Prefer a command** for anything the editor does: it keeps the editor's conventions (effect wrappers, keys on the node's own clock, title and shape styles), so the result stays editable by hand. Use ops for the rest.

## The example document

Every example on this page runs in Tella's test suite on a 1920×1080, 4-second document that holds a background shape `bg`, a sequence `camera` of two video clips `cam.1` and `cam.2`, a voice track `voice`, and what this batch adds:

```json theme={"system"}
[
  {
    "type": "addTitle",
    "nodeId": "title",
    "start": 0,
    "duration": 4,
    "text": "Quarterly update",
    "box": {
      "x": 160,
      "y": 420,
      "width": 1600
    },
    "font": "inter",
    "fontSize": 120,
    "weight": 700,
    "color": "#ffffff",
    "align": "center"
  },
  {
    "type": "addShape",
    "nodeId": "card",
    "start": 0,
    "duration": 4,
    "shape": "rect",
    "box": {
      "x": 160,
      "y": 760,
      "width": 800,
      "height": 160
    },
    "color": "#111827",
    "radius": 24
  },
  {
    "type": "addMedia",
    "nodeId": "music",
    "sourceId": "su_music",
    "start": 0
  }
]
```

`su_music` and `su_logo` stand for uploads of your own: a source ID from `create_source`, a chat attachment or a library item's `sourceId`. The `fitCanvas` example runs on an overlay's document holding only that batch.

## All commands

| Command | What it does |
| - | - |
| [`addTitle`](#addtitle) | Adds a title (text). `box` places the top-left of its first line and the width it wraps and aligns in. |
| [`addShape`](#addshape) | Adds a shape: `fill` covers its composition, `rect` and `circle` draw in `box`. A `#00000000` fill with backdrop effects is an adjustment layer. |
| [`addMedia`](#addmedia) | Places a video, image or sound: `mediaId` for media already in the document (`list_motion_media`), or `sourceId` for an upload, which Tella reads as the image, video or sound it is. |
| [`addPath`](#addpath) | Adds a vector path from SVG path data in canvas pixels, filled, stroked or both. |
| [`setTitle`](#settitle) | Changes a title's text, box, font, size, colour or spacing. |
| [`setShape`](#setshape) | Changes a shape's kind, box, colour or corner radius. |
| [`setPath`](#setpath) | Changes a path's points or paint, or morphs it between keyed paths (matching point counts morph point for point). |
| [`split`](#split) | Splits a node in two at a time. |
| [`trim`](#trim) | Moves a node's start or end. A new start trims the head: what plays stays put. |
| [`delete`](#delete) | Removes a node. `ripple` closes the gap it leaves in a sequence. |
| [`duplicate`](#duplicate) | Copies a node, with its keys and effects, next to itself. |
| [`setSpeed`](#setspeed) | Plays a node faster or slower (0.05 to 20). |
| [`setTransition`](#settransition) | Sets the transition into a sequence child from the child that ends where it starts. |
| [`stagger`](#stagger) | Starts each node `interval` seconds after the one before, moving their keys and effects with them. |
| [`setTimeRemap`](#settimeremap) | Replaces a media node's or composition instance's timing with keys of which source time shows when: slow motion, speed ramps and freeze frames (`hold`). Media plays forward only. |
| [`setMarkers`](#setmarkers) | Sets every marker a node keeps: moments or spans with labels, for timing other nodes to. |
| [`setProperty`](#setproperty) | Sets a property. On an animated property `at` is required and sets the key there; without `value` it adds a key holding the value at `at`. `blendMode`, `flipX` and `flipY` are set without `at`. |
| [`setKeyframes`](#setkeyframes) | Replaces a property's animation with these keys. `easing: "continuous"` carries speed through each key, like After Effects' auto bezier. |
| [`editKeyframe`](#editkeyframe) | Moves, revalues or re-eases one key by its id (`inspect_motion_node_properties` lists ids), or re-eases several keys, or every key when neither `key` nor `keys` is given. |
| [`removeKeyframes`](#removekeyframes) | Removes keys by id or time, or all of them, leaving the property at its value there. |
| [`animate`](#animate) | Applies an In or Out preset: two keys on opacity, position or scale from an edge at the node's start, or to one at its end. They are ordinary keys afterwards. |
| [`setExpression`](#setexpression) | Adds a wiggle (random drift over the property's value) or a loop (repeats its keys) to a property, or removes one. The value and keys underneath stay editable. |
| [`setTextAnimation`](#settextanimation) | Animates a title per character, word or line as it comes in or goes out. `preset: null` removes it. |
| [`addEffect`](#addeffect) | Adds an effect to a node's stack. `kind: "shader"` runs your own SkSL. `backdrop: true` filters what is beneath the node instead (frosted glass; on a transparent shape, an adjustment layer). |
| [`setEffect`](#seteffect) | Changes an effect's parameters or preset, or switches it off (`enabled: false` keeps its settings and keys). |
| [`removeEffect`](#removeeffect) | Removes an effect from a node's stack. |
| [`moveEffect`](#moveeffect) | Moves an effect in the stack; index 0 applies first. |
| [`setMasks`](#setmasks) | Replaces a node's masks (rectangles, ellipses or SVG paths), applied before its effects. `[]` removes them. |
| [`setMatte`](#setmatte) | Shows a node through another node of the same composition (a track matte), by its alpha or luma. The matte node is hidden unless `showMatte`. |
| [`applyLook`](#applylook) | Copies one node's style (opacity, blend, flips, corners, colour, blur, shadow) and effect stack onto others. Where they sit and how big they are stay their own. |
| [`group`](#group) | Groups sibling nodes into one layer in the topmost one's place, to move, fade or add effects to them together. |
| [`ungroup`](#ungroup) | Puts a group's nodes back in its place. A moved group leaves a hidden null they follow; a clipped or filtered group is refused. |
| [`fitCanvas`](#fitcanvas) | Shrinks the canvas to what its nodes draw at rest, plus `padding`, moving them and their keys with it. Its note gives the rectangle to place the overlay at so the content stays where it was in the video. |
| [`setCurve`](#setcurve) | Sets a named curve: a number, keys on the canvas clock, or an expression over other curves. `null` removes it. |
| [`bindProperty`](#bindproperty) | Binds a node's property to an expression: a curve, the time, arithmetic over them, or where a canvas point lands through a lens shader's camera. Bound to a title's `text`, the title shows the number. |
| [`setFades`](#setfades) | Sets a sound's fade in and fade out, in seconds. |
| [`duck`](#duck) | Turns a sound (music) down while other nodes (voice) play. Its level and fades stay editable; `under: []` removes the duck. |
| [`expect`](#expect) | Fails the batch unless a node exists (or not), or a property has a value at a time, within `tolerance`. |

## Add content

Each adds one node on top of what is there, under `parent` (a sequence, a group, a composition instance, or the canvas when left out). Leave `nodeId` out to have one generated: the batch result lists it in `affected.created`.

### addTitle

Adds a title (text). `box` places the top-left of its first line and the width it wraps and aligns in.

| Field | Type | Required | Description |
| - | - | - | - |
| `parent` | string | | Where the new node goes: a sequence, a group, a composition instance (its composition's root), or "" for the canvas. The canvas when left out. |
| `nodeId` | string | | The new node's id; generated when left out. |
| `start` | number, ≥ 0 | Yes | On the clock of the composition it goes in. |
| `duration` | number, > 0 | | Seconds; 3 by default. |
| `text` | string | | What it says; `Title` for a new title. |
| `box` | object | | Where the text lays out, px in its composition: x, y the top-left of the first line's box (fontSize × lineHeight tall, glyphs sit inside it by their font's metrics), width what it wraps and aligns in. |
| `box.x` | number | Yes | |
| `box.y` | number | Yes | |
| `box.width` | number, > 0 | Yes | |
| `font` | string | | A font from list\_motion\_fonts: a catalog slug, e.g. inter, or a font uploaded to your library or your workspace's by its id (mi\_…). |
| `fontSize` | number, 0–2000 | | Px; 96 for a new title. |
| `color` | colour `#rrggbb` | | Text colour; white for a new title. |
| `align` | `left` \| `center` \| `right` | | Within the box's width; center for a new title. |
| `weight` | integer, 100–900 | | 100 (thin) to 900 (black); list\_motion\_fonts gives each font's range, and a font draws the nearest weight it has. |
| `letterSpacing` | number, -1–2 | | Added between characters, in em: 0.05 is 5% of the font size. 0 for a new title. |
| `lineHeight` | number, 0.5–4 | | A multiple of the font size; 1.2 for a new title. |

Example item:

```json theme={"system"}
{
  "type": "addTitle",
  "nodeId": "subtitle",
  "start": 0.5,
  "duration": 3,
  "text": "Revenue up 40%",
  "box": {
    "x": 160,
    "y": 580,
    "width": 1600
  },
  "font": "inter",
  "fontSize": 64,
  "weight": 500,
  "color": "#cbd5e1",
  "align": "center"
}
```

### addShape

Adds a shape: `fill` covers its composition, `rect` and `circle` draw in `box`. A `#00000000` fill with backdrop effects is an adjustment layer.

| Field | Type | Required | Description |
| - | - | - | - |
| `parent` | string | | Where the new node goes: a sequence, a group, a composition instance (its composition's root), or "" for the canvas. The canvas when left out. |
| `nodeId` | string | | The new node's id; generated when left out. |
| `start` | number, ≥ 0 | Yes | On the clock of the composition it goes in. |
| `duration` | number, > 0 | | Seconds; 3 by default. |
| `shape` | `fill` \| `rect` \| `circle` | | fill covers its composition; rect when box is given. |
| `box` | object | | Where a rect or circle draws, px in its composition; a circle fits the box's smaller side, centred. A rect or circle keeps its box when left out. |
| `box.x` | number | Yes | |
| `box.y` | number | Yes | |
| `box.width` | number, > 0 | Yes | |
| `box.height` | number, > 0 | Yes | |
| `color` | colour `#rrggbb` or `#rrggbbaa` | | #rrggbbaa for a see-through fill; #00000000 makes a shape whose backdrop effects adjust what is beneath it. |
| `radius` | number, ≥ 0 | | A rect's corner radius, px. |

Example item:

```json theme={"system"}
{
  "type": "addShape",
  "nodeId": "dot",
  "start": 0,
  "duration": 4,
  "shape": "circle",
  "box": {
    "x": 900,
    "y": 200,
    "width": 120,
    "height": 120
  },
  "color": "#22c55e"
}
```

### addMedia

Places a video, image or sound: `mediaId` for media already in the document (`list_motion_media`), or `sourceId` for an upload, which Tella reads as the image, video or sound it is.

| Field | Type | Required | Description |
| - | - | - | - |
| `parent` | string | | Where the new node goes: a sequence, a group, a composition instance (its composition's root), or "" for the canvas. The canvas when left out. |
| `nodeId` | string | | The new node's id; generated when left out. |
| `start` | number, ≥ 0 | Yes | On the clock of the composition it goes in. |
| `mediaId` | string | | Media already in the document, from list\_motion\_media. |
| `sourceId` | string | | Or an upload (su\_…): a source from create\_source, a chat attachment, or a library item's sourceId. Tella reads from the upload whether it is an image, a video or a sound and declares the asset to match. Pass exactly one of mediaId and sourceId. |
| `duration` | number, > 0 | | How long it plays: an image 3s by default; video and audio play from their start for this long, else whole, cut where the composition ends. |
| `ripple` | boolean | | Into a sequence: what starts at start or later moves on to make room, instead of refusing an overlap. A clip that start falls inside is still refused; split it first. |

Example item:

```json theme={"system"}
{
  "type": "addMedia",
  "nodeId": "logo",
  "sourceId": "su_logo",
  "start": 0,
  "duration": 4
}
```

### addPath

Adds a vector path from SVG path data in canvas pixels, filled, stroked or both.

| Field | Type | Required | Description |
| - | - | - | - |
| `parent` | string | | Where the new node goes: a sequence, a group, a composition instance (its composition's root), or "" for the canvas. The canvas when left out. |
| `nodeId` | string | | The new node's id; generated when left out. |
| `start` | number, ≥ 0 | Yes | On the clock of the composition it goes in. |
| `duration` | number, > 0 | | Seconds; 3 by default. |
| `path` | string | Yes | SVG path data in canvas pixels. |
| `color` | colour `#rrggbb` | | Fill and stroke colour. |
| `paint` | `fill` \| `stroke` \| `both` | | Fill the path's inside, stroke its line, or both. |
| `strokeWidth` | number, 0–1000 | | Px. |
| `cap` | `butt` \| `round` \| `square` | | How a stroke's open ends are drawn. |
| `join` | `miter` \| `round` \| `bevel` | | How a stroke's corners are drawn. |

Example item:

```json theme={"system"}
{
  "type": "addPath",
  "nodeId": "underline",
  "start": 0.5,
  "duration": 3.5,
  "path": "M 560 560 L 1360 560",
  "paint": "stroke",
  "strokeWidth": 8,
  "cap": "round",
  "color": "#f43f5e"
}
```

## Change content

Change what a title, shape or path shows. Fields you leave out keep their value.

### setTitle

Changes a title's text, box, font, size, colour or spacing.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `text` | string | | What it says; `Title` for a new title. |
| `box` | object | | Where the text lays out, px in its composition: x, y the top-left of the first line's box (fontSize × lineHeight tall, glyphs sit inside it by their font's metrics), width what it wraps and aligns in. |
| `box.x` | number | Yes | |
| `box.y` | number | Yes | |
| `box.width` | number, > 0 | Yes | |
| `font` | string | | A font from list\_motion\_fonts: a catalog slug, e.g. inter, or a font uploaded to your library or your workspace's by its id (mi\_…). |
| `fontSize` | number, 0–2000 | | Px; 96 for a new title. |
| `color` | colour `#rrggbb` | | Text colour; white for a new title. |
| `align` | `left` \| `center` \| `right` | | Within the box's width; center for a new title. |
| `weight` | integer, 100–900 | | 100 (thin) to 900 (black); list\_motion\_fonts gives each font's range, and a font draws the nearest weight it has. |
| `letterSpacing` | number, -1–2 | | Added between characters, in em: 0.05 is 5% of the font size. 0 for a new title. |
| `lineHeight` | number, 0.5–4 | | A multiple of the font size; 1.2 for a new title. |

Example item:

```json theme={"system"}
{
  "type": "setTitle",
  "node": "title",
  "text": "Q3 results",
  "fontSize": 140
}
```

### setShape

Changes a shape's kind, box, colour or corner radius.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `shape` | `fill` \| `rect` \| `circle` | | fill covers its composition; rect when box is given. |
| `box` | object | | Where a rect or circle draws, px in its composition; a circle fits the box's smaller side, centred. A rect or circle keeps its box when left out. |
| `box.x` | number | Yes | |
| `box.y` | number | Yes | |
| `box.width` | number, > 0 | Yes | |
| `box.height` | number, > 0 | Yes | |
| `color` | colour `#rrggbb` or `#rrggbbaa` | | #rrggbbaa for a see-through fill; #00000000 makes a shape whose backdrop effects adjust what is beneath it. |
| `radius` | number, ≥ 0 | | A rect's corner radius, px. |

Example item:

```json theme={"system"}
{
  "type": "setShape",
  "node": "card",
  "color": "#1e3a8a",
  "radius": 32
}
```

### setPath

Changes a path's points or paint, or morphs it between keyed paths (matching point counts morph point for point).

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `path` | string | | New points, constant. |
| `keys` | array of objects, up to 100 | | Morph keys: the shape takes each path at its time, morphing between (matching point counts morph point for point). |
| `keys[].at` | number, ≥ 0 | Yes | Seconds on the node's composition clock. |
| `keys[].path` | string | Yes | SVG path data at this key. |
| `keys[].easing` | [easing](https://www.tella.com/docs/mcp-tools/motion-values.md#easing) | | Into the next key. |
| `color` | colour `#rrggbb` | | Fill and stroke colour. |
| `paint` | `fill` \| `stroke` \| `both` | | Fill the path's inside, stroke its line, or both. |
| `strokeWidth` | number, 0–1000 | | Px. |
| `cap` | `butt` \| `round` \| `square` | | How a stroke's open ends are drawn. |
| `join` | `miter` \| `round` \| `bevel` | | How a stroke's corners are drawn. |

A batch that uses it:

```json theme={"system"}
[
  {
    "type": "addPath",
    "nodeId": "wave",
    "start": 0,
    "duration": 4,
    "path": "M 200 900 L 1720 900",
    "paint": "stroke",
    "strokeWidth": 6,
    "color": "#38bdf8"
  },
  {
    "type": "setPath",
    "node": "wave",
    "keys": [
      {
        "at": 0,
        "path": "M 200 900 L 1720 900"
      },
      {
        "at": 1.5,
        "path": "M 200 900 L 1720 700",
        "easing": "easeInOutCubic"
      }
    ]
  }
]
```

## Timing

Times are seconds on the clock of the composition the node sits in. `ripple` only matters in a sequence, where nodes play one after another.

### split

Splits a node in two at a time.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `at` | number, ≥ 0 | Yes | Where to split, on the node's composition clock. |
| `newNodeId` | string | | The second half's id; generated when left out. |

Example item:

```json theme={"system"}
{
  "type": "split",
  "node": "card",
  "at": 2,
  "newNodeId": "card.tail"
}
```

### trim

Moves a node's start or end. A new start trims the head: what plays stays put.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `start` | number, ≥ 0 | | The new start; what plays stays put. |
| `end` | number, ≥ 0 | | The new end. |
| `ripple` | boolean | | In a sequence, the node keeps its start and what follows moves by as much as its length changes. |

Example item:

```json theme={"system"}
{
  "type": "trim",
  "node": "cam.1",
  "end": 1.5,
  "ripple": true
}
```

### delete

Removes a node. `ripple` closes the gap it leaves in a sequence.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `ripple` | boolean | | In a sequence, close the gap it leaves. |

Example item:

```json theme={"system"}
{
  "type": "delete",
  "node": "cam.2",
  "ripple": true
}
```

### duplicate

Copies a node, with its keys and effects, next to itself.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `newNodeId` | string | | The copy's id; generated when left out. |

Example item:

```json theme={"system"}
{
  "type": "duplicate",
  "node": "card",
  "newNodeId": "card2"
}
```

### setSpeed

Plays a node faster or slower (0.05 to 20).

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `speed` | number, 0.05–20 | Yes | 2 plays twice as fast. |

Example item:

```json theme={"system"}
{
  "type": "setSpeed",
  "node": "cam.2",
  "speed": 2
}
```

### setTransition

Sets the transition into a sequence child from the child that ends where it starts.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | string | Yes | The sequence child the transition leads into; another child must end where it starts. |
| `kind` | `none` \| `crossfade` \| `left` \| `right` \| `up` \| `down` | Yes | crossfade dissolves; left, right, up and down wipe in that direction; none removes the transition. |
| `duration` | number, > 0 | | Seconds, 0.5 by default; never longer than either child. |

Example item:

```json theme={"system"}
{
  "type": "setTransition",
  "node": "cam.2",
  "kind": "crossfade",
  "duration": 0.5
}
```

### stagger

Starts each node `interval` seconds after the one before, moving their keys and effects with them.

| Field | Type | Required | Description |
| - | - | - | - |
| `nodes` | array of node id, up to 200 | Yes | In the order they start; each moves with its keys and effects. |
| `interval` | number | Yes | Seconds between one node's start and the next's. |
| `start` | number, ≥ 0 | | Where the first starts; where it starts now when left out. |

Example item:

```json theme={"system"}
{
  "type": "stagger",
  "nodes": [
    "title",
    "card"
  ],
  "interval": 0.15
}
```

### setTimeRemap

Replaces a media node's or composition instance's timing with keys of which source time shows when: slow motion, speed ramps and freeze frames (`hold`). Media plays forward only.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `keys` | array of objects, up to 200 or `null` | Yes | Replaces the node's timing with these keys (media forward only; an instance can reverse); null restores it. |
| `keys[].at` | number, ≥ 0 | Yes | On the node's composition clock. |
| `keys[].source` | number, ≥ 0 | Yes | The media's (or instance's composition's) time shown there. |
| `keys[].easing` | `linear` \| `hold` \| `ease` | | Into the next key; hold freezes the frame. |

Example item:

```json theme={"system"}
{
  "type": "setTimeRemap",
  "node": "cam.1",
  "keys": [
    {
      "at": 0,
      "source": 0
    },
    {
      "at": 1,
      "source": 0.5,
      "easing": "hold"
    },
    {
      "at": 1.5,
      "source": 0.5
    },
    {
      "at": 2,
      "source": 1.5
    }
  ]
}
```

### setMarkers

Sets every marker a node keeps: moments or spans with labels, for timing other nodes to.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `markers` | array of objects, up to 4096 | Yes | Every marker the node has afterwards; \[] removes them all. |
| `markers[].id` | string | | Keep a marker's id to edit it; generated when left out. |
| `markers[].at` | number, ≥ 0 | Yes | On the node's composition clock, at or after its start. |
| `markers[].duration` | number, > 0 | | A span rather than a moment. |
| `markers[].label` | string | | Its text. |

Example item:

```json theme={"system"}
{
  "type": "setMarkers",
  "node": "title",
  "markers": [
    {
      "at": 0.8,
      "label": "Revenue"
    },
    {
      "at": 2.4,
      "duration": 1,
      "label": "Chart"
    }
  ]
}
```

## Animate properties

Properties work as in After Effects: a value is constant until you key it, then it follows its keys. Property names, units and easing names are listed in [Properties, easing and fonts](https://www.tella.com/docs/mcp-tools/motion-values.md). Keys are set at times on the node's composition clock, so a node that starts at 2 s is keyed from 2.

### setProperty

Sets a property. On an animated property `at` is required and sets the key there; without `value` it adds a key holding the value at `at`. `blendMode`, `flipX` and `flipY` are set without `at`.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `property` | string | Yes | A property name (x, y, scale, rotation, opacity, volume, …) or a field:… path; inspect\_motion\_node\_properties lists a node's. |
| `value` | number or colour `#rrggbb` or boolean or one of 44 names | | Left out, a key at `at` holding the value the property has there. |
| `at` | number, ≥ 0 | | Where to set it when the property is animated: the key there is added or changed. Required then. |

Example item:

```json theme={"system"}
{
  "type": "setProperty",
  "node": "card",
  "property": "opacity",
  "value": 0.8
}
```

### setKeyframes

Replaces a property's animation with these keys. `easing: "continuous"` carries speed through each key, like After Effects' auto bezier.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `property` | string | Yes | A property name (x, y, scale, rotation, opacity, volume, …) or a field:… path; inspect\_motion\_node\_properties lists a node's. |
| `keys` | array of objects, up to 200 | Yes | |
| `keys[].at` | number, ≥ 0 | Yes | Seconds on the node's composition clock. |
| `keys[].value` | number or colour `#rrggbb` | Yes | In the property's units. |
| `keys[].easing` | [easing](https://www.tella.com/docs/mcp-tools/motion-values.md#easing) or `continuous` | | Into the next key. continuous (After Effects' auto bezier) carries the speed through the keys around it, so a fast move eases into a slow one and out again without a jolt and without overshooting; numbers only. |
| `easing` | [easing](https://www.tella.com/docs/mcp-tools/motion-values.md#easing) or `continuous` | | Every key's easing into the next, unless the key sets its own. |

Example item:

```json theme={"system"}
{
  "type": "setKeyframes",
  "node": "title",
  "property": "y",
  "keys": [
    {
      "at": 0,
      "value": 520,
      "easing": "easeOutCubic"
    },
    {
      "at": 0.6,
      "value": 420
    }
  ]
}
```

### editKeyframe

Moves, revalues or re-eases one key by its id (`inspect_motion_node_properties` lists ids), or re-eases several keys, or every key when neither `key` nor `keys` is given.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `property` | string | Yes | A property name (x, y, scale, rotation, opacity, volume, …) or a field:… path; inspect\_motion\_node\_properties lists a node's. |
| `key` | string | | A keyframe's id, as inspect\_motion\_node\_properties lists it; it stays as the key moves. |
| `keys` | array of string, up to 200 | | Several keys to ease at once; every key when neither key nor keys is given. Easing only. |
| `at` | number, ≥ 0 | | Moves the key here. |
| `value` | number or colour `#rrggbb` | | The key's new value. |
| `easing` | [easing](https://www.tella.com/docs/mcp-tools/motion-values.md#easing) | | Into the next key. |

A batch that uses it:

```json theme={"system"}
[
  {
    "type": "setKeyframes",
    "node": "card",
    "property": "x",
    "keys": [
      {
        "at": 0,
        "value": -400
      },
      {
        "at": 1,
        "value": 160
      }
    ]
  },
  {
    "type": "editKeyframe",
    "node": "card",
    "property": "x",
    "easing": "springGentle"
  }
]
```

### removeKeyframes

Removes keys by id or time, or all of them, leaving the property at its value there.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `property` | string | Yes | A property name (x, y, scale, rotation, opacity, volume, …) or a field:… path; inspect\_motion\_node\_properties lists a node's. |
| `keys` | array of string, up to 200 | | The keys' ids. |
| `at` | array of number, ≥ 0 | | The keys' times; all of them (the property holds its value there) when left out. |

A batch that uses it:

```json theme={"system"}
[
  {
    "type": "setKeyframes",
    "node": "card",
    "property": "rotation",
    "keys": [
      {
        "at": 0,
        "value": -10
      },
      {
        "at": 1,
        "value": 0
      }
    ]
  },
  {
    "type": "removeKeyframes",
    "node": "card",
    "property": "rotation"
  }
]
```

### animate

Applies an In or Out preset: two keys on opacity, position or scale from an edge at the node's start, or to one at its end. They are ordinary keys afterwards.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `in` | object | | Keys from an edge at its start to where it is duration later. |
| `in.kind` | `fade` \| `slideLeft` \| `slideRight` \| `slideUp` \| `slideDown` \| `pop` | Yes | |
| `in.duration` | number, > 0 | Yes | Seconds. |
| `in.easing` | [easing](https://www.tella.com/docs/mcp-tools/motion-values.md#easing) | | |
| `out` | object | | Keys from where it is duration before its end to an edge there. |
| `out.kind` | `fade` \| `slideLeft` \| `slideRight` \| `slideUp` \| `slideDown` \| `pop` | Yes | |
| `out.duration` | number, > 0 | Yes | Seconds. |
| `out.easing` | [easing](https://www.tella.com/docs/mcp-tools/motion-values.md#easing) | | |

Example item:

```json theme={"system"}
{
  "type": "animate",
  "node": "title",
  "in": {
    "kind": "slideUp",
    "duration": 0.6,
    "easing": "easeOutCubic"
  },
  "out": {
    "kind": "fade",
    "duration": 0.4
  }
}
```

### setExpression

Adds a wiggle (random drift over the property's value) or a loop (repeats its keys) to a property, or removes one. The value and keys underneath stay editable.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `property` | string | Yes | A property name (x, y, scale, rotation, opacity, volume, …) or a field:… path; inspect\_motion\_node\_properties lists a node's. |
| `expression` | object | Yes | |
| `expression.kind` | `wiggle` \| `noWiggle` \| `loop` \| `noLoop` | Yes | Which kind; the fields below say which kinds take them. |
| `expression.frequency` | number, 0–100 | Yes | `wiggle` only. Wiggles per second. |
| `expression.amount` | number, ≥ 0 | Yes | `wiggle` only. How far it wanders, in the property's units. |
| `expression.seed` | integer, 0–1000000 | | `wiggle` only. Another seed gives another random path. |
| `expression.direction` | `in` \| `out` | Yes | `loop` only. out repeats after the last key (loopOut), in before the first. |
| `expression.mode` | `cycle` \| `pingPong` \| `offset` \| `continue` | Yes | `loop` only. cycle repeats the keys, pingPong plays them back and forth, offset repeats them from where the last cycle ended, continue carries on at the last key's speed. |
| `expression.keyframes` | integer, ≥ 2 | | `loop` only. Loop only the last (or first) this many keys. |
| `expression.direction` | `in` \| `out` | Yes | `noLoop` only. |

Example item:

```json theme={"system"}
{
  "type": "setExpression",
  "node": "card",
  "property": "rotation",
  "expression": {
    "kind": "wiggle",
    "frequency": 2,
    "amount": 3
  }
}
```

### setTextAnimation

Animates a title per character, word or line as it comes in or goes out. `preset: null` removes it.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `phase` | `in` \| `out` | Yes | in as the title appears; out ends as the title ends. |
| `preset` | `typewriter` \| `fade` \| `rise` \| `drop` \| `pop` \| `blur` \| `slideLeft` \| `slideRight` \| `spread` \| `spin` or `null` | Yes | null removes the phase's animation. |
| `unit` | `character` \| `word` \| `line` | | What animates one at a time; character by default. |
| `duration` | number, 0–10 | | Seconds each unit takes (0.4 by default). |
| `stagger` | number, 0–5 | | Seconds between units' starts (0.04 by default). |
| `from` | `start` \| `end` \| `center` \| `edges` \| `random` | | Which units go first; start by default. |

Example item:

```json theme={"system"}
{
  "type": "setTextAnimation",
  "node": "title",
  "phase": "in",
  "preset": "rise",
  "unit": "word",
  "duration": 0.5,
  "stagger": 0.08
}
```

## Effects, masks and mattes

A node's effect stack applies after the inspector's own colour, blur and shadow, innermost (index 0) first. Effect kinds and their parameters are listed in [Tella Motion effects](https://www.tella.com/docs/mcp-tools/motion-effects.md). After it is added, an effect's numbers are properties (`field:@e.<effectId>/effect/<param>`) you can key like any other.

### addEffect

Adds an effect to a node's stack. `kind: "shader"` runs your own SkSL. `backdrop: true` filters what is beneath the node instead (frosted glass; on a transparent shape, an adjustment layer).

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `kind` | string | Yes | An effect kind from list\_motion\_effects: a render timeline effect type (blur, glow, dropShadow, …), a built-in shader's name (fx.levels, fx.vignette, …), or shader for your own SkSL in source. |
| `params` | object of name → value | | Parameters by name, as list\_motion\_effects gives them: numbers, #rrggbb(aa) colours, \[x, y] points, true/false, an option, or a node path. |
| `preset` | string | | A preset of the kind, by name from list\_motion\_effects; list\_motion\_effects \{kind, preset} shows the settings it sets, which params then override. |
| `source` | string | | For kind shader: SkSL with `uniform shader image;` (the node) and `half4 main(float2 p)`; `uniform float2 resolution` is bound to the node's size. |
| `uniforms` | object of name → value | | For kind shader: each other uniform's values, as many as it declares (float 1, float2 2, half4 4); the renderer checks them against the SkSL. Each value keys as a property afterwards. |
| `inputs` | object of name → value | | For kind shader: each `uniform shader` input, a node of the same composition. |
| `effectId` | string | | The effect's id in the node's stack, as inspect\_motion\_node\_properties lists it. |
| `index` | integer, ≥ 0 | | Place in the stack, 0 innermost (applied first); outermost when left out. |
| `backdrop` | boolean | | Filter what is drawn beneath the node's bounds instead of the node (frosted glass; on a transparent shape, an adjustment layer). Pixel filters only. |

Example item:

```json theme={"system"}
{
  "type": "addEffect",
  "node": "title",
  "kind": "glow",
  "effectId": "glow1",
  "params": {
    "radius": 40,
    "intensity": 1.5
  }
}
```

### setEffect

Changes an effect's parameters or preset, or switches it off (`enabled: false` keeps its settings and keys).

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `effectId` | string | Yes | The effect's id in the node's stack, as inspect\_motion\_node\_properties lists it. |
| `params` | object of name → value | | Parameters by name, as list\_motion\_effects gives them: numbers, #rrggbb(aa) colours, \[x, y] points, true/false, an option, or a node path. |
| `preset` | string | | A preset of the kind, by name from list\_motion\_effects; list\_motion\_effects \{kind, preset} shows the settings it sets, which params then override. |
| `enabled` | boolean | | The effect's switch: false draws the node unaffected, keeping its settings and keyframes. |
| `backdrop` | boolean | | Filter what is drawn beneath the node's bounds instead of the node (frosted glass; on a transparent shape, an adjustment layer). Pixel filters only. |

A batch that uses it:

```json theme={"system"}
[
  {
    "type": "addEffect",
    "node": "title",
    "kind": "glow",
    "effectId": "glow1",
    "params": {
      "radius": 40,
      "intensity": 1.5
    }
  },
  {
    "type": "setEffect",
    "node": "title",
    "effectId": "glow1",
    "params": {
      "radius": 80
    }
  }
]
```

### removeEffect

Removes an effect from a node's stack.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `effectId` | string | Yes | The effect's id in the node's stack, as inspect\_motion\_node\_properties lists it. |

A batch that uses it:

```json theme={"system"}
[
  {
    "type": "addEffect",
    "node": "title",
    "kind": "glow",
    "effectId": "glow1",
    "params": {
      "radius": 40,
      "intensity": 1.5
    }
  },
  {
    "type": "removeEffect",
    "node": "title",
    "effectId": "glow1"
  }
]
```

### moveEffect

Moves an effect in the stack; index 0 applies first.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `effectId` | string | Yes | The effect's id in the node's stack, as inspect\_motion\_node\_properties lists it. |
| `index` | integer, ≥ 0 | Yes | Its new place, 0 innermost (applied first). |

A batch that uses it:

```json theme={"system"}
[
  {
    "type": "addEffect",
    "node": "title",
    "kind": "glow",
    "effectId": "glow1",
    "params": {
      "radius": 40,
      "intensity": 1.5
    }
  },
  {
    "type": "addEffect",
    "node": "title",
    "kind": "fx.vignette",
    "effectId": "vig1"
  },
  {
    "type": "moveEffect",
    "node": "title",
    "effectId": "vig1",
    "index": 0
  }
]
```

### setMasks

Replaces a node's masks (rectangles, ellipses or SVG paths), applied before its effects. `[]` removes them.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `masks` | array of objects, up to 32 | Yes | Replaces the node's masks, applied before its effects; \[] removes them. Their numbers key as properties afterwards. |
| `masks[].shape` | `rect` \| `ellipse` \| `path` | Yes | |
| `masks[].x` | number | | Left of a rect or ellipse, in the pixels the node is placed in: masks do not follow its position, scale or rotation, so key them with it. |
| `masks[].y` | number | | Top of a rect or ellipse, in the pixels the node is placed in. |
| `masks[].width` | number, > 0 | | |
| `masks[].height` | number, > 0 | | |
| `masks[].radius` | number, ≥ 0 | | A rect's corner radius. |
| `masks[].path` | string | | SVG path data in the pixels the node is placed in. |
| `masks[].mode` | `add` \| `subtract` \| `intersect` \| `lighten` \| `darken` \| `difference` | | How it combines with the masks before it; add by default. |
| `masks[].feather` | number, 0–1000 | | Px of soft edge. |
| `masks[].expansion` | number, -1000–1000 | | Px the mask grows by; negative shrinks it. |
| `masks[].inverted` | boolean | | Keep what is outside the mask instead. |
| `masks[].opacity` | number, 0–1 | | |

Example item:

```json theme={"system"}
{
  "type": "setMasks",
  "node": "bg",
  "masks": [
    {
      "shape": "ellipse",
      "x": 660,
      "y": 240,
      "width": 600,
      "height": 600,
      "feather": 40
    }
  ]
}
```

### setMatte

Shows a node through another node of the same composition (a track matte), by its alpha or luma. The matte node is hidden unless `showMatte`.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `matte` | node id or `null` | Yes | A node in the same composition; null removes the matte. |
| `mode` | `alpha` \| `alphaInverted` \| `luma` \| `lumaInverted` | | Show the node where the matte is opaque (alpha) or bright (luma), or the inverse; alpha by default. |
| `showMatte` | boolean | | Keep the matte node visible; it is hidden by default. |

Example item:

```json theme={"system"}
{
  "type": "setMatte",
  "node": "card",
  "matte": "title",
  "mode": "alpha"
}
```

### applyLook

Copies one node's style (opacity, blend, flips, corners, colour, blur, shadow) and effect stack onto others. Where they sit and how big they are stay their own.

| Field | Type | Required | Description |
| - | - | - | - |
| `from` | string | Yes | The node whose look is copied. |
| `nodes` | array of node id, up to 200 | Yes | The nodes that take it. |
| `include` | array of `style` \| `effects` | | style: opacity, blend, flips, corners, colour, blur and shadow; effects: the effect stack, keys included. Both when left out. Where each node sits and how big it is stay its own. |

Example item:

```json theme={"system"}
{
  "type": "applyLook",
  "from": "card",
  "nodes": [
    "bg"
  ],
  "include": [
    "style"
  ]
}
```

## Structure and canvas

Group nodes to move, fade or add effects to them as one, and fit the canvas to what it shows.

### group

Groups sibling nodes into one layer in the topmost one's place, to move, fade or add effects to them together.

| Field | Type | Required | Description |
| - | - | - | - |
| `nodes` | array of node id, up to 200 | Yes | Siblings to group. |
| `nodeId` | string | | The group's id; generated when left out. |
| `name` | string | | The group's name in the editor. |

Example item:

```json theme={"system"}
{
  "type": "group",
  "nodes": [
    "title",
    "card"
  ],
  "nodeId": "lockup",
  "name": "Title lockup"
}
```

### ungroup

Puts a group's nodes back in its place. A moved group leaves a hidden null they follow; a clipped or filtered group is refused.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |

A batch that uses it:

```json theme={"system"}
[
  {
    "type": "group",
    "nodes": [
      "title",
      "card"
    ],
    "nodeId": "lockup"
  },
  {
    "type": "ungroup",
    "node": "lockup"
  }
]
```

### fitCanvas

Shrinks the canvas to what its nodes draw at rest, plus `padding`, moving them and their keys with it. Its note gives the rectangle to place the overlay at so the content stays where it was in the video.

| Field | Type | Required | Description |
| - | - | - | - |
| `padding` | number, 0–2000 | | Px around the content, room for shadows and glows; 48 when left out. |

Example item:

```json theme={"system"}
{
  "type": "fitCanvas",
  "padding": 48
}
```

## Curves and bindings

Curves are After Effects' expression controls: named values on the canvas clock that nodes and shader uniforms can read, so one number drives many properties.

### setCurve

Sets a named curve: a number, keys on the canvas clock, or an expression over other curves. `null` removes it.

| Field | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | The curve's name. |
| `value` | number or array of objects, up to 1000 or expression or `null` | Yes | A number, keys, or an expression over other curves; null removes it. Every reader (bindProperty, a shader uniform) sees the same value at the same canvas time. |

Example item:

```json theme={"system"}
{
  "type": "setCurve",
  "name": "progress",
  "value": [
    {
      "at": 0,
      "value": 0,
      "easing": "ease"
    },
    {
      "at": 2,
      "value": 100
    }
  ]
}
```

### bindProperty

Binds a node's property to an expression: a curve, the time, arithmetic over them, or where a canvas point lands through a lens shader's camera. Bound to a title's `text`, the title shows the number.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | string | Yes | |
| `property` | string | Yes | A numeric property in the units commands take (x, y px, rotation °, scale and opacity as fractions, a field:… such as a shader uniform), or text on a title: its text becomes the number. |
| `to` | expression | Yes | \{curve}, \{time: 'seconds'}, \{op, args}, or \{project, axis}: where a canvas point lands through a camera, so a label follows a box a lens shader films. A plain number unbinds it. |
| `text` | object | | For text: around the number (0 decimals by default). |
| `text.prefix` | string | | |
| `text.suffix` | string | | |
| `text.decimals` | integer, 0–6 | | |

A batch that uses it:

```json theme={"system"}
[
  {
    "type": "setCurve",
    "name": "progress",
    "value": [
      {
        "at": 0,
        "value": 0
      },
      {
        "at": 2,
        "value": 100
      }
    ]
  },
  {
    "type": "bindProperty",
    "node": "title",
    "property": "text",
    "to": {
      "curve": "progress"
    },
    "text": {
      "suffix": "%",
      "decimals": 0
    }
  }
]
```

## Audio

`volume` (dB) and `pan` (-1 to 1) are properties of anything with sound, so they key with `setProperty` and `setKeyframes`.

### setFades

Sets a sound's fade in and fade out, in seconds.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | |
| `fadeIn` | number, ≥ 0 | | Seconds. |
| `fadeOut` | number, ≥ 0 | | Seconds. |

Example item:

```json theme={"system"}
{
  "type": "setFades",
  "node": "music",
  "fadeIn": 1,
  "fadeOut": 2
}
```

### duck

Turns a sound (music) down while other nodes (voice) play. Its level and fades stay editable; `under: []` removes the duck.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | string | Yes | The sound to turn down (music). |
| `under` | array of node id, up to 500 | Yes | Nodes (voice) it ducks under while they play; \[] removes the duck. |
| `amountDb` | number, -60–0 | | -12 by default. |
| `attack` | number, 0–5 | | Seconds, 0.3 by default. |
| `release` | number, 0–5 | | Seconds, 0.5 by default. |

Example item:

```json theme={"system"}
{
  "type": "duck",
  "node": "music",
  "under": [
    "voice"
  ],
  "amountDb": -14
}
```

## Guards

A guard checks the document and fails the batch, so nothing applies, when it is not as you expect.

### expect

Fails the batch unless a node exists (or not), or a property has a value at a time, within `tolerance`.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | | |
| `exists` | boolean | | true: the node must exist; false: it must not. |
| `property` | string | | A property name (x, y, scale, rotation, opacity, volume, …) or a field:… path; inspect\_motion\_node\_properties lists a node's. |
| `at` | number, ≥ 0 | | Seconds on the node's composition clock. |
| `value` | number or colour `#rrggbb` | | The property's value at `at` (its start by default). |
| `tolerance` | number, ≥ 0 | | How far the value may be from `value`. |

Example item:

```json theme={"system"}
{
  "type": "expect",
  "node": "title",
  "property": "opacity",
  "at": 0,
  "value": 1
}
```

## Ops

Ops are the edits the document stores, written with render timeline values. Use one when no command does what you need. `get_motion_schema` gives each op's full schema, including the `RenderNode` shapes that `node.insert` and `node.patch` take.

### document.rename

Renames the document.

| Field | Type | Required | Description |
| - | - | - | - |
| `name` | string, up to 256 characters | Yes | The new name. |

Example item:

```json theme={"system"}
{
  "type": "document.rename",
  "name": "Lower third: pricing"
}
```

### canvas.set

Sets the canvas size, frame rate or length. A new document is as large as the video and 10 s long.

| Field | Type | Required | Description |
| - | - | - | - |
| `canvas.width` | integer, 1–32768 | | Width in pixels. |
| `canvas.height` | integer, 1–32768 | | Height in pixels. |
| `canvas.duration` | ticks or `{"seconds": n}` | | How long the document plays. A Tella Motion clip lasts this long. |
| `canvas.frameRate` | `{numerator, denominator}` | | Frames per second as a fraction, e.g. `{"numerator": 30, "denominator": 1}`. |

Example item:

```json theme={"system"}
{
  "type": "canvas.set",
  "canvas": {
    "duration": {
      "seconds": 6
    }
  }
}
```

### node.insert

Inserts a node written as the render timeline stores it. Prefer `addTitle`, `addShape`, `addMedia` and `addPath`, which fill in the editor's conventions.

| Field | Type | Required | Description |
| - | - | - | - |
| `parent` | node id or `""` | Yes | A node with children, an instance (its composition's root), or `""` for the canvas. |
| `node` | RenderNode | Yes | The node. Its schema is `RenderNode` in `get_motion_schema` with `schema: "document"`. |
| `meta` | object | | Editor state: `name`, `color`, `muted`, `solo`, `locked`. |
| `index` | integer | | Paint position, counted from the back. |
| `ripple` | boolean | | In a sequence, what starts there or later moves on first. |

Example item:

```json theme={"system"}
{
  "type": "node.insert",
  "parent": "",
  "node": {
    "type": "shape",
    "id": "band",
    "common": {
      "start": {
        "seconds": 0
      },
      "duration": {
        "seconds": 4
      }
    },
    "paint": {
      "style": "fill",
      "source": {
        "type": "solid",
        "color": {
          "r": 0,
          "g": 0,
          "b": 0,
          "a": 0.5
        }
      }
    },
    "geometry": {
      "type": "rect",
      "rect": {
        "x": 0,
        "y": 900,
        "width": 1920,
        "height": 180
      }
    }
  },
  "meta": {
    "name": "Band"
  }
}
```

### node.remove

Removes a node.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | The node to remove. |
| `ripple` | boolean | | In a sequence, close the gap it leaves. |

Example item:

```json theme={"system"}
{
  "type": "node.remove",
  "node": "card"
}
```

### node.patch

Changes any of a node's stored fields. A key left out is untouched, `null` clears it, an object merges into the object it lands on (unless it names another `type`), and anything else replaces it. Keyframes and markers are written by id: `{"keyframes": {"k1": {"value": 2}, "k2": null}}` edits `k1` and removes `k2`.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | The node to change. |
| `patch` | object | Yes | The fields to change, nested as the node stores them (`inspect_motion_node` shows them). Common uses: motion blur `{"common": {"motionBlur": {"shutter": 180, "samples": 16}}}`, parenting `{"common": {"transformParent": "<node id>"}}`, hiding `{"common": {"hidden": true}}`, a bus's gain `{"gain": {"db": -6}}`. |

Example item:

```json theme={"system"}
{
  "type": "node.patch",
  "node": "card",
  "patch": {
    "common": {
      "motionBlur": {
        "shutter": 180,
        "samples": 16
      }
    }
  }
}
```

### node.move

Moves a node to another parent, time or paint position.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | The node to move. |
| `parent` | node id | | The new parent. |
| `start` | ticks or `{"seconds": n}` | | The new start, on the new parent's clock. |
| `index` | integer | | Paint position, counted from the back. |
| `ripple` | boolean | | Close the gap it leaves and make room where it lands; `start` is then where it lands once its gap has closed. |

Example item:

```json theme={"system"}
{
  "type": "node.move",
  "node": "card",
  "start": {
    "seconds": 1
  }
}
```

### node.trim

Moves a node's start or end.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | The node to trim. |
| `start` | ticks or `{"seconds": n}` | | The new start. |
| `end` | ticks or `{"seconds": n}` | | The new end. |
| `ripple` | boolean | | In a sequence the node keeps its start and what follows moves by as much as its length changed. |

Example item:

```json theme={"system"}
{
  "type": "node.trim",
  "node": "card",
  "end": {
    "seconds": 3
  }
}
```

### node.wrap

Wraps a node in an effect wrapper. Prefer `addEffect`.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | The node to wrap. |
| `wrapper` | object | Yes | The effect node that wraps it, as the render timeline stores it: `type: "effect"`, its `id`, the `effect`, and `common` with the window it covers. |

Example item:

```json theme={"system"}
{
  "type": "node.wrap",
  "node": "card",
  "wrapper": {
    "type": "effect",
    "id": "card.blur",
    "effect": {
      "type": "blur",
      "kind": "image",
      "radiusX": 8,
      "radiusY": 8,
      "tileMode": "decal"
    },
    "common": {
      "start": {
        "seconds": 0
      },
      "duration": {
        "seconds": 4
      }
    }
  }
}
```

### node.unwrap

Removes a wrapper node, keeping what it wraps.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | The wrapper to remove. |

A batch that uses it:

```json theme={"system"}
[
  {
    "type": "node.wrap",
    "node": "card",
    "wrapper": {
      "type": "effect",
      "id": "card.blur",
      "effect": {
        "type": "blur",
        "kind": "image",
        "radiusX": 8,
        "radiusY": 8,
        "tileMode": "decal"
      },
      "common": {
        "start": {
          "seconds": 0
        },
        "duration": {
          "seconds": 4
        }
      }
    }
  },
  {
    "type": "node.unwrap",
    "node": "card.blur"
  }
]
```

### node.rewrap

Moves a wrapper so it wraps another node.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | The wrapper. |
| `around` | node id | Yes | The node it wraps afterwards. |

A batch that uses it:

```json theme={"system"}
[
  {
    "type": "node.wrap",
    "node": "card",
    "wrapper": {
      "type": "effect",
      "id": "card.blur",
      "effect": {
        "type": "blur",
        "kind": "image",
        "radiusX": 8,
        "radiusY": 8,
        "tileMode": "decal"
      },
      "common": {
        "start": {
          "seconds": 0
        },
        "duration": {
          "seconds": 4
        }
      }
    }
  },
  {
    "type": "node.rewrap",
    "node": "card.blur",
    "around": "title"
  }
]
```

### node.split

Splits a node in two.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | The node to split. |
| `at` | ticks or `{"seconds": n}` | Yes | Where to split it. |
| `newId` | string | Yes | The second half's id. |
| `order` | string | | The second half's paint order key. Required for a node that has one and is not in a sequence; the `split` command fills it in. |

Example item:

```json theme={"system"}
{
  "type": "node.split",
  "node": "cam.2",
  "at": {
    "seconds": 3
  },
  "newId": "cam.2b"
}
```

### meta.set

Sets a node's editor state, or clears it with `null`.

| Field | Type | Required | Description |
| - | - | - | - |
| `node` | node id | Yes | The node. |
| `meta` | object or `null` | Yes | `name`, `color`, `muted`, `solo`, `locked`. |

Example item:

```json theme={"system"}
{
  "type": "meta.set",
  "node": "card",
  "meta": {
    "name": "Price card",
    "locked": true
  }
}
```

### asset.set

Adds or replaces an asset (fonts, images, lotties, luts, media, pictures, shaders, springs, svgs). Place photos, videos and sounds with `addMedia` and a `sourceId` instead.

| Field | Type | Required | Description |
| - | - | - | - |
| `kind` | `fonts` \| `images` \| `lotties` \| `luts` \| `media` \| `pictures` \| `shaders` \| `springs` \| `svgs` | Yes | Which assets. |
| `asset` | object | Yes | The asset, as the render timeline stores it. |

Example item:

```json theme={"system"}
{
  "type": "asset.set",
  "kind": "svgs",
  "asset": {
    "id": "svg.arrow",
    "source": {
      "type": "uri",
      "uri": "https://example.com/arrow.svg"
    }
  }
}
```

### asset.remove

Removes an asset.

| Field | Type | Required | Description |
| - | - | - | - |
| `kind` | string | Yes | Which assets, as for `asset.set`. |
| `assetId` | string | Yes | The asset's id. |

A batch that uses it:

```json theme={"system"}
[
  {
    "type": "asset.set",
    "kind": "svgs",
    "asset": {
      "id": "svg.arrow",
      "source": {
        "type": "uri",
        "uri": "https://example.com/arrow.svg"
      }
    }
  },
  {
    "type": "asset.remove",
    "kind": "svgs",
    "assetId": "svg.arrow"
  }
]
```

### composition.set

Adds or replaces a composition: a reusable tree of nodes that instances play. Editing it changes every instance.

| Field | Type | Required | Description |
| - | - | - | - |
| `composition` | object | Yes | The composition, as the render timeline stores it. |

Example item:

```json theme={"system"}
{
  "type": "composition.set",
  "composition": {
    "id": "comp.badge",
    "width": 400,
    "height": 120,
    "duration": {
      "seconds": 2
    },
    "nodes": []
  }
}
```

### composition.remove

Removes a composition.

| Field | Type | Required | Description |
| - | - | - | - |
| `compositionId` | string | Yes | The composition's id. |

A batch that uses it:

```json theme={"system"}
[
  {
    "type": "composition.set",
    "composition": {
      "id": "comp.badge",
      "width": 400,
      "height": 120,
      "duration": {
        "seconds": 2
      },
      "nodes": []
    }
  },
  {
    "type": "composition.remove",
    "compositionId": "comp.badge"
  }
]
```

### transition.set

Adds or replaces a transition in a sequence. Prefer `setTransition`.

| Field | Type | Required | Description |
| - | - | - | - |
| `sequence` | node id | Yes | The sequence. |
| `transition` | object | Yes | The transition as the render timeline stores it: its `id`, the child it plays `after`, `duration`, `effect` and optional `easing`. |

Example item:

```json theme={"system"}
{
  "type": "transition.set",
  "sequence": "camera",
  "transition": {
    "id": "t1",
    "after": "cam.1",
    "duration": {
      "seconds": 0.5
    },
    "effect": {
      "type": "crossfade"
    }
  }
}
```

### transition.remove

Removes a transition from a sequence.

| Field | Type | Required | Description |
| - | - | - | - |
| `sequence` | node id | Yes | The sequence. |
| `transitionId` | string | Yes | The transition's id. |

A batch that uses it:

```json theme={"system"}
[
  {
    "type": "transition.set",
    "sequence": "camera",
    "transition": {
      "id": "t1",
      "after": "cam.1",
      "duration": {
        "seconds": 0.5
      },
      "effect": {
        "type": "crossfade"
      }
    }
  },
  {
    "type": "transition.remove",
    "sequence": "camera",
    "transitionId": "t1"
  }
]
```

### curve.set

Sets or removes a named curve as stored. Prefer `setCurve`.

| Field | Type | Required | Description |
| - | - | - | - |
| `name` | string | Yes | The curve's name. |
| `curve` | object or `null` | Yes | The curve as the render timeline stores it. |

Example item:

```json theme={"system"}
{
  "type": "curve.set",
  "name": "progress",
  "curve": null
}
```


## Related topics

- [Tella Motion](https://www.tella.com/docs/mcp-tools/motion.md)
- [Tella Motion properties, easing and fonts](https://www.tella.com/docs/mcp-tools/motion-values.md)
- [Create motion graphics with Tella Motion](https://www.tella.com/docs/help/editing/tella-motion.md)
- [Duplicate a Tella Motion document](https://www.tella.com/docs/api-reference/tella-motion/duplicate-a-tella-motion-document.md)
- [Edit a Tella Motion document](https://www.tella.com/docs/api-reference/tella-motion/edit-a-tella-motion-document.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.