# cuttingfloor (remote · api.cuttingfloor.ai)

Hosted video editing for AI agents: cut, caption, mix and export real video. Sign in with OAuth.

- Trust score: 82/100 (high trust)
- Change this week: +3
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-11-06

## Components

- remote · `api.cuttingfloor.ai`: 82/100 (this document), [markdown](https://verifymcp.io/servers/mvp2o-ai-cuttingfloor/api.md), [page](https://verifymcp.io/servers/mvp2o-ai-cuttingfloor/api)

## Channel facts

- Endpoint: `https://api.cuttingfloor.ai/mcp`
- Transports: `streamable-http`
- Auth: `required`
- Version: `0.34.9`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically against the live server, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-11-06.

- **Endpoint Security**: 92/100
  - The endpoint's TLS certificate is valid, in date, and uses a strong key.
  - Authorisation is enforced on tool calls, advertised via RFC 9728 protected-resource metadata. Discovery is public, which costs nothing: no tool can be invoked without a token.
  - HTTPS is enforced; there's no plaintext access path.
  - HSTS check failed: the Strict-Transport-Security header is absent.
  - DNSSEC check failed: this domain isn't protected by DNSSEC.
  - The authorisation server supports Client ID Metadata Documents, the current MCP client-registration mechanism.
- **Transport & Reachability**: 100/100
  - Verified streamable-http transport via a live MCP handshake.
- **Schema Quality & AI Usability**: 70/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (good).
  - Context-footprint check failed: tool/resource definitions use about 21156 tokens (~246/item across 86 items; 72 tools + 14 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 37/100
  - Stability observed for 11 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 92/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 73% of tool parameters carry a description.
  - Structured output schemas are declared (100% of tools); any adoption earns full credit.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - All 8 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation.
  - An AI judge read all 74 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a current MCP spec version (2026-07-28).
  - Supports UI / widget rendering.

## Install

### How do I install the cuttingfloor MCP server?

cuttingfloor is a hosted endpoint at https://api.cuttingfloor.ai/mcp, so there is nothing to install locally. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add --transport http mvp2o-ai-cuttingfloor 'https://api.cuttingfloor.ai/mcp'
```

### Cursor

```json
{
  "mcpServers": {
    "mvp2o-ai-cuttingfloor": {
      "url": "https://api.cuttingfloor.ai/mcp"
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "mvp2o-ai-cuttingfloor": {
      "type": "http",
      "url": "https://api.cuttingfloor.ai/mcp"
    }
  }
}
```

### Codex

```toml
[mcp_servers.mvp2o-ai-cuttingfloor]
url = "https://api.cuttingfloor.ai/mcp"
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "mvp2o-ai-cuttingfloor": {
      "type": "remote",
      "url": "https://api.cuttingfloor.ai/mcp",
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add mvp2o-ai-cuttingfloor --url 'https://api.cuttingfloor.ai/mcp' --transport streamable-http
```

### Hermes

```yaml
mcp_servers:
  mvp2o-ai-cuttingfloor:
    url: "https://api.cuttingfloor.ai/mcp"
```

### Netclaw

```json
{
  "McpServers": {
    "mvp2o-ai-cuttingfloor": {
      "Transport": "http",
      "Url": "https://api.cuttingfloor.ai/mcp"
    }
  }
}
```

### Vellum

```bash
assistant mcp add mvp2o-ai-cuttingfloor -t streamable-http -u 'https://api.cuttingfloor.ai/mcp'
```

### Other

```json
{
  "mcpServers": {
    "mvp2o-ai-cuttingfloor": {
      "type": "http",
      "url": "https://api.cuttingfloor.ai/mcp"
    }
  }
}
```

The mcpServers block is a cross-client convention. Remote transports vary, so check your client's docs.

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-11-06 (score 82, +1)

- [security] Tool “create.lipsync” rewrote its description, which is the text the model reads
- [functional] Server version: 0.34.8 → 0.34.9
- [functional] Server version: 0.34.5 → 0.34.8
- [functional] Server version: 0.34.4 → 0.34.5
- [functional] Server version: 0.34.0 → 0.34.4

### 2026-11-05 (score 81, 0)

- [functional] We updated how we score, so this day's move reflects our rubric, not a change to the server
- [functional] Server version: 0.32.0 → 0.34.0

### 2026-11-04 (score 81, 0)

- [functional] New resource “library”
- [functional] Server version: 0.31.0 → 0.32.0
- [functional] Server version: 0.30.0 → 0.31.0

### 2026-11-03 (score 81, +1)

- [functional] New resource “editor”
- [functional] Server version: 0.29.3 → 0.30.0

### 2026-11-02 (score 80, 0)

- [security] Tool “job.get” rewrote its description, which is the text the model reads
- [security] Tool “clip.set_mask” rewrote its description, which is the text the model reads
- [functional] Server version: 0.29.2 → 0.29.3
- [functional] Server version: 0.29.1 → 0.29.2
- [functional] Server version: 0.28.0 → 0.29.1
- [cosmetic] “job.get” reworded the description of “wait_seconds”
- [cosmetic] “clip.set_speed” reworded the description of “interpolate”
- [cosmetic] “audio.set_gain” reworded the description of “gain_db”
- [cosmetic] “qa.check” reworded the description of “speech”

### 2026-11-01 (score 80, +1)

- [security] The server rewrote its instructions, which are the text every model session reads
- [security] Tool “audio.set_gain” rewrote its description, which is the text the model reads
- [security] Tool “audio.set_master” rewrote its description, which is the text the model reads
- [security] Tool “caption.add” rewrote its description, which is the text the model reads
- [security] Tool “clip.replace” rewrote its description, which is the text the model reads
- [security] Tool “clip.set_animation” rewrote its description, which is the text the model reads
- [security] Tool “clip.set_mask” rewrote its description, which is the text the model reads
- [security] Tool “clip.set_transform” rewrote its description, which is the text the model reads
- [security] Tool “create.cutout” rewrote its description, which is the text the model reads
- [security] Tool “export.timeline” rewrote its description, which is the text the model reads
- [security] Tool “export.video” rewrote its description, which is the text the model reads
- [security] Tool “filter.add” rewrote its description, which is the text the model reads
- [security] Tool “job.cancel” rewrote its description, which is the text the model reads
- [security] Tool “job.get” rewrote its description, which is the text the model reads
- [security] Tool “job.list” rewrote its description, which is the text the model reads
- [security] Tool “judge.ask” rewrote its description, which is the text the model reads
- [security] Tool “media.analyze” rewrote its description, which is the text the model reads
- [security] Tool “media.get” rewrote its description, which is the text the model reads
- [security] Tool “media.import” rewrote its description, which is the text the model reads
- [security] Tool “media.listen” rewrote its description, which is the text the model reads
- [security] Tool “media.seek” rewrote its description, which is the text the model reads
- [security] Tool “qa.check” rewrote its description, which is the text the model reads
- [security] Tool “range.extract” rewrote its description, which is the text the model reads
- [security] Tool “range.lift” rewrote its description, which is the text the model reads
- [security] Tool “text.set” rewrote its description, which is the text the model reads
- [security] Tool “timeline.set_sequence” rewrote its description, which is the text the model reads
- [security] Tool “title.add” rewrote its description, which is the text the model reads
- [security] Tool “create.motion” rewrote its description, which is the text the model reads
- [security] Tool “create.still” rewrote its description, which is the text the model reads
- [security] Tool “job.cancel” is now declared destructive
- [functional regression] Schema quality: 221 → 248
- [functional] Server version: 0.27.6 → 0.28.0
- [functional] Server version: 0.27.5 → 0.27.6
- [functional] Server version: 0.27.4 → 0.27.5
- [functional] New tool “create.upscaled”
- [cosmetic] “clip.replace” added an optional parameter “linked”
- [cosmetic] “clip.set_speed” added an optional parameter “interpolate”
- [cosmetic] “create.cutout” added an optional parameter “region”
- [cosmetic] “job.get” added an optional parameter “result”
- [cosmetic] “job.get” added an optional parameter “wait_seconds”
- [cosmetic] “job.list” added an optional parameter “media_id”
- [cosmetic] “job.list” added an optional parameter “result”
- [cosmetic] “job.list” added an optional parameter “tool”
- [cosmetic] “media.analyze” added an optional parameter “cluster_threshold”
- [cosmetic] “media.analyze” added an optional parameter “percentiles”
- [cosmetic] “media.analyze” added an optional parameter “regions”
- [cosmetic] “media.analyze” added an optional parameter “speakers”
- [cosmetic] “text.set” added an optional parameter “spans”
- [cosmetic] “title.add” added an optional parameter “spans”
- [cosmetic] “clip.set_animation” reworded the description of “loop”
- [cosmetic] “filter.add” reworded the description of “name”
- [cosmetic] “filter.add” reworded the description of “params”
- [cosmetic] “judge.ask” reworded the description of “state”
- [cosmetic] “media.analyze” reworded the description of “at_seconds”
- [cosmetic] “media.analyze” reworded the description of “every_seconds”
- [cosmetic] “media.analyze” reworded the description of “from_seconds”
- [cosmetic] “media.analyze” reworded the description of “kind”
- [cosmetic] “media.analyze” reworded the description of “model”
- [cosmetic] “media.analyze” reworded the description of “region”
- [cosmetic] “media.analyze” reworded the description of “threshold”
- [cosmetic] “media.analyze” reworded the description of “timeline_id”
- [cosmetic] “media.analyze” reworded the description of “until_seconds”
- [cosmetic] “media.get” reworded the description of “from_seconds”
- [cosmetic] “media.get” reworded the description of “include”

### 2026-10-30 (score 79, +1)

- [functional] Server version: 0.27.3 → 0.27.4
- [functional] Server version: 0.27.1 → 0.27.3

### 2026-10-28 (score 78, +1)

No change was recorded against any check on this day. Stability & Change Management went from 3 to 7. That category is still filling its 30-day observation window: 1 days of observed history at the previous scan, 2 at this one. The score rises as the window fills, whether or not the server changes.

## MCP tools (72)

### `timeline.create` (~102 tokens)

Create timeline

Start an empty timeline. Returns its timeline_id and its tracks (ids for track_id). Timelines are immutable: every edit returns a new timeline_id, and older ids stay valid for 24 hours after last use. For work longer than a day, keep the timeline.get document on your side and restore it with timeline.load.

Input parameters:

- `name` (string)
- `sequence` (object, required)
- `tracks` (object): How many empty tracks to start with

Output parameters:

- `timeline_id` (string): Timeline handle; every edit returns a new one
- `tracks` (array): The new tracks; edits take the id (track_id), not the name

### `timeline.get` (~84 tokens)

Get timeline

The full timeline: tracks, clips (frames, end exclusive), link groups and the media it uses. The server keeps a timeline 24 hours after last use: for work longer than a day, save this document on your side (it is the edit) and restore it with timeline.load.

Input parameters:

- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `duration` (integer): Timeline length in frames
- `effects` (array): Effects on the whole picture and mix
- `master` (object)
- `media` (object)
- `name` (string)
- `nested` (object): Timelines nested as compound clips, at any depth: each has this shape without timeline_id, parent or nested
- `parent` (string|null)
- `sequence` (object)
- `timeline_id` (string)
- `tracks` (array): Video tracks bottom to top, then audio tracks

### `timeline.load` (~77 tokens)

Load timeline

Restore a timeline from the document timeline.get returned, e.g. after it expired (24 h after last use). Its media must still be here: media expires 7 days after last use, so import saved files again with media.import and swap them in with clip.replace.

Input parameters:

- `timeline` (object, required): A timeline as returned by timeline.get

Output parameters:

- `timeline_id` (string): Timeline handle; every edit returns a new one

### `timeline.history` (~55 tokens)

Timeline history

The chain of edits that made this timeline, newest first. To go back, keep using an older timeline_id.

Input parameters:

- `limit` (integer)
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `entries` (array)
- `truncated` (boolean)

### `timeline.diff` (~51 tokens)

Diff timelines

Which clips, tracks and settings differ between two timelines.

Input parameters:

- `from` (string, required): Timeline handle; every edit returns a new one
- `to` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)

### `timeline.set_sequence` (~139 tokens)

Set sequence

Change resolution, frame rate or audio format. Clip times are kept in time, so they survive a rate change. Each picture is refitted to the new shape by its fit (contain by default, which leaves bars where the shapes differ); the result warns how many clips no longer fill the frame, and clip.set_transform {fit: "cover"} fills it. Returns the edit result (see the server instructions).

Input parameters:

- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `sequence` (object, required)
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `track.add` (~87 tokens)

Add track

Add an empty video or audio track. New video tracks go on top. Returns the edit result (see the server instructions).

Input parameters:

- `kind` (string, required)
- `name` (string)
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `track.remove` (~73 tokens)

Remove track

Remove a track and its clips. Returns the edit result (see the server instructions).

Input parameters:

- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `track_id` (string, required)

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `track.set` (~109 tokens)

Set track

Rename, mute (audio), hide (video) or lock a track. Returns the edit result (see the server instructions).

Input parameters:

- `hidden` (boolean)
- `locked` (boolean)
- `muted` (boolean)
- `name` (string)
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `track_id` (string, required)

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.add` (~453 tokens)

Add clip

Place media on the timeline. A/V media lands as a linked pair on a video and an audio track. Default: the whole media, after the last clip, overwriting. A picture is scaled to fit the frame (a small logo fills it edge to edge); clip.set_transform {fit: "none"} keeps its own pixel size. Returns the edit result (see the server instructions).

Input parameters:

- `after_clip_id` (string): Start where this clip ends (instead of at_frame)
- `at_frame` (integer): Where the clip starts. Default: after the last clip
- `av` (string): linked: video and audio as a link group; video or audio: only that part
- `duration_frames` (integer): Length for stills (default 5 s); ignored when source is given
- `link_track_id` (string): Track for the other half of a linked clip. Default: the first track of that kind
- `media_id` (string): Media handle from media.import or a create.* tool
- `mode` (string): overwrite replaces what is underneath; insert pushes later clips right
- `nested_timeline_id` (string): Instead of media: nest another timeline as one clip (a compound clip)
- `other_tracks` (string): What the other tracks do when this edit moves later clips on the clip's own tracks (linked clips always move with it). stay: they don't move, and the result warns which clips there now play early or…
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `source`: Source range in sequence frames, out exclusive. Default: the whole media
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `track_id` (string, required): Track for the clip; a linked A/V clip puts its other half on link_track_id

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.remove` (~205 tokens)

Remove clip

Remove a clip and the clips linked to it; ripple closes the gap. Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required)
- `other_tracks` (string): What the other tracks do when this edit moves later clips on the clip's own tracks (linked clips always move with it). stay: they don't move, and the result warns which clips there now play early or…
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `ripple` (boolean): Close the gap by pulling later clips left
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.move` (~239 tokens)

Move clip

Move a clip (and its linked clips) to a frame and optionally another track. Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required)
- `mode` (string): overwrite replaces what is underneath; insert pushes later clips right
- `other_tracks` (string): What the other tracks do when this edit moves later clips on the clip's own tracks (linked clips always move with it). stay: they don't move, and the result warns which clips there now play early or…
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `ripple` (boolean): Close the gap left behind
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `to_frame` (integer, required)
- `to_track_id` (string)

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.trim` (~259 tokens)

Trim clip

Move the start or end edge of a clip. side: video or audio trims one half of a link group (J/L cuts). Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required)
- `edge` (string, required)
- `other_tracks` (string): What the other tracks do when this edit moves later clips on the clip's own tracks (linked clips always move with it). stay: they don't move, and the result warns which clips there now play early or…
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `ripple` (boolean): Shift later clips so nothing overlaps and no gap opens
- `side` (string): Trim only one half of a link group (J/L cuts)
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `to_frame` (integer, required): New timeline position of that edge

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.split` (~89 tokens)

Split clip

Cut a clip (and its linked clips) in two at a frame. Returns the edit result (see the server instructions).

Input parameters:

- `at_frame` (integer, required)
- `clip_id` (string, required)
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.link` (~75 tokens)

Link clips

Make clips move, trim and split together. Returns the edit result (see the server instructions).

Input parameters:

- `clip_ids` (array, required)
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.unlink` (~75 tokens)

Unlink clip

Break the link group a clip belongs to. Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required)
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.set_enabled` (~87 tokens)

Enable/disable clip

A disabled clip stays on the timeline but renders as a gap. Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required)
- `enabled` (boolean, required)
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.set_speed` (~445 tokens)

Set speed

Speed up, slow down, ramp or reverse a clip and its linked clips. rate is constant; curve is a speed ramp over the clip. The clip keeps its source and its length changes; ripple (default) moves later clips. Pitch is kept unless preserve_pitch is false. Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required)
- `curve` (array): Speed ramp: rates at fractions of the clip as it plays after the change (first at 0, last at 1; 0.5 is halfway through the new length), linear between. timeline.get gives each point its clip frame an…
- `interpolate` (string): Video, when it shows fewer new frames a second than the sequence (slowed, or a lower frame rate; rate 1 smooths that): none repeats frames; blend crossfades neighbours; motion makes new frames from t…
- `other_tracks` (string): What the other tracks do when this edit moves later clips on the clip's own tracks (linked clips always move with it). stay: they don't move, and the result warns which clips there now play early or…
- `preserve_pitch` (boolean): Keep voices at their pitch (default true)
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `rate` (number): Constant speed: 2 = twice as fast
- `reverse` (boolean)
- `ripple` (boolean): Move later clips as the clip gets longer or shorter
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.freeze` (~244 tokens)

Freeze frame

Hold the frame at at_frame for duration_frames: the clip splits there, a freeze frame goes in, and later clips on its tracks move later. Freezing a clip's last frame holds it after the clip, with silence under the hold. Returns the edit result (see the server instructions).

Input parameters:

- `at_frame` (integer, required): Timeline frame to hold
- `clip_id` (string, required)
- `duration_frames` (integer, required)
- `other_tracks` (string): What the other tracks do when this edit moves later clips on the clip's own tracks (linked clips always move with it). stay: they don't move, and the result warns which clips there now play early or…
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.roll` (~109 tokens)

Roll edit

Move the cut between a clip and the clip right after it: one gets longer, the other shorter, nothing else moves. Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required): The clip before the cut
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `to_frame` (integer, required): New position of the cut

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.slip` (~93 tokens)

Slip edit

Show earlier or later source in a clip without moving or resizing it. Returns the edit result (see the server instructions).

Input parameters:

- `by_frames` (integer, required): Positive shows later source
- `clip_id` (string, required)
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.slide` (~97 tokens)

Slide edit

Move a clip between its neighbours: the clip before it and the clip after it change length so nothing else moves. Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required)
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `to_frame` (integer, required)

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.replace` (~191 tokens)

Replace media

Swap the media in a clip, keeping its place, length, speed and look. Starts at source_in (default 0). Its linked clips swap too (linked: false for this clip only); clip_ids lists every clip swapped. Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required)
- `linked` (boolean): Also swap its linked clips (its sound, or its picture) onto the new media, keeping their offset, where the new media has that kind; false swaps this clip only
- `media_id` (string, required): Media handle from media.import or a create.* tool
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `source_in` (integer): Source frame the clip starts at. Default 0
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `range.lift` (~155 tokens)

Lift range

Remove everything in a frame range on the given tracks (default all unlocked, so a picture range takes the sound under it too: lock a track with track.set or name track_ids to keep it), leaving a gap. changed lists each clip removed or cut, with its track; a warning says when sound went with the picture. Returns the edit result (see the server instructions).

Input parameters:

- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `range` (object, required): Timeline frames, end exclusive
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `track_ids` (array): Tracks to act on. Default: every unlocked track

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `range.extract` (~171 tokens)

Extract range

Remove everything in a frame range on the given tracks (default all unlocked, so a picture range takes the sound under it too: lock a track with track.set or name track_ids to keep it) and close the gap. For cutting out silences, filler words or mistakes across all tracks at once. changed lists each clip removed or cut, with its track; a warning says when sound went with the picture. Returns the edit result (see the server instructions).

Input parameters:

- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `range` (object, required): Timeline frames, end exclusive
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `track_ids` (array): Tracks to act on. Default: every unlocked track

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `title.add` (~289 tokens)

Add title

Text on a video track, over the tracks below: styled with font, size, colour, outline, shadow and box, placed by x/y, revealed with a typewriter or word animation. A style field you leave out takes the published default (video://catalog/text: bold white Inter at 9% of the frame height, centred, with a soft shadow); timeline.get shows the full style. spans style part of the text (words first_word to last_word, from 0: one word in yellow or bold). It is a clip, so transforms, masks, animations and transitions apply. Returns the edit result (see the server instructions).

Input parameters:

- `at_frame` (integer): Default: after the last clip on the track
- `duration_frames` (integer): Default 3 s
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `spans` (array): Style part of the text: each span restyles words first_word to last_word (from 0: the timed words, or the text split at spaces), e.g. one word in yellow
- `style`
- `text` (string, required): Line breaks with \n
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `track_id` (string, required): A video track; text composites over the tracks below

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `caption.add` (~384 tokens)

Add captions

Captions on a video track from segments (with optional word timings for highlights), from SRT/VTT text, or in_clip: segments timed in seconds of a clip's media, such as transcript word times, placed where they play. One clip per caption; returns their clip_ids. A style field you leave out takes the published default (video://catalog/text: bold white Inter at 6% of the frame height, near the bottom, with a black outline); timeline.get shows the full style. Lines wrap on their own at style max_width (a fraction of the frame width), balanced in length; \n forces a break. style max_lines caps the lines on screen at once: a longer caption shows a screenful at a time, each from its first word's time. A segment's spans style part of its text (words first_word to last_word, from 0: the timed words, or the text split at spaces), e.g. the key word in yellow. Burned in when exported. Returns the edit result (see the server instructions).

Input parameters:

- `in_clip` (object): Segments timed in seconds of a clip's media (e.g. transcript word times), placed where those moments play in the clip, through speed changes; what doesn't play is left out
- `offset_frames` (integer): Shifts srt/vtt times
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `segments` (array)
- `srt` (string): SubRip text instead of segments
- `style`
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `track_id` (string, required): A video track for the captions, usually the top one
- `vtt` (string): WebVTT text instead of segments

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `text.set` (~224 tokens)

Set text

Change a title's or caption's text, or restyle many at once (clip_ids, or every one on track_id). The style is merged; null turns an outline, shadow, box or highlight off (and max_lines back to all lines). spans (one clip) restyle part of its text. Fonts: video://catalog/fonts, or an unknown font is refused with the close matches. Returns the edit result (see the server instructions).

Input parameters:

- `clip_ids` (array): Titles or captions
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `spans`: Only with a single clip: replaces its spans (null removes them). New text drops spans it doesn't give
- `style`: Merged into each clip's style; null fields reset
- `text` (string): Only with a single clip
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `track_id` (string): Instead of clip_ids: every title and caption on a track

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `audio.set_gain` (~228 tokens)

Set gain

Volume of an audio clip (or a video clip's linked audio) in dB; keyframes ride it up and down. The preview is a waveform of the clip; with preview inline it also carries the measured loudness (preview.loudness), a link preview does not. Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required): An audio clip, or a video clip whose linked audio to change
- `gain_db` (required): dB, -60 to 24; 0 leaves it as it is. Keyframes make a ride: the first key holds backward to the clip start, so a ride that begins part-way down must also key its starting level at frame 0 or the fram…
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `audio.set_pan` (~100 tokens)

Set pan

Place a clip's sound left (-1) to right (1). Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required)
- `pan` (required): -1 left, 0 centre, 1 right; null resets
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `audio.fade` (~98 tokens)

Fade audio

Fade a clip's sound in from its start and out to its end. null removes a fade. Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required)
- `in_frames`
- `out_frames`
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `audio.set_master` (~153 tokens)

Set master

Gain (dB) and limiter ceiling on the whole mix. The ceiling caps sample peaks (dBFS); the true peak between samples can land above it, most on dense, bright sound, and qa.check measures it (dBTP). Audio edits with preview inline carry the measured loudness (preview.loudness); a link preview does not. Returns the edit result (see the server instructions).

Input parameters:

- `gain_db`
- `limit_db`: Limiter ceiling on sample peaks, dBFS
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `filter.add` (~296 tokens)

Add effect

Apply an effect to a clip, every clip on a track, or the whole timeline: picture effects (colour, LUT, blur, vignette, chroma key, glitch, denoise and more) on video, sound effects (EQ, compressor, noise_reduction, voice_denoise, pitch and more) on audio. Order matters: effects run in the order added (a track's before a clip's own). Picture numbers take keyframes, except glitch, denoise and chroma_key tolerance; sound params are constants. A wrong name or param is refused with the full list, ranges, units and defaults (also at video://catalog/effects); timeline.get shows every param applied. Returns its id as clip_id. Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string): Apply to one clip (or an adjustment clip)
- `name` (string, required)
- `params` (object): The effect's own params; any left out take their defaults. A wrong one is refused with the effect's params, ranges, units and defaults
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline` (boolean): Or true for the whole picture or mix
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `track_id` (string): Or to every clip on a track

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `filter.set` (~100 tokens)

Set effect

Change an effect's params (merged) or turn it off and on. Returns the edit result (see the server instructions).

Input parameters:

- `enabled` (boolean)
- `filter_id` (string, required)
- `params` (object): Merged into its params
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `filter.remove` (~71 tokens)

Remove effect

Take an effect off. Returns the edit result (see the server instructions).

Input parameters:

- `filter_id` (string, required)
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `adjustment.add` (~139 tokens)

Add adjustment clip

An empty clip on a video track whose effects (filter.add with its clip_id) change everything below it while it runs: a colour grade or blur over a stretch of the edit. Returns the edit result (see the server instructions).

Input parameters:

- `at_frame` (integer): Default: after the last clip on the track
- `duration_frames` (integer, required)
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `track_id` (string, required): A video track; the effects reach every track below it

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `transition.add` (~296 tokens)

Add transition

Transition at the cut after a clip, into the clip that touches it (video://catalog/transitions). Linked audio gets a crossfade at the same cut. kind crossfade is sound only: on a video clip it crossfades the linked audio and the picture cuts hard (use mode handles for that). mode overlap (default) overlaps the clips and moves later clips earlier; handles uses source past the cut and moves nothing. changed lists each transition added, with its track and kind. Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required): The clip before the cut; the clip after it must touch it
- `duration_frames` (integer, required)
- `kind` (string, required): video://catalog/transitions; crossfade on audio tracks
- `mode` (string): overlap: the clips overlap by the duration and later clips move earlier; handles: use source past the cut, nothing moves
- `other_tracks` (string): overlap only: what the other tracks do. stay: they don't move, so what sits after the cut there plays later against the picture (the result warns which clips); extract: the same span comes out of the…
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `transition.set` (~89 tokens)

Set transition

Change a transition's kind or duration. Returns the edit result (see the server instructions).

Input parameters:

- `duration_frames` (integer)
- `kind` (string)
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `transition_id` (string, required)

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `transition.remove` (~77 tokens)

Remove transition

Remove a transition; the cut becomes a hard cut. Returns the edit result (see the server instructions).

Input parameters:

- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `transition_id` (string, required)

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.set_transform` (~551 tokens)

Set transform

Position, scale, rotate, fade, flip, crop or fit a video clip over the tracks below (picture-in-picture, overlays, punch-ins). fit none shows a graphic at its own pixel size, so scale 1 is its real size; the default contain scales it to touch the frame edges. Any number takes a constant or {keyframes: [{frame, value, ease}]} with frames from the clip start; a keyframe's ease shapes the move from it to the next one (sine_in_out, cubic_out, back_out, hold and more). Overlays that must follow a zoom or pan: put the clip and its overlays in a timeline of the same shape, nest it with clip.add {nested_timeline_id} and transform the nested clip, so everything moves together. Omitted fields keep their value; null resets one. To pin an overlay to a moving subject, use media.analyze track boxes as keyframes: for each box, frame = timeline frame of the box time (where the tracked clip plays that media second) minus this clip's start, x = box x + w/2 and y = box y + h/2 (add your offset). That holds when the tracked clip fills the frame as is; if it is scaled, moved or cropped, map the box through its transform first. Skip boxes with found false. The result's placements say where the clip now shows, in sequence pixels: its picture box, the part visible after crop and mask inside the frame, and the source pixels that show there. Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required)
- `crop`
- `fit`
- `flip`
- `opacity`: Opacity (default 1); 0 to 1
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `rotation`: Degrees clockwise (default 0); -36000 to 36000
- `scale`: Size relative to the fit (default 1); 0 to 20
- `scale_x`: Extra horizontal stretch (default 1); 0 to 20
- `scale_y`: Extra vertical stretch (default 1); 0 to 20
- `timeline_id` (string, required): Timeline handle; every edit returns a new one
- `x`: Centre x, 0 = left edge, 1 = right edge (default 0.5); -4 to 5
- `y`: Centre y, 0 = top, 1 = bottom (default 0.5); -4 to 5

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `placements` (array): Where each clip the edit moved, cropped, masked or animated shows, sequence pixels
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.set_blend` (~102 tokens)

Set blend mode

How a video clip mixes with the tracks below: normal, multiply, screen, overlay and more (video://catalog/blend_modes). Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required)
- `mode` (string, required)
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.set_mask` (~326 tokens)

Set mask

Select part of a video clip, then show, hide or fill it: rectangle (rounded), ellipse, linear, mirror, heart, star, polygon or matte (a media's alpha, e.g. a create.cutout), with feather and invert. Several shapes: add. A fill paints the selection from the picture around it and from earlier frames where that area showed, so something that never moves against its background leaves a smudge: nothing ever shows what is behind it. It is a job of its own (job.list shows its progress); a render that needs it names the job until it is done, and an export waits for it. Position, size and rotation animate like transform values. Coordinates are fractions of the clip's fitted frame: a sequence-shaped box with the picture fitted inside (bars where the shapes differ), before the clip's scale and position. A narrower picture fills k = (its aspect / the sequence's) of the box width, centred, so a picture-fraction box maps to x = 0.5 + (x - 0.5) * k, width * k (a wider picture: the same on y and height, with k = the sequence aspect / its). null removes the mask. Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required)
- `mask` (required): null removes the mask
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `placements` (array): Where each clip the edit moved, cropped, masked or animated shows, sequence pixels
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `clip.set_animation` (~198 tokens)

Set animation

Entrance, exit and loop presets layered over the clip transform (video://catalog/animations): e.g. in {name: "pop", duration_frames: 12}, loop {name: "float"}; a loop runs the whole clip, or only from_frame to to_frame of it (loop {name: "shake", from_frame: 30, to_frame: 45} for a jolt). null removes one. Returns the edit result (see the server instructions).

Input parameters:

- `clip_id` (string, required)
- `in`: Entrance, from the clip start
- `loop`: Repeats over the clip, or over from_frame to to_frame of it
- `out`: Exit, ending at the clip end
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `placements` (array): Where each clip the edit moved, cropped, masked or animated shows, sequence pixels
- `preview`
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `edit.batch` (~161 tokens)

Batch edit

Apply many edits as one change: one new timeline_id, one preview. All apply or none do. Use {"$ref":"ops[N].clip_id"} (or track_id, clip_ids[M]) to use an id an earlier op made. Returns the edit result plus results: [{tool, clip_id?, clip_ids?, track_id?}] per op.

Input parameters:

- `ops` (array, required): Edits applied in order as one change. An arg may be {"$ref":"ops[2].clip_id"} to use an id made by an earlier op
- `preview` (string): inline: image in the result; link: URI that renders on read; none
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

Output parameters:

- `changed` (array)
- `clip_id` (string)
- `clip_ids` (array)
- `parent` (string): Timeline handle; every edit returns a new one
- `placements` (array): Where each clip the edit moved, cropped, masked or animated shows, sequence pixels
- `preview`
- `results` (array)
- `timeline_id` (string): Timeline handle; every edit returns a new one
- `track_id` (string)
- `warnings` (array)

### `media.import` (~428 tokens)

Import media

Import the user's own video, audio, image or font to edit with or place as is: probe, fingerprint, conform if needed (VFR, rotation, HDR → BT.709), 540p proxy and integrated loudness. A reference image to recreate needs no import: rebuild it with create.motion or create.still. Long-running: returns a task, or {job_id, media_id} for job.get. The media_id is usable in edits as soon as the job reports processing, so with many files, start every import first and edit while they finish. Sources: an http(s) URL (Dropbox and Google Drive share links work as they are), {upload: {name, bytes}} for a file you send yourself (the answer is the new media with upload.url: PUT the file there, curl --fail -T file '<url>', and it imports once the last byte arrives, with no base64 in a message; a broken PUT resumes as media.upload says), the upload_id of a file sent through media.upload, a small file as a data: URI (up to 8 MB, with its sha256 so a garbled copy is refused), the job_id of a completed export.video (its render becomes media), or still: one frame of a video (media_id) or of an edit (timeline_id) as a PNG still at full size, to place as a picture, cut out or reuse. A URL is fetched once, when the import starts: it only has to work until then (a short-lived signed link from the user's own storage is enough), and the copy stays here 7 days after last use. A file the user attached in the chat comes as file, when the client passes it. Importing the same file again returns the same media_id.

Input parameters:

- `file` (object): A file the user attached in the chat, as the client passes it (instead of source)
- `idempotency_key` (string)
- `name` (string)
- `source`: Where the file is. Give this or file

### `media.upload` (~210 tokens)

Upload media

A signed URL to send one of the user's files to this server, for files on their machine that the server can't read: PUT the file there (curl --fail -T interview.mp4 '<url>'), then media.import {source: {upload_id}}. The URL needs no other credentials and works for a day. A broken upload resumes: HEAD the url for Upload-Offset (the bytes received), then PUT the rest with Content-Range: bytes <offset>-<last>/<total>. Large files can go in parts the same way. Without a way to send it yourself, give the user the url: opened in a browser it is a page to drop the file on, which sends it and resumes on its own. media.import says whether the file is all in yet.

Input parameters:

- `bytes` (integer): The file size, to be refused now rather than part way if it is too big
- `name` (string, required): The file name with its extension, e.g. interview.mp4

Output parameters:

- `expires_at` (string)
- `how` (string)
- `max_bytes` (number)
- `upload_id` (string): Upload handle from media.upload
- `url` (string): Signed: PUT the file here; no other credentials needed

### `media.list` (~120 tokens)

List media

What the user has here: imported and created media, and files they sent that aren't imported yet (through media.upload, or dropped on their account page at cuttingfloor.ai/account), with names, sizes and when each expires, plus the account's storage used and its cap. When the user mentions a file they sent or dropped ("the interview I uploaded"), find it here by name and media.import {source: {upload_id}} it; complete says it is all in. Listing does not count as a use, so nothing here lasts longer for being listed.

Output parameters:

- `media` (array): Imported and created media, most recently used first (up to 200)
- `media_count` (integer): All media held, including any not listed
- `storage` (object)
- `upload_count` (integer)
- `uploads` (array): Files sent to the server and not imported yet, newest first (up to 200): through media.upload, or dropped on the account page by the user

### `media.delete` (~128 tokens)

Delete media

Delete a media for good, so an old or wrong version stops turning up: its file here, proxy, mezzanine and media.analyze results go, and its room comes back to the storage cap. A path import leaves the original in its folder; exports made from it stay. Timelines that use it keep their ids and edits but no longer render (media_offline names it, as for expired media): replace those clips with clip.replace. Importing the same file again makes a new media_id.

Input parameters:

- `media_id` (string, required): Media handle from media.import or a create.* tool

Output parameters:

- `deleted` (boolean)
- `freed_bytes` (number): What it held here: the file, its proxy and mezzanine, and its analyses
- `media_id` (string): Media handle from media.import or a create.* tool
- `storage` (object)

### `media.analyze` (~2506 tokens)

Analyze media

Find what is in imported media, or in the sound of an edit, as data for edits. transcript: every word with its time (whisper.cpp on the CPU; the model downloads once). For English the word edges are then aligned to the sound (wav2vec2 forced alignment): a word starts and ends within about 0.05 s of where its letters sound, and an end on a soft consonant (s, t, k, f) or a breath can ring on up to about 0.1 s more; timing_confidence says how clearly each word was heard. Other languages keep whisper's own times, up to about a second off. Short ums and uhs are often left out of the words; kind speech still hears them. from_seconds and until_seconds transcribe just that stretch. beats: tempo, beat and downbeat times, and strengths: each beat's onset strength, 0–1 of the strongest (the hardest hits, the drop). faces: face boxes over time, e.g. to reframe to 9:16 with clip.set_transform keyframes. track: follow a region you give from a moment on, a box for each sampled time (found false where the subject was lost); to make a title, sticker, arrow or mask follow the subject, turn the boxes into clip.set_transform (or clip.set_mask) keyframes, no tracking of your own needed. look: your own typed questions about what is on screen, answered on every sampled frame (to find where something happens from plain words, media.seek). silence: stretches quieter than threshold_db for at least min_seconds. scenes: times the picture changes by more than threshold (0–100; hard cuts between ordinary shots score about 10 to 30, see threshold), with each cut's score. speech: the probability that someone is speaking, every 32 ms (Silero VAD), and the spans where it is at least your min_probability; it hears speech, not noise or music, so a cough or a door is not speech. loudness: integrated loudness (LUFS), true peak (dBTP), loudness range (LU), RMS level and noise floor (dBFS) over the whole media or from_seconds to until_seconds. To line up two recordings, media.correlate. sounds: what can…

Input parameters:

- `at_seconds` (array): look, colour: sample exactly these times instead
- `cluster_threshold` (number): speakers: when the count is unknown, how different two voices must be to count as two people; lower finds more speakers (try 0.5 to 0.9)
- `every_seconds` (number): look, colour: sample a frame this often. Default 1
- `from_seconds` (number): Start sampling or measuring here (any kind but beats, and faces and scenes on media). Default 0
- `idempotency_key` (string)
- `kind` (string, required): transcript: words with times (whisper.cpp, then aligned to the sound); beats: tempo, beat times and the onset strength at each; faces: face boxes over time; track: follow a region; look: your own que…
- `labels` (array): sounds: the sounds to report, named as in video://catalog/sounds (e.g. ["Laughter", "Applause", "Cough"]). Default: every sound that reaches min_probability somewhere
- `language` (string): transcript: e.g. 'en', or 'auto'
- `media_id` (string): Analyze imported media (media seconds)
- `min_probability` (number): speech (required): a 32 ms frame with at least this speech probability is speech; sounds (required): a 40 ms frame has the sound when its probability is at least this
- `min_seconds` (number): silence (required): only stretches at least this long
- `model` (string): transcript: whisper size, on the server CPU. Bigger is more accurate and slower: small takes about 3 times as long as base, tiny about half
- `percentiles` (array): colour: the percentiles to give of each channel
- `questions` (object): look: id (yours, not shown to the model) to question; up to 32, all asked of every frame
- `region` (object): track: the box to follow (top-left x, y and size) and when
- `regions` (object): colour: named boxes to measure besides the whole frame (frame), up to 16
- `speakers` (integer): speakers: how many people speak, when you know (or give cluster_threshold)
- `state` (object): look: text or JSON entries shown beside `frame` on every frame, by name, for questions to refer to (a script line, a definition)
- `threshold` (number): scenes (required): how much the picture must change to count as a cut, 0–100 (FFmpeg scdet's score): the change from the frame before, in percent of full range, less the change just before it (so ste…
- `threshold_db` (number): silence (required): quieter than this counts as silence, dBFS
- `timeline_id` (string): Analyze an edit as it plays (timeline seconds): its mix (speech, loudness, silence, sounds, speakers, voice) or its picture (faces, track, look, scenes, colour, picture)
- `until_seconds` (number): Stop sampling or measuring here. Default: the end
- `with_previous` (boolean): look: also show the frame sampled before, as `previous`

### `media.correlate` (~365 tokens)

Sync offset

Find how far one recording sits from another: the sync offset of b against a, by cross-correlating their sound, with how strongly they match. Use it to line up a camera with a separate microphone, two cameras of the same moment, or a re-recorded line against the original. Each side is imported media or an edit's mix (timeline_id), over its own from_seconds to until_seconds, so you can match a short stretch of b against a longer stretch of a.
offset_seconds: b's moment t lines up with a's moment t + offset_seconds, each in its own seconds. To line them up on a timeline, place b's clip so it starts offset_seconds later than a's (earlier when negative), source starts being equal. strength is the correlation of the overlapping sound at that offset, -1 to 1: near 1 the same sound, near 0 unrelated; next_best is the best offset outside the winning peak, and a strength close to it means the match is unclear (repetitive sound, or not the same moment). Offsets are searched up to max_offset_seconds either way, where at least half of the shorter sound overlaps the other. Up to 600 s read from each side.
A side with no sound in its range (no audio stream, a range past its end, or digital silence) fails with no_audio, naming which side. Short reads answer at once; longer ones return a task or job.

Input parameters:

- `a` (object, required): The sound to line up against: the reference
- `b` (object, required): The sound whose offset you want
- `idempotency_key` (string)
- `max_offset_seconds` (number, required): Search offsets up to this far either way

### `media.get` (~447 tokens)

Get media

Status, where it came from (origin: the import source, the export of which timeline_id and preset, or the create.* tool and the media it was made from), a signed download url for the file (media is kept 7 days after last use: save the file from it to keep it, and media.import it again later) and a page_url that plays or shows it in any browser (to give a person), its format (extension and mime_type, read from the file, not its name), everything the probe read (probe: the format and every stream, e.g. pix_fmt, colour space, primaries, transfer), duration, frame rate, conform reasons, integrated loudness and summaries of media.analyze results under analyses (counts only: analyses.transcript.words is how many words). While it is importing or being made, job names the job doing it and its progress. include returns those results at the top level, e.g. include ["transcript"] gives transcript.words, the list of words with times. For long media, read them in pieces: from_seconds and to_seconds keep what overlaps that stretch of the media, offset and limit page through what is left, and pages says, per analysis, the index of the first item returned in the whole list (for transcript words, the index clip.add source {words} takes), how many matched and next_offset for the next page (null at the end).

Input parameters:

- `from_seconds` (number): Only what ends after this media time: transcript words, beats, speaker turns, frames
- `include` (array): Also return these analyses (otherwise only their summaries): in full, or the piece from_seconds, to_seconds, offset and limit choose. One never run on this media is absent, and warnings says so
- `limit` (integer): At most this many items of each included list; pages says where the next starts
- `media_id` (string, required): Media handle from media.import or a create.* tool
- `offset` (integer): Skip this many items of each included list (after the time range)
- `to_seconds` (number): Only what starts before this media time

Output parameters:

- `analyses` (object): Summaries of media.analyze results (counts, not data); include puts each in full at the top level
- `analysis`
- `beats` (object)
- `codecs` (object)
- `colour` (object)
- `conform` (array): Why a mezzanine was made (vfr, rotation, hdr, codec); empty when not needed
- `created_at` (string): When it was imported or made
- `duration_seconds` (number|null)
- `error` (object)
- `expires_at` (string): When url and page_url stop working
- `extension` (string): The extension its format takes (.png, .mp4), from the file itself, not its name
- `faces` (object)
- `fingerprint` (string|null): Identity of the file (BLAKE3 of its size and first, middle and last 4 MB), as export.video gives
- `font_family` (string): Fonts: the family; use font_media_id in a text style to set text in it
- `fps`
- `has_audio` (boolean)
- `has_video` (boolean)
- `height` (number|null)
- `job` (object): media.get, while importing or being made: the job doing it
- `kind`
- `look` (object)
- `media_id` (string): Media handle from media.import or a create.* tool
- `mime_type` (string): Its MIME type, likewise
- `name` (string)
- `origin` (object): Where it came from, to tell one version from another: the import source, the export of which timeline, or the create.* tool and the media it was made from
- `page_url` (string): media.get: a page that plays or shows the file in any browser, with no sign-in, to give a person; works until expires_at
- `pages` (object): media.get with a range, offset or limit: per included analysis, which piece of its list came back
- `picture` (object)
- `probe` (object): media.get: everything FFprobe read from the file, as it gives it: the format and every stream (codec, pix_fmt, color_space, color_primaries, color_transfer, color_range, bit depth, sample rate, chann…
- `sound_seconds` (number): How long the sound runs; under duration_seconds, the end is soundless
- `sounds` (object): With include ["sounds"]: the latest media.analyze sounds run, every probability included
- `speakers` (object)
- `speech` (object): With include ["speech"]: the speech probability of every 32 ms frame
- `status` (string): importing: not probed yet; processing: usable in edits, proxy still rendering; ready; failed
- `transcript` (object): With include ["transcript"]: every word as {text, start, end, confidence}
- `upload` (object): media.import {source: {upload}}: where to send the file. It imports once the last byte arrives (the PUT answers with the job_id); media.get says where it stands
- `url` (string): media.get: a signed download of the file itself, once it has one
- `voice` (object)
- `warnings` (array): create.motion and create.still: what went wrong in the page as it rendered (uncaught errors, console errors, files blocked or not found, tags never closed). It rendered anyway
- `width` (number|null)

### `media.seek` (~1131 tokens)

Seek by natural language

Find the moments in a video, or in an edit as it renders, that match what you describe in plain words, so you go straight to them instead of watching it all. Name each criterion and say what a matching frame shows; you get back the stretches of time where each one holds, with the time and picture of the best frame in each.
Use it to:
\- Check quality: find frames that are too dark or blown out, out of focus, black or blank (a gap in an edit), with a microphone or crew in shot, with text cut off at the edge, or with a caption over a face. With timeline_id it checks the edit as it renders, titles, effects and gaps included.
\- Watch efficiently: looking through a whole video costs you a picture for every moment. Seek judges the frames for you and says where things are, so you look only there: where the demo starts, where the speaker holds up the product, where the second location begins.
\- Pick shots: B-roll or a thumbnail moment that fits a description ("a wide shot of a city street at night", "she smiles straight at the camera with her eyes open").
\- Find changes: with with_previous, where the slide changes, someone walks in or the camera angle switches ("`frame` shows a different slide from `previous`").
\- Find room or trouble for overlays: where the lower third stays empty for a title, or where a logo, a licence plate or a face appears that needs a blur.

HOW TO WRITE CRITERIA. TypeSafe Jev, a fast judgment model (preview), judges each sampled frame on its own, as a picture named `frame` (about 512 px on its long edge; with with_previous also `previous`, the frame sampled before), and gives each criterion the probability that it is true. A criterion is a statement or a question about how the picture looks. Keep one judgment to a criterion, and say what shows rather than a verdict: not "bad lighting" but "the faces are too dark to make out their eyes"; not "a good thumbnail" but "a person looks straight at the camera with eyes open and mouth closed". Criteria that n…

Input parameters:

- `at_seconds` (array): Sample exactly these times instead
- `criteria` (object, required): Name (yours, not shown to the model) to what a matching frame shows: a statement or question about the picture, or {instructions, criteria: {true, false}} to say what counts as yes and as no. Up to 3…
- `every_seconds` (number): Sample a frame this often. Default 1
- `from_seconds` (number): Start sampling here. Default 0
- `idempotency_key` (string)
- `media_id` (string): Seek in imported media (media seconds)
- `min_probability` (number): A frame matches a criterion when its probability is at least this
- `state` (object): Text or JSON entries shown beside `frame` on every frame, by name, for criteria to refer to (a script line, what a term means)
- `timeline_id` (string): Seek in an edit as it renders (timeline seconds): gaps, cuts, titles and effects included
- `until_seconds` (number): Stop here. Default: the end
- `with_previous` (boolean): Also show the frame sampled before, as `previous`, for criteria about change. The first sample is only shown as `previous`

### `media.listen` (~834 tokens)

Find sounds by description

Find when something can be heard in a video or audio file, or in an edit as it plays, from what you describe in plain words, so you go straight to it instead of listening to it all: laughter, applause, a cough, a door slamming, a phone ringing, a dog barking, music starting, someone typing, a car passing. Name each sound and say what it is; you get back the stretches of time where each one can be heard, with the moment it is clearest.
Use it to:
\- Find reactions and moments: where the crew or the audience laughs, where people clap or cheer, a gasp, a scream.
\- Find trouble to cut or cover: a cough, a sneeze, a phone going off, a door, a siren, wind on the microphone, a click or a bump.
\- Find sound to cut on: a clap, a bang, a hit, the music starting or stopping.

HOW IT WORKS. FlexSED, an open-vocabulary sound detection model (local, on the CPU; about 15 to 20 times faster than real time), listens to every 40 ms of the sound and gives each description the probability that it can be heard there. Describe the sound itself, briefly, as you would caption it ("laughter", "a crowd cheering", "glass breaking"), not the scene or a judgment; it is asked as "The sound of <your words>". Short, common sound names work best; put each sound you want in its own entry, up to 16 a call, all listened for at once.
LIMITS. Inside music it hears "music", not drums, instruments or single hits (for hits in music, media.analyze beats). A short sound can score higher over a short from_seconds to until_seconds around it.
READING THE RESULT. matches gives, per sound, each run of frames at or above min_probability (default 0.5) with its start, end, peak_seconds and frame (for render.frames; media frames for video, timeline frames for an edit). peaks gives the highest probability each sound reaches, and scores every probability (step_seconds apart), for your own cut-off. A sound whose peak stays well under your cut-off is most likely not there; one hovering near it is worth a listen or a lowe…

Input parameters:

- `from_seconds` (number): Start listening here (media or timeline seconds). Default 0
- `idempotency_key` (string)
- `media_id` (string): Listen to imported media (media seconds)
- `min_probability` (number): A frame matches a sound when its probability is at least this
- `sounds` (object, required): Name (yours) to the sound in plain words: what it is, as you would caption it ("laughter", "a crowd cheering", "a door slams", "a phone ringing", "a dog barking", "keyboard typing"). Up to 16, each l…
- `step_seconds` (number): scores: one value per this many seconds (the highest in each). Default 0.04, every frame, coarser when that would be too many
- `timeline_id` (string): Listen to an edit as it plays (timeline seconds): its mix, with cuts, gaps, levels and effects
- `until_seconds` (number): Stop here. Default: the end

### `judge.ask` (~793 tokens)

Ask Jev

Ask TypeSafe Jev, a fast judgment model (preview), your questions about a state you give, and get typed answers back in about a second.
Jev sees exactly the state: each entry under its name, and your questions refer to entries by name in backticks ("in `passage`"). An entry is text (a string, or a JSON object or array of related facts) or a picture: {media_id} for an imported image, {media_id, at_seconds} for the frame of a video at that time, {timeline_id, at_seconds} for a frame of an edit as it renders (to judge a grade or a layout you made). Mix them freely. Each call stands alone, so put everything a judgment needs into its state.
Choose the type by the answer you need. noul: yes or no, answered as noul, the probability of yes from 0 to 1 (0.5 or more reads as yes). choice: one of options you name, answered as choice plus probabilities and confidence. score: a degree on levels you order, answered as score (0 is the first level) plus confidence. box: where one thing is in a picture, answered as box {x, y, w, h} in fractions of that picture plus confidence.
Write each question as one thing, worded so yes means it is there. Open with what the state is ("`passage` is a transcript of someone speaking off the cuff"), and give a noul criteria {true, false} with an example of each: Jev answers most accurately from examples. To locate, name the one thing so only it fits ("the yellow headline 'Get started' at the top of `shot`") and ask a noul whether it is there; trust a box with high confidence.
One call answers many questions about one state at once, so judge many items together: put the shared text in state once, and give each item its own question carrying its own marked copy. Filler words: state {passage: "So basically the way this works is"}, and per word a noul "`passage` is a transcript of someone speaking off the cuff. With one word marked: "So [[basically]] the way this works is". Is the marked word filler, so the sentence says the same without it?" with crit…

Input parameters:

- `questions` (object, required): id (yours, not shown to the model) to question; up to 300, all answered about the same state
- `state` (object, required): What Jev sees, entry by entry: name to a string, a JSON object or array, or a picture: {media_id} (an image), {media_id, at_seconds} (a video frame) or {timeline_id, at_seconds} (a frame of an edit a…

Output parameters:

- `answers` (object): Per question id: noul {noul}; choice {choice, probabilities, confidence}; score {score, probabilities, confidence}; box {box: {x, y, w, h} fractions of the picture, confidence}
- `model` (string)

### `create.motion` (~368 tokens)

Create a motion graphic

Render animated HTML to a video with alpha, frame-exactly: lower thirds, animated titles, callouts, logo stings, kinetic type, generated graphics. Place the media_id with clip.add on a track above the video. Up to 60 s, and width × height × fps × seconds up to 30 s of 1080p30. Runs in the sandbox: no network (inline everything, load libraries from lib/; assets are for imported files the page must show as they are, like the user's logo or a font), fixed memory and time limits. To match a reference you were shown, rebuild it in HTML/SVG; it does not need importing. The result's warnings list what went wrong in the page: uncaught errors, console errors, files blocked or not found. Long-running: returns a task, or {job_id, media_id} for job.get. The same recipe returns the same media.

Input parameters:

- `assets` (array)
- `duration_seconds` (number, required)
- `fps` (number)
- `height` (integer)
- `html` (string, required): An animated HTML page or fragment laid out at width × height. Anything that animates in a browser renders frame-exactly: CSS animations and transitions, the Web Animations API, SVG, requestAnimationF…
- `idempotency_key` (string)
- `name` (string)
- `transparent` (boolean): Alpha (ProRes 4444) so it composites over video; false gives H.264
- `width` (integer)

### `create.still` (~239 tokens)

Create a still

Render an HTML page to a PNG: title and end cards, thumbnails, overlays, designed text. Transparent where nothing is painted. Capture waits for fonts, and for window.__ready if the page sets it to a promise (up to 10 s). Runs in the sandbox: no network (inline everything, load libraries from lib/; assets are for imported files the page must show as they are, like the user's logo or a font), fixed memory and time limits. To match a reference you were shown, rebuild it in HTML/SVG; it does not need importing. The result's warnings list what went wrong in the page: uncaught errors, console errors, files blocked or not found. Long-running: returns a task, or {job_id, media_id} for job.get.

Input parameters:

- `assets` (array)
- `height` (integer)
- `html` (string, required): An HTML page or fragment, laid out at width × height
- `idempotency_key` (string)
- `name` (string)
- `transparent` (boolean): Unpainted areas stay transparent
- `width` (integer)

### `create.matte` (~125 tokens)

Create a matte

A solid or gradient picture (PNG, any length on a timeline): backgrounds, slugs, colour washes with clip.set_blend. Gradients work like CSS: angle 180 runs top to bottom; radial reaches the corners; colours may carry alpha (#RRGGBBAA). Long-running like every create.* tool, but quick.

Input parameters:

- `color` (string): A solid colour
- `gradient` (object)
- `height` (integer)
- `idempotency_key` (string)
- `name` (string)
- `width` (integer)

### `create.voiceover` (~135 tokens)

Create a voiceover

English text spoken by a natural voice (Kokoro, local; the model downloads once) as WAV media. words (default) times each word into the media transcript (media.get include transcript). Long-running: a task, or {job_id, media_id} for job.get.

Input parameters:

- `idempotency_key` (string)
- `name` (string)
- `speed` (number)
- `text` (string, required): What to say, in English
- `voice` (string): a/b: American/British; f/m: female/male
- `words` (boolean): Time each word

### `create.cutout` (~263 tokens)

Cut out a subject

A subject without its background, local: ProRes 4444 with alpha and the sound, or a PNG, to lay over a new background. subject any (BiRefNet) cuts out products, animals or objects; people (MODNet) is fast enough for long video. region: one item by its bounds, followed; also a clip.set_mask matte. Long-running: a task, or {job_id, media_id} for job.get.

Input parameters:

- `idempotency_key` (string)
- `media_id` (string, required): Media handle from media.import or a create.* tool
- `name` (string)
- `region` (object): The item in this box (top-left x, y, size w, h; fractions) and/or at these points (keep false: not it) at at_seconds, outlined through the video (EdgeTAM, ~0.5 s a frame)
- `smooth` (number): Video: steadies the edge
- `subject` (string): people: MODNet, fast, for video of people; any: BiRefNet, any subject (products, animals, objects), about 1 s a frame and 3 GB of memory; auto: any for pictures, people for video

### `create.stabilized` (~258 tokens)

Steady shaky video

A steadied copy of shaky video, local (vid.stab): one pass measures the camera shake over the whole media, a second moves each frame against a smoothed camera path. New media, ProRes with the sound, to put on the timeline in place of the shaky clip (clip.replace keeps its place and timing). Long-running: a task, or {job_id, media_id} for job.get.

Input parameters:

- `edges` (string): What fills the edges steadying moves into view. zoom: one zoom for the whole video, enough to hide them; adaptive_zoom: as little zoom as each moment needs; black: no zoom, black edges; keep: no zoom…
- `idempotency_key` (string)
- `media_id` (string, required): Video to steady
- `name` (string)
- `shakiness` (integer): How shaky the footage is: 1 a little, 10 a lot
- `smoothing` (integer): Frames each side the camera path is smoothed over: more is steadier but follows intended moves (pans) more slowly
- `tripod` (boolean): Hold the picture still against its first frame, as if on a tripod

### `create.upscaled` (~238 tokens)

Add resolution

A larger copy of low-resolution video or an image with detail drawn in, local: Real-ESRGAN (realesr-general-x4v3, on the CPU) makes each frame four times the size, then it is resized to scale. Slow: on 8 cores about 1 s a frame for 320x180, 2.5 s for 640x360. Sources up to 1280x720, results up to 3840 px on the long edge. New media (ProRes with the sound, or a PNG) to put in place of the original (clip.replace keeps its place and timing). Long-running: a task, or {job_id, media_id} for job.get.

Input parameters:

- `denoise` (string): How much noise and compression blocking is cleaned while detail is drawn in: light keeps grain and fine texture, strong gives the smoothest picture
- `idempotency_key` (string)
- `media_id` (string, required): Video or image to enlarge
- `name` (string)
- `scale` (number): How many times wider and taller the result is

### `create.stems` (~367 tokens)

Separate voice and music

The sound of a video or audio file pulled apart into stems, each new audio media for clip.add: vocals (voices, spoken or sung), drums, bass, other (everything else: guitars, keys, effects, room noise) and no_vocals (all but the voice, in one file). Laid together at the same time, the four stems make the original sound again.
Use it to keep only the voice (lose the music under an interview: vocals), keep only the music (take the voice out of a clip: no_vocals), turn the music down under speech (vocals and no_vocals on their own tracks, with their own gain), or take a beat or a bass line on its own.
Demucs v4 (htdemucs), local, on the CPU: about a fifth of the media's length on 8 cores (a minute of sound in about 15 s). Built for music; on speech with background noise, vocals keeps the voice and other takes most of the noise. Media up to 3 hours. Each stem is a 32-bit float WAV, 44.1 kHz stereo, as long as the original; the same media and stems give the same media again while they last. Long-running: a task, or a job_id for job.get.

Input parameters:

- `idempotency_key` (string)
- `media_id` (string, required): Video or audio with sound
- `name` (string)
- `stems` (array): vocals: voices, spoken or sung; drums, bass, other: the rest, by kind (other holds everything else: guitars, keys, effects, room noise); no_vocals: all but the voice in one file. Each becomes its own…

### `create.lipsync` (~654 tokens)

Lip-sync a video to new speech

Make a person on camera say different words: their mouth is redrawn to match the speech you give (a re-recorded line, a create.voiceover, a word taken from elsewhere in the take). Runs at the user's lip-sync provider on their own key (sync. labs), and the provider bills them for each second sent. Only for the user's own footage, or people who agreed to it (cuttingfloor.ai/acceptable-use). To replace a line or swap a word, give range, the span of the video where it is said, and audio (with audio_range) holding just the new words. The result is that span alone, re-synced and carrying the new speech, as long as the speech with sync_mode remap; place it over the original span (clip.add on a track above, or clip.replace) and mute the old words. Send only the span: it costs less and finishes sooner. Lip sync is for when the face stays on screen while the words change. When the speaker is off screen at that moment, or a cutaway (b-roll, the screen being talked about, a reaction) can cover the splice, the cutaway hides the edit without it. Takes minutes. Long-running: a task, or {job_id, media_id} for job.get. The same recipe returns the same media. Provider limits (plan length, credits, file size) come back as limit_exceeded with the provider's error_code.

Input parameters:

- `audio` (string, required): The speech they should say: audio media, or a video whose sound is used (e.g. create.voiceover, a re-recorded line)
- `audio_range` (object): Only this span of the audio, in the audio's seconds. Default: all of it
- `idempotency_key` (string)
- `model` (string): The provider's model: lipsync-2 (the cheapest, about $0.05 a second), lipsync-2-pro (finer teeth, beards and detail), sync-3 (close-ups, profiles, hands or mics in front of the mouth; about $0.13 a s…
- `name` (string)
- `provider` (string): sync: sync. labs (sync.so), with the user's SYNC_API_KEY
- `range` (object): Only this span of the video, in the video's seconds (e.g. the sentence or word being replaced). The result is that span alone, re-synced; place it over the same span on the timeline. Default: the who…
- `sync_mode` (string): When the picture and the speech differ in length. remap: the picture is sped up or slowed to the speech (result as long as the speech); cut_off: the longer one is cut (as long as the shorter); silenc…
- `video` (string, required): The video of the person speaking, whose mouth is redrawn

### `render.frames` (~280 tokens)

Render frames

Look at a timeline (or a media file): up to 16 frames as one contact-sheet image (layout grid) or one image each (single). Media frames are numbered at the media frame rate; a picture that can be see-through (alpha or a palette) is shown over a light checkerboard, so transparency shows as checks and a background baked into the file as itself. size full: up to 4 frames at the sequence (or media) size from the original media, for thumbnails and stills to keep. format webp (default), png or jpeg, with quality for webp and jpeg. Each image comes inline and with a signed download url.

Input parameters:

- `format` (string): jpeg: for places that take nothing else; png: lossless
- `frames` (array, required): Timeline frames, or media frames at the media rate
- `layout` (string): grid: one contact sheet
- `long_edge` (integer)
- `media_id` (string): Media handle from media.import or a create.* tool
- `quality` (integer): WebP and JPEG, 1–100 (default 82 WebP, 85 JPEG)
- `size` (string): full: the sequence size from original media (long_edge ignored)
- `timeline_id` (string): Timeline handle; every edit returns a new one

Output parameters:

- `images` (array)

### `export.video` (~414 tokens)

Export video

Render the timeline to a file. Presets: web_1080p_h264 (MP4, fits 1920x1080), master_prores_422hq (MOV, sequence size), social_9x16_h264 (9:16 sequences, 1080x1920), web_1080p_av1 (MP4, AV1/Opus), gif (640 px, 15 fps), review_h264 (MP4, fits 854x854 with sound: small and quick, to watch a cut), audio_wav_48k, audio_mp3. overrides changes size, fps, bitrate or crf (video://catalog/presets). Long-running: returns a task, or {job_id} for job.get. The result links the file (a resource_link to video://jobs/{job_id}/output, which reads as the file, or its signed URL when large), has a signed download URL and a page_url that plays it in any browser (the link to give a person to watch or share), both working for 24 h (job.get gives fresh ones) . The server keeps the file 7 days: to keep it longer, save it from the url, and media.import it to use it again. qa.check measures a render (gaps, frames, loudness) before you export. sha256 and fingerprint tell two renders apart without downloading them (fingerprint matches media.get of the file once imported).

Input parameters:

- `filename` (string): Output file name without extension. Default: timeline name and id
- `idempotency_key` (string)
- `overrides` (object): Change the preset: a size box (aspect kept), frame rate, bitrate or quality
- `overwrite` (boolean)
- `preset` (string, required): video://catalog/presets lists each with what it takes
- `range` (object): Frames, end exclusive
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

### `qa.check` (~326 tokens)

QA check

Measure the rendered timeline: gaps in the picture, offline media, rendered against expected frame count, integrated loudness (LUFS) and true peak (dBTP), plus black and silent stretches when you give their settings. With speech {min_probability}, every cut is listed with speech facts: the speech probability of the mix just before and after it (Silero VAD), whether it lands while someone is speaking, the seconds without speech each side, and, per clip edge, the transcript word it cuts through and how much of that word is lost (from the media's transcript, when it has one). Facts only, no verdicts. A timeline up to 20 s is checked within the call (which waits its turn for a render when the server is busy); longer ones return a task or job.

Input parameters:

- `black` (object): Find black stretches with these settings; left out, none are looked for
- `range` (object)
- `silence` (object): Find silent stretches in the mix with these settings; left out, none are looked for
- `speech` (object): List every cut with speech facts (Silero VAD on the mix, and the transcript word each clip edge cuts through); left out, cuts are not listed. Only junctions between two clips are listed, so a lone cl…
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

### `job.get` (~306 tokens)

Get job

Status, progress and, once completed, the result of long-running calls (media.import, media.analyze, create.*, export.video, qa.check). Same record as tasks/get. status_message says what it is doing, or where it stands in the queue; for names the media or timeline. wait_seconds (up to 50) answers when any job asked for finishes, fails or is cancelled, or when the wait runs out (then with its progress), so one call waits instead of many polls. seconds_left and starts_in_seconds come only when progress so far allows. job_ids reads many and answers {jobs, not_found}, each result in brief (result_summary: lists cut to their first items, with what was cut) unless result is full; result summary does the same for one job. A job whose progress stops for minutes ends with a stalled error, freeing its slot.

Input parameters:

- `job_id` (string): One job: the answer is that job
- `job_ids` (array): Many jobs at once (up to 100): the answer is {jobs, not_found}
- `result` (string): Default full for one job_id; summary for job_ids, so a check on many stays small (full gives them whole)
- `wait_seconds` (number): Wait up to this long (at most 50 s) for any job asked for to finish (its status changes); progress along the way does not end the wait; 0 answers at once

### `job.list` (~144 tokens)

List jobs

Your recent jobs, newest first, each with its status, progress and for (the media or timeline it is for), filtered by status, tool or media_id. Results are left out unless you ask (result summary or full); job.get reads one.

Input parameters:

- `limit` (integer)
- `media_id` (string): Only jobs for this media (importing, making or reading it)
- `result` (string): none (default): each job without its result, to find one; summary: lists cut short; full: every result whole
- `status` (string)
- `tool` (string): Only jobs of this tool, e.g. media.import

Output parameters:

- `jobs` (array)

### `job.cancel` (~59 tokens)

Cancel job

Stop a job, queued or running, within seconds: its engines are killed and its slot goes to the next job. Finished jobs are left as they are.

Input parameters:

- `job_id` (string, required): Job handle; the same value is the MCP task id

Output parameters:

- `created_at` (string)
- `error` (object)
- `for` (object): What it is for, known from the start
- `job_id` (string): Job handle; the same value is the MCP task id
- `progress` (number)
- `result`: The tool's structured result once completed
- `result_summary` (object): result "summary": the result in brief; result "full" has all of it
- `seconds_left` (number): Running: seconds left at its recent rate of progress, once it has moved steadily (later steps may differ)
- `starts_in_seconds` (number): Queued next in line: seconds until a slot should free, when every job holding one has seconds_left
- `status` (string)
- `status_message` (string): What it is doing; while it waits, its place in the queue and what it waits for (a job slot, a render slot or the sandbox)
- `tool` (string)
- `updated_at` (string)

### `export.timeline` (~302 tokens)

Export timeline

Write the edit for another editor: fcpxml (Final Cut Pro, DaVinci Resolve), otio (Premiere, Resolve, OpenTimelineIO apps), edl (CMX 3600, one video track), or srt/vtt (the captions). Best effort: warnings lists what the format could not carry (masks, blend modes, effects, animated transforms, ramps, text styling). timeline.get is the lossless copy. media reference (default) points at the media where this server keeps it, which another machine cannot open and which goes 7 days after last use (the result warns); media collect makes a .zip with the media files next to the timeline file, to hand the edit on. Long-running: a task, or {job_id} for job.get; the result has a signed download URL.

Input parameters:

- `filename` (string): Output file name without extension. Default: timeline name and id
- `format` (string, required): fcpxml: Final Cut Pro, Resolve; otio: Premiere, Resolve, any OTIO app; edl: CMX 3600; srt/vtt: captions
- `idempotency_key` (string)
- `media` (string): reference: point at the media where it is; collect: a .zip with the media in it
- `overwrite` (boolean)
- `timeline_id` (string, required): Timeline handle; every edit returns a new one

### `skills.search` (~180 tokens)

Find a skill

Find step-by-step instructions for a common job, written for you to follow with these tools: cutting the pauses, ums and restarts out of a talking video, for one. Search in the user's words; every skill comes back best first with its score (roughly 0 to 1). Below 0.1 is unrelated; a real fit usually scores 0.15 to 0.45, lower for a short or general ask, and an unrelated skill that shares words can score as high, so read the description of each one above 0.1 to judge the fit. Read one with skills.get, then follow its Steps.

Input parameters:

- `limit` (integer): Most matches to return, best first
- `query` (string, required): What the user wants done, in their words, e.g. "remove the ums from my video"

Output parameters:

- `skills` (array): Best first; empty when nothing fits

### `skills.get` (~69 tokens)

Read a skill

The whole skill as Markdown: when to use it, what it needs, numbered Steps naming the tools to call, a decision table and the rules to keep. Follow the Steps in order.

Input parameters:

- `slug` (string, required): From skills.search, e.g. "cut-pauses-ums-and-restarts"

Output parameters:

- `author` (string): Who wrote the skill; credit them when you name it
- `author_url`: A link to the author or the work the skill came from; null when there is none
- `description` (string)
- `markdown` (string): The whole skill, frontmatter included: follow its Steps
- `name` (string)
- `related` (array)
- `slug` (string)
- `version` (number)

## Diagnostics

Captured diagnostic sections: TLS, DNSSEC, Authorisation, Transports. The full working is on the page: https://verifymcp.io/servers/mvp2o-ai-cuttingfloor/api#diagnostics

## Score history

- 2026-11-06: 82
- 2026-11-05: 81
- 2026-11-04: 81
- 2026-11-03: 81
- 2026-11-02: 80
- 2026-11-01: 80
- 2026-10-31: 79
- 2026-10-30: 79
- 2026-10-29: 78
- 2026-10-28: 78
- 2026-10-27: 77
- 2026-10-26: 77
- 2026-10-25: 37
- 2026-10-24: 37
- 2026-10-23: 37
- 2026-10-22: 37
- 2026-10-21: 37
- 2026-10-20: 37
- 2026-10-19: 37

## Common questions

### What is the cuttingfloor MCP server?

cuttingfloor is an MCP server listed in the public MCP registry as io.github.Mvp2o-ai/cuttingfloor. Hosted video editing for AI agents: cut, caption, mix and export real video. Sign in with OAuth. This page covers its hosted endpoint (https://api.cuttingfloor.ai/mcp).

### Is the cuttingfloor MCP server safe to use?

cuttingfloor scores 82 out of 100 on VerifyMCP. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the cuttingfloor MCP server expose?

cuttingfloor exposes 72 tools: timeline.create, timeline.get, timeline.load, timeline.history, timeline.diff, and 67 more. Their descriptions and schemas cost roughly 19,335 tokens of context every time the server is loaded.

### Does the cuttingfloor MCP server require authentication?

Yes. cuttingfloor asked us for credentials when we connected, so you will need to authorise it in your MCP client before it can do anything.

### Is the cuttingfloor MCP server still maintained?

cuttingfloor is still listed as active in the MCP registry. We last reached this channel on 6 November 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What did VerifyMCP test on the cuttingfloor MCP server?

VerifyMCP ran 14 checks on cuttingfloor, covering TLS certificate, Authorization, HTTPS, HSTS header and 10 more. Supply Chain Security and Provenance & Transparency do not apply to a hosted endpoint, so they were not tested. The score does not measure everything, and the scoring documentation lists what it leaves out.

## Links

- Remote endpoint: https://api.cuttingfloor.ai/mcp
- Repository: https://github.com/Mvp2o-ai/cuttingfloor
- Website: https://cuttingfloor.ai/
- Changelog RSS feed: https://verifymcp.io/servers/mvp2o-ai-cuttingfloor/api.xml
- Changelog JSON feed: https://verifymcp.io/servers/mvp2o-ai-cuttingfloor/api/changelog.json
- Trust report (JSON): https://verifymcp.io/servers/mvp2o-ai-cuttingfloor/api.json
- HTML version of this page: https://verifymcp.io/servers/mvp2o-ai-cuttingfloor/api
