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

# Editor and canvas

> Projects, their timelines and canvases, the templates you place on them, and exports.

Projects, their timelines and canvases, the templates you place on them, and exports. 28 tools.

Every tool below is exposed by the hosted MCP server at `https://mcp.contenthero.ai` and by the `@contenthero/mcp` npm package. A <Badge>write</Badge> tool changes your account; a <Badge>read</Badge> tool does not.

### `list_projects`

<Badge>read</Badge>

List the account's editor (video timeline) and canvas (slides/layers) projects. Filter by state (archived / favorited), by type (editor / canvas), or by a title search. Returns lightweight summaries; call get\_project for a single project's full composition. Requires the editor:read scope.

<ParamField body name="cursor" type="string">
  The previous page's cursor.
</ParamField>

<ParamField body name="filter" type="`archived` \| `favorited`">
  archived -> only archived; favorited -> favorited and not archived; omitted -> active (not archived).
</ParamField>

<ParamField body name="kind" type="`editor` \| `canvas`">
  Deprecated alias for `type`, accepted for one release window.
</ParamField>

<ParamField body name="limit" type="integer">
  How many to return.
</ParamField>

<ParamField body name="order" type="`asc` \| `desc`">
  The sort direction.
</ParamField>

<ParamField body name="search" type="string">
  Case-insensitive title search.
</ParamField>

<ParamField body name="sort" type="`updatedAt` \| `createdAt` \| `title`">
  The field to sort by.
</ParamField>

<ParamField body name="surface" type="`editor` \| `canvas`">
  Deprecated alias for `type`, accepted for one release window.
</ParamField>

<ParamField body name="type" type="`editor` \| `canvas`">
  Restrict to one project type; omitted returns both.
</ParamField>

### `get_project`

<Badge>read</Badge>

Read a project's composition + revision. By DEFAULT returns a SUMMARY: metadata + revision + a lightweight per-clip view, dropping heavy payloads (media URLs, transcripts, clips' code, caption words) so a structural read stays small. For a TIMELINE the summary is each clip's \{ id, type, from, durationInFrames, speed, trackId, disabled, sourceId }, where `sourceId` identifies the SOURCE FILE the clip was cut from: clips sharing a sourceId came from one recording, and a clip id never tells you this (ids are minted per edit, so a shared prefix means a shared operation, not a shared file). Use it to tell a rough cut of one recording apart from a timeline of separate files, which decides how much work a source-wide job is: an audio enhancement runs one vendor production per source. Absent when the clip's media is not one of our stored objects (an external stock url); for a CANVAS it is each slide with its layers' \{ id, type, text }, where a text layer's `text` is truncated to 80 chars and is what lets you tell one slide (or one of two text layers) from another without a full read. Pass detail:'full' for the complete composition (every prop, for a faithful round-trip or a deep edit). Scope a timeline read to a window with fromFrame/toFrame (and optionally trackId) to get just the clips overlapping that range, the same convention as get\_transcript's startMs/endMs; scope a CANVAS read to one slide with slideId, which applies to detail:'full' too, so asking for a single slide's detail does not pay for the whole deck. Pass the returned revision back as expectedRevision for a concurrency-safe edit. Editing does NOT require this call: update\_timeline/update\_canvas' expectedRevision is optional, and get\_transcript already returns the revision. Requires the editor:read scope.

<ParamField body name="projectId" type="string" required>
  The project id to read.
</ParamField>

<ParamField body name="detail" type="`summary` \| `full`">
  'summary' (default) returns the lightweight per-clip/per-layer structure; 'full' returns the complete composition with every property.
</ParamField>

<ParamField body name="fromFrame" type="integer">
  Timeline only: start of a frame window; returns clips overlapping \[fromFrame, toFrame].
</ParamField>

<ParamField body name="slideId" type="string">
  Canvas only: scope the read to a single slide by id. Applies to detail:'full' as well. An id matching no slide returns every slide rather than nothing.
</ParamField>

<ParamField body name="toFrame" type="integer">
  Timeline only: end of the frame window (see fromFrame).
</ParamField>

<ParamField body name="trackId" type="string">
  Timeline only: scope the read to a single track by id.
</ParamField>

### `create_project`

<Badge>write</Badge>

Create a new project. `type` picks what it is: 'editor' (video timeline) or 'canvas' (slides/layers). All fields are optional; defaults match the in-app new-project flow (16:9 landscape, an editor project). A new canvas starts with one empty slide already, so add content to it with update\_canvas create\_layer (use create\_slide only to add MORE slides); A new editor starts with one empty main track, named in the result: put the first clips on it with update\_timeline create\_clip, and add a track only for something that sits over the main story. When the project is for a planned piece, pass cardId to link it to that card in the same call. Returns the new project id + revision. Requires the editor:write scope (and planner:write with cardId).

<ParamField body name="brandKitId" type="string">
  Optional brand kit to associate.
</ParamField>

<ParamField body name="cardId" type="string">
  The card this project is for: the new project is linked to it in the same call (it shows among the card's assets), so the card opens the edit from the moment it exists. Also needs the planner:write scope.
</ParamField>

<ParamField body name="fps" type="number or number or number or number or number">
  Frames per second for an editor project: 24, 25, 30, 50 or 60. Defaults to 30.
</ParamField>

<ParamField body name="height" type="number">
  Pixel height. Defaults from the orientation.
</ParamField>

<ParamField body name="kind" type="`editor` \| `canvas`">
  Deprecated alias for `type`, accepted for one release window.
</ParamField>

<ParamField body name="orientation" type="string">
  Aspect ratio, e.g. '16:9', '9:16', '1:1'. Defaults to '16:9'.
</ParamField>

<ParamField body name="surface" type="`editor` \| `canvas`">
  Deprecated alias for `type`, accepted for one release window.
</ParamField>

<ParamField body name="title" type="string">
  Project title. Defaults to 'Untitled'.
</ParamField>

<ParamField body name="type" type="`editor` \| `canvas`">
  The project type. Defaults to 'editor'.
</ParamField>

<ParamField body name="width" type="number">
  Pixel width. Defaults from the orientation.
</ParamField>

### `update_project`

<Badge>write</Badge>

Change a project's own fields: rename it, resize it, associate a brand kit, or choose and frame its cover. This is a PATCH: a field you omit is left alone. To change what is in the composition, use update\_timeline or update\_canvas instead. Returns the project as it now stands. Requires the editor:write scope.

<ParamField body name="projectId" type="string" required>
  The project id.
</ParamField>

<ParamField body name="brandKitId" type="string,null">
  A brand kit id from list\_brand\_kits, or null to clear it.
</ParamField>

<ParamField body name="cover" type="string or object or object">
  How the cover is chosen: 'auto' follows the composition, \{ frame } is a chosen moment (for a canvas, a slide's index among its visible slides), \{ mediaId } is an image from the library.
</ParamField>

<ParamField body name="coverPosition" type="object or null">
  Where the cover is framed, as percentages of its width and height, or null for the default framing.
</ParamField>

<ParamField body name="height" type="number">
  A new pixel height.
</ParamField>

<ParamField body name="orientation" type="string">
  A new aspect ratio.
</ParamField>

<ParamField body name="title" type="string">
  A new title.
</ParamField>

<ParamField body name="width" type="number">
  A new pixel width.
</ParamField>

### `duplicate_project`

<Badge>write</Badge>

Copy a project: a new project with the same type, size, composition and brand kit, titled as the editor titles a copy. Returns the new project. Requires the editor:write scope.

<ParamField body name="projectId" type="string" required>
  The project id to copy.
</ParamField>

### `delete_project`

<Badge>write</Badge>

PERMANENTLY delete a project. This is irreversible: the project, its edit history, and its render exports are destroyed (uploaded media stays in the library). To reversibly hide a project instead, use archive. You must pass confirm: true to proceed. Requires the editor:write scope.

<ParamField body name="confirm" type="boolean" required>
  Must be true to confirm the irreversible permanent delete.
</ParamField>

<ParamField body name="projectId" type="string" required>
  The project id to permanently delete.
</ParamField>

### `share_project`

<Badge>write</Badge>

Make a project's public live link, or revoke it with shared:false. Anyone with the link sees the project as it is now, with no sign-in, until it is revoked; a revoked link stays dead, and sharing again makes a new one. A project has one link, so sharing a shared project returns it. get\_project reports the link. Requires the editor:write scope.

<ParamField body name="projectId" type="string" required>
  The project id.
</ParamField>

<ParamField body name="shared" type="boolean">
  Default true. Pass false to revoke the link.
</ParamField>

### `import_project`

<Badge>write</Badge>

Import a PowerPoint / Google Slides file (by URL) or a Canva design (by id) into a NEW canvas project with editable layers. Set sourceType to 'pptx' and pass fileUrl (a URL to a .pptx / slides file), or set sourceType to 'canva' and pass designId (uses the account's Canva connection; fails with canva\_not\_connected if not linked). Pass cardId to link the new project to its card in the same call. Returns the new project id + revision. Requires the editor:write scope (and planner:write with cardId).

<ParamField body name="sourceType" type="`pptx` \| `canva`" required>
  'pptx' for a file URL, 'canva' for a Canva design id.
</ParamField>

<ParamField body name="cardId" type="string">
  The card this project is for: the new project is linked to it in the same call (it shows among the card's assets), so the card opens the edit from the moment it exists. Also needs the planner:write scope.
</ParamField>

<ParamField body name="designId" type="string">
  Required when sourceType is 'canva': the Canva design id.
</ParamField>

<ParamField body name="fileUrl" type="string">
  Required when sourceType is 'pptx': a URL to the .pptx / slides file.
</ParamField>

<ParamField body name="title" type="string">
  Title for the created project. Defaults to 'Imported deck'.
</ParamField>

### `export_project`

<Badge>write</Badge>

Export (render) a project's saved composition to a downloadable file the user KEEPS: a permanent deliverable that counts against the user's storage. To preview or verify a frame or slide while editing, do NOT export; use get\_context with render (ephemeral, stored nowhere). The formats, what each makes and the options each takes are listed by get\_schema kind 'export'; read it before choosing one. format 'mp4' works for both editor and canvas (a video render; may take a while). 'png' / 'jpg' work for both project types too: a canvas project renders one image per slide (multiple slides come back as a zip), while an editor project renders a single composited frame of the timeline (pick which frame with `frame`; defaults to frame 0). Canvas projects additionally support 'pdf' and 'pptx'. An editor project also exports its sound (mp3, aac, wav), its subtitles (srt, vtt) and its transcript (txt, docx, rtf, md, html). Resolution and watermark apply to every picture format: a free account never exports above 720p and never removes the watermark. `quality` is mp4 only. Returns the download URL when the export finishes in time, otherwise an exportId to poll with get\_export. Requires the editor:write scope.

<ParamField body name="projectId" type="string" required>
  The project to export.
</ParamField>

<ParamField body name="format" type="string">
  Output format, one get\_schema kind 'export' lists. Defaults to 'mp4'.
</ParamField>

<ParamField body name="frame" type="integer">
  Editor still (png/jpg) only: which timeline frame to render. Clamped to the composition length. Defaults 0. Use the playhead frame from get\_context to render exactly the frame the user is viewing.
</ParamField>

<ParamField body name="maxCharsPerLine" type="integer">
  Subtitles (srt, vtt) only: the longest a line may be, 20 to 80 characters. Defaults 42.
</ParamField>

<ParamField body name="maxLinesPerCard" type="integer">
  Subtitles (srt, vtt) only: lines per subtitle card, 1 to 4. Defaults 2.
</ParamField>

<ParamField body name="quality" type="`low` \| `recommended` \| `high`">
  mp4 ONLY: video bitrate. Meaningless for a still, which has no duration to spread bits over. Defaults recommended.
</ParamField>

<ParamField body name="resolution" type="`480p` \| `720p` \| `1080p` \| `2k` \| `4k`">
  Output resolution, for EVERY format including stills. Defaults '720p' for an editor mp4 and the project's NATIVE size for a still or canvas mp4, in both cases clamped to your plan. 1080p and above are plan-gated: NAMING one above your plan is a 403, omitting one clamps instead.
</ParamField>

<ParamField body name="showSpeakers" type="boolean">
  Subtitles (srt, vtt) only: name who speaks, when the captions hold more than one speaker. Defaults false.
</ParamField>

<ParamField body name="timecodes" type="boolean">
  Transcripts (txt, docx, rtf, md, html) only: start each paragraph with its timecode.
</ParamField>

<ParamField body name="watermark" type="boolean">
  Keep the watermark. Defaults true; removing it is plan-gated.
</ParamField>

### `get_export`

<Badge>read</Badge>

Poll an export job started by export\_project. Returns its status and, when done, the download URL. Requires the editor:read scope.

<ParamField body name="exportId" type="string" required>
  The export id returned by export\_project.
</ParamField>

### `list_project_exports`

<Badge>read</Badge>

List a project's exports, newest first, as the editor's Exports tab shows them: each finished one with its download and public share page, and the ones still running with their status (poll one with get\_export). A failed export is not listed. Requires the editor:read scope.

<ParamField body name="projectId" type="string" required>
  The project id.
</ParamField>

<ParamField body name="cursor" type="string">
  The previous page's cursor.
</ParamField>

<ParamField body name="limit" type="integer">
  How many to return.
</ParamField>

### `get_timeline_settings`

<Badge>read</Badge>

Read an editor project's timeline settings for the owner, the settings the editor's timeline settings menu holds: how editing behaves (the magnetic main track, snapping, linkage and which kinds of track it reaches) and how the timeline plays (follow playhead, skimming, skip disabled clips). update\_timeline edits by them as the editor does. Requires the editor:read scope.

<ParamField body name="projectId" type="string" required>
  The editor project id.
</ParamField>

### `update_timeline_settings`

<Badge>write</Badge>

Change some of an editor project's timeline settings; a setting you omit is left alone. The editor shows the change, and later edits ripple by it. Returns all of them. Requires the editor:write scope.

<ParamField body name="projectId" type="string" required>
  The editor project id.
</ParamField>

<ParamField body name="followPlayhead" type="boolean">
  Follow playhead, on or off.
</ParamField>

<ParamField body name="linkage" type="boolean">
  Linkage, on or off: whether other tracks follow the main track's ripple and delete.
</ParamField>

<ParamField body name="linkedTracks" type="object">
  Which kinds of track linkage reaches.
</ParamField>

<ParamField body name="magneticTrack" type="boolean">
  The magnetic main track, on or off.
</ParamField>

<ParamField body name="skimming" type="boolean">
  Skimming, on or off.
</ParamField>

<ParamField body name="skipDisabledClips" type="boolean">
  Skip disabled clips during playback, on or off.
</ParamField>

<ParamField body name="snapping" type="boolean">
  Snapping, on or off.
</ParamField>

### `list_project_versions`

<Badge>read</Badge>

List a project's saved versions, newest first: each with its id, when and why it was saved, its name and who saved it. Bring one back with restore\_project\_version. Version history is a premium feature, as in the editor. Requires the editor:read scope.

<ParamField body name="projectId" type="string" required>
  The project id.
</ParamField>

<ParamField body name="cursor" type="string">
  The previous page's cursor.
</ParamField>

<ParamField body name="limit" type="integer">
  How many to return.
</ParamField>

### `save_project_version`

<Badge>write</Badge>

Save the project's current state as a version, optionally named, so it can be brought back later. Requires the editor:write scope.

<ParamField body name="projectId" type="string" required>
  The project id.
</ParamField>

<ParamField body name="label" type="string">
  A name for the version.
</ParamField>

### `restore_project_version`

<Badge>write</Badge>

Bring back a saved version. action 'restore' (the default) puts it back into the project, saving the current state as a version first so the restore can itself be undone by restoring that; action 'copy' makes a new project from it and leaves this one as it is. Version history is a premium feature. Requires the editor:write scope.

<ParamField body name="projectId" type="string" required>
  The project id.
</ParamField>

<ParamField body name="versionId" type="string" required>
  The version id, from list\_project\_versions.
</ParamField>

<ParamField body name="action" type="`restore` \| `copy`">
  'restore' (the default) puts it back into this project; 'copy' makes a new project from it.
</ParamField>

### `update_project_version`

<Badge>write</Badge>

Name a saved version, or clear its name with an empty label. Requires the editor:write scope.

<ParamField body name="label" type="string" required>
  The new name; empty clears it.
</ParamField>

<ParamField body name="projectId" type="string" required>
  The project id.
</ParamField>

<ParamField body name="versionId" type="string" required>
  The version id, from list\_project\_versions.
</ParamField>

### `delete_project_version`

<Badge>write</Badge>

Permanently remove a saved version. The project itself is not changed. Requires the editor:write scope.

<ParamField body name="projectId" type="string" required>
  The project id.
</ParamField>

<ParamField body name="versionId" type="string" required>
  The version id, from list\_project\_versions.
</ParamField>

### `undo_project_edit`

<Badge>write</Badge>

Reverse the project's most recent edit, as the editor's Undo does, whoever made it. The undo is itself an edit with its own revision, and redo\_project\_edit re-applies what it reversed. Requires the editor:write scope.

<ParamField body name="projectId" type="string" required>
  The project id.
</ParamField>

<ParamField body name="expectedRevision" type="integer">
  The revision from get\_project; rejects with a conflict if a concurrent edit landed.
</ParamField>

<ParamField body name="revision" type="integer">
  A specific revision to reverse. Omit for the most recent edit.
</ParamField>

### `redo_project_edit`

<Badge>write</Badge>

Re-apply the edit most recently undone, as the editor's Redo does. Requires the editor:write scope.

<ParamField body name="projectId" type="string" required>
  The project id.
</ParamField>

<ParamField body name="expectedRevision" type="integer">
  The revision from get\_project; rejects with a conflict if a concurrent edit landed.
</ParamField>

### `update_timeline`

<Badge>write</Badge>

Apply a batch of ops to an EDITOR (video timeline) project. update\_timeline both CREATES and EDITS. EDIT ops act on existing clips: disable\_ranges, delete\_ranges, merge\_clips, move\_clip, trim\_clip, split, delete\_clip, duplicate, set\_disabled, set\_hidden, set\_locked, group, ungroup, update\_group, update\_clip. CREATE ops add new clips/tracks: create\_clip (\{ op: 'create\_clip', trackId, clip }) appends a clip to a track; insert\_track (\{ op: 'insert\_track', referenceTrackId, position: 'above'|'below', trackType }) adds an empty track; insert\_prebuilt\_track (\{ op: 'insert\_prebuilt\_track', index, track: \{ id, name, items: \[item], trackType } }) inserts a whole track WITH its clips in one op (index 0 = top overlay) - the one-shot way to drop a code clip, text or shape onto a project without an existing empty track. TRANSITIONS: add\_transition (\{ op: 'add\_transition', trackId, leftClipId, rightClipId, preset, durationFrames?, curve? }) adds a transition at the CUT between two TOUCHING adjacent clips (leftClipId's out-point meets rightClipId's in-point); preset is one of the transition presets in get\_schema's animation catalog (animations.transition); durationFrames defaults to 1s; curve is a speed curve, shaped as in add\_keyframe; absent is Linear; one transition per cut (re-creating on the same pair replaces it); the result carries createdTransitionId. update\_transition (\{ op: 'update\_transition', transitionId, patch: \{ preset?, durationFrames?, curve? } }) and remove\_transition (\{ op: 'remove\_transition', transitionId }) edit or remove one (transition ids are on each track.transitions\[] in get\_project). ANIMATIONS: add\_animation (\{ op: 'add\_animation', clipId, edge: 'in'|'out', preset, durationFrames?, curve? }) gives a single clip an ENTRANCE (edge 'in') or EXIT (edge 'out') animation, the clip animating against emptiness on that edge; preset is one of the presets in get\_schema's animation catalog (animations.in for edge 'in', animations.out for edge 'out'); durationFrames defaults to half a second, curve is a speed curve, shaped as in add\_keyframe; absent is Linear; idempotent, a per-clip field keyed by clipId + edge so it creates or updates that edge's animation. remove\_animation (\{ op: 'remove\_animation', clipId, edge: 'in'|'out' }) clears it. Choose add\_animation when the motion belongs to ONE clip against emptiness; choose add\_transition for a blend BETWEEN two adjacent clips. KEYFRAMES: add\_keyframe, update\_keyframe and remove\_keyframe animate a clip's properties over time (position, scale, opacity, corner radius, color, volume and more); keyframes live on the track, so one move can span many clips and survives later cuts. Shapes and rules are in get\_schema (kind 'timeline'). COMBO: apply\_combo (\{ op: 'apply\_combo', clipId, presetKey, frame }): Apply a Combo preset to a clip at a moment. It writes ordinary keyframes on the clip's track, starting at that moment, for the properties the preset animates, and replaces keyframes of those properties inside the preset's span. presetKey is one of the Combo presets in get\_schema's animation catalog (animations.combo). The keyframes it writes are then edited like any others. TEMPLATES: insert\_template (\{ op: 'insert\_template', templateId, placement?, props?, brandKitId?, brand?: false }) places a template from list\_templates as the Elements panel does: the clip it makes, branded with the project's brand kit, over the main video at the playhead unless placement says otherwise (its shape is in get\_schema, kind 'timeline'); the result names the clip in createdIds. FRAME RATE: set\_frame\_rate (\{ op: 'set\_frame\_rate', fps }) Change the project's frame rate. Every frame number converts, so clips, cuts and transitions keep their timing; at a lower rate each lands on the nearest frame. BACKGROUND REMOVAL: remove\_background (\{ op: 'remove\_background', clipId }) cuts out the background of an IMAGE or VIDEO clip, replacing it with transparency, as an ASYNC job: the result carries a generatingOutputId to poll with get\_generation\_status, and the clip's media swaps to the transparent cutout when it completes (the original is kept, so it stays restorable). Image removal is FREE; video removal is a PREMIUM metered feature (Champion+, charged per second, 60s cap) and returns an error if the plan or credits are insufficient. Only image/video clips have a background; other clip types return an error. SEPARATE LAYERS: separate\_layers (\{ op: 'separate\_layers', clipId }) splits an IMAGE clip into its background, gaps filled, and one clip per element, each on its own track stacked in place above it, as an ASYNC job (the result carries a generatingOutputId to poll with get\_generation\_status). Paid plans; metered per image returned. MASKS: to add a CapCut-style shape mask to an IMAGE or VIDEO clip, set its `masks` array via update\_clip (or update\_clips) - each entry is a ClipMask cutout (shape, normalized position/size, rotation, feather, invert) that keeps only the pixels inside its shape, multiple masks union, and invert:true subtracts; masks are a clip PROPERTY, not an op, so patch them like any other field (a patch REPLACES the whole array; set \[] to clear), and read get\_schema (kind 'timeline') for the exact ClipMask shape. CAPTIONS: add\_captions (\{ op: 'add\_captions', style?, clipIds? }) generates word-timed captions from the project's transcript, one block per spoken clip on a dedicated caption track - whole-timeline by default, or pass clipIds to scope; `style` is a caption template key (omit for the default); clips without a ready transcript are skipped and reported in the result warnings (media is normally transcribed on ingest); re-running refreshes + restyles existing caption blocks. update\_captions (\{ op: 'update\_captions', style?, patch?, clipIds? }) restyles EXISTING captions - `style` re-resolves a template, `patch` sets caption overlay props directly (e.g. \{ textColor: '#FFDD00', fontSize: 32 }); pass one or both; it never creates captions where none exist (that is add\_captions). remove\_captions (\{ op: 'remove\_captions', clipIds? }) removes captions (all, or a clipIds subset) and drops the caption track if empty. Build the `clip` from get\_schema (kind 'timeline'), which returns a copy-pasteable `example` skeleton per clip type plus the `creation` op shapes; mint your own string ids and put overlays on a NON-primary media track so they do not ripple the primary. Each op is an object with an `op` name plus its fields (e.g. \{ op: 'delete\_clip', clipIds: \['clip-id'] } or \{ op: 'move\_clip', clipId: 'clip-id', toFrame: 90, toTrackIndex: 0 }). CONTENT-AWARE EDITING: to cut sections you found in get\_transcript, DEFAULT to disable\_ranges: \{ op: 'disable\_ranges', clipId, ranges: \[\{ startMs, endMs }], note? } - it takes SOURCE-media time ranges, splits the clip and marks those ranges disabled (non-destructively excluded from the render but still on the timeline, so the user can review via skip-disabled playback and toggle any back on). Prefer the SILENCE edges get\_transcript reports as your cut boundaries (they already include breathing room, so cuts do not clip words or feel abrupt) rather than exact word starts. Pass all of a clip's ranges in ONE disable\_ranges op. The optional `note` is shown to the USER, so keep it concise and human and use mm:ss for any times (never raw ms). delete\_ranges has the same shape but HARD-deletes (ripple-closes the gap, irreversible) - use it ONLY after the user explicitly approves a permanent delete; otherwise always prefer disable\_ranges. set\_disabled toggles a WHOLE clip by id. RE-TIMING: a time-based clip's timeline length is DERIVED from its source media and its playback speed, so you never set its `durationInFrames` directly. To retime a clip, set the duration-affecting property in the `update_clip` (or `update_clips`) patch (today that property is `speed`) and the reducer recomputes the clip's length for you: the clip keeps covering the same span of its source, so its timeline length scales inversely with the speed change (2x speed halves its length, 0.5x doubles it). Re-timing then honors each track's positioning in the SAME op: on the magnetic primary track the following clips ripple so that no gap opens and none is left behind, while clips on other (free) tracks, and every clip while the magnetic track is off, keep their absolute positions. So a speed change, whether on one clip or a bulk `update_clips`, lands gap-free in a single call with no per-clip length math on your side. Any `durationInFrames` you pass for such a clip is ignored in favor of the derived value. update\_clips (\{ op: 'update\_clips', clipIds?, groupId?, patch }) applies one patch to a SET of clips at once, the bulk form of update\_clip; target an explicit clipIds array OR a whole group via groupId (resolves to its members; clipIds wins if both). Every listed clip takes the same patch, but a duration-affecting property re-times each clip from ITS OWN values (a `speed` change recomputes each clip's length from its own source and speed, per the RE-TIMING rule above), and the magnetic primary track ripples so the whole batch lands gap-free in one op. Use it for a bulk property change (speed, volume, opacity, and so on) instead of many update\_clip ops. GROUPS: group (\{ op: 'group', clipIds, name? }) links 2+ clips under one shared groupId, stamping a stable 'Group N' ordinal that never renumbers (the result carries groupId + groupOrdinal); optional name labels it. ungroup (\{ op: 'ungroup', clipIds }) clears the group. update\_group (\{ op: 'update\_group', groupId, patch: \{ name } }) renames a group. List groups with their ids / ordinals / names / member clips via get\_project's top-level `groups`, then target a whole group with update\_group or update\_clips \{ groupId }. merge\_clips (\{ op: 'merge\_clips', clipIds }) rejoins adjacent, same-source, contiguous clips into one (the inverse of split; use it to clean up fragments a range edit leaves behind, or to reverse a cut after re-enabling the disabled pieces). ONE-SHOT CLEANUPS (prefer these over hand-rolling ranges for the common cases): remove\_silence (\{ op: 'remove\_silence', paceThresholdMs?, paddingStartMs?, paddingEndMs? }) detects and disables dead-air pauses across the WHOLE timeline (paceThresholdMs = min pause length to cut, default 500; paddingStartMs/paddingEndMs = breathing room, default 200) - idempotent + re-adjustable, so re-running re-cuts at the new settings; remove\_filler\_words (\{ op: 'remove\_filler\_words' }) disables high-confidence disfluencies (um/uh/er) across the whole timeline; extract\_audio (\{ op: 'extract\_audio', clipIds? }) splits each video clip's audio onto its own track (whole-timeline, or a clipIds subset). remove\_silence + remove\_filler\_words need a ready transcript; each reports a warning + changes nothing when there is nothing to do (no transcript / no gaps / no fillers / no video). They expand to the same disable\_ranges / create\_clip primitives, so reach for get\_transcript + disable\_ranges only for CONTEXTUAL or selective cuts the macros cannot express. expectedRevision is OPTIONAL: omit it to apply to the project's current revision (last-write-wins, fine for single-editor and id-targeted ops), or pass the revision from a prior get\_project/get\_transcript to fail loudly on a concurrent change instead of clobbering it. You do NOT need to fetch the project just to get the revision. Each successful edit returns the new revision for chaining further edits. Requires the editor:write scope.

<ParamField body name="ops" type="object[]" required>
  The timeline ops to apply, in order.
</ParamField>

<ParamField body name="projectId" type="string" required>
  The editor project id.
</ParamField>

<ParamField body name="userIntent" type="string" required>
  A short description of what this edit does (for attribution).
</ParamField>

<ParamField body name="expectedRevision" type="integer">
  The revision from get\_project; rejects with a conflict if a concurrent edit landed.
</ParamField>

### `update_canvas`

<Badge>write</Badge>

Apply a batch of ops to a CANVAS (slides/layers) project. Ops act on layers + slides: create\_layer, update\_layer, delete\_layer, reorder\_layer, duplicate\_layers, set\_layer\_hidden, set\_layer\_locked, group\_layers, ungroup\_layers, set\_layer\_as\_background, create\_slide, update\_slide, delete\_slide, duplicate\_slides, reorder\_slides, set\_background, and more. TEMPLATES: insert\_template (\{ op: 'insert\_template', templateId, placement?, props?, brandKitId?, brand?: false }) places a template from list\_templates as a layer, as the Elements panel does: branded with the project's brand kit, at its own size, centered on the focused slide unless placement says otherwise (its shape is in get\_schema, kind 'layer'); the result names the layer in createdIds. BACKGROUND REMOVAL: remove\_background (\{ op: 'remove\_background', layerId }) cuts out the background of an IMAGE or VIDEO layer, replacing it with transparency, as an ASYNC job (the result carries a generatingOutputId to poll with get\_generation\_status; the layer's media swaps to the transparent cutout when done, the original kept). Image removal is FREE; video removal is PREMIUM + metered (Champion+, per second, 60s cap). Other layer types return an error. SEPARATE LAYERS: separate\_layers (\{ op: 'separate\_layers', layerId }) splits an IMAGE layer into its background, gaps filled, and one layer per element stacked in place, as an ASYNC job (the result carries a generatingOutputId to poll with get\_generation\_status). Paid plans; metered per image returned. Each op is an object with an `op` name plus its fields. expectedRevision is OPTIONAL: omit it to apply to the project's current revision (last-write-wins, fine for a single editor), or pass the revision from a prior get\_project to fail loudly on a concurrent change instead of clobbering it. You do NOT need to fetch the project just to get the revision. Each successful edit returns the new revision for chaining further edits. Requires the editor:write scope.

<ParamField body name="ops" type="object[]" required>
  The canvas ops to apply, in order.
</ParamField>

<ParamField body name="projectId" type="string" required>
  The canvas project id.
</ParamField>

<ParamField body name="userIntent" type="string" required>
  A short description of what this edit does (for attribution).
</ParamField>

<ParamField body name="expectedRevision" type="integer">
  The revision from get\_project; rejects with a conflict if a concurrent edit landed.
</ParamField>

### `get_transcript`

<Badge>read</Badge>

Read an EDITOR project's transcript mapped to its timeline clips, so you can do content-aware editing. Returns one segment per transcribable clip in timeline order, each carrying the words spoken within it plus its current state (\[disabled] = cut/excluded from the render, \[enabled] = kept) and its exact clipId. Use this to read what is said, see which parts are already disabled, then target the exact clipId(s) or source-time ranges with update\_timeline (disable\_ranges to non-destructively cut, set\_disabled to toggle a whole clip). Pass `granularity: 'word'` to also get, per segment: word-level timing with ABSOLUTE timeline frames (so split / range ops are exact), per-word confidence + speaker, the derived silence gaps (for dead-air removal; already inset to the cuttable region so cutting them keeps breathing room), and the non-speech audio events (e.g. "\[chuckles]"); plus the distinct speaker set at the top level. Word mode can be large, so scope it with `search` (a phrase to find) or `startMs`/`endMs` (a source-media window, which also clips the returned words) to page a long clip. Also returns the project's current `revision` so you can edit right away without a separate get\_project: pass it as update\_timeline's expectedRevision for a concurrency-safe edit, or omit expectedRevision to just apply to the current state. Returns mediaTranscribed:false when the media has not been transcribed yet. Requires the editor:read scope.

<ParamField body name="projectId" type="string" required>
  The editor project id.
</ParamField>

<ParamField body name="endMs" type="integer">
  Source-media end time in ms; companion to startMs.
</ParamField>

<ParamField body name="granularity" type="`clip` \| `word`">
  'clip' (default) returns text per clip; 'word' adds word timing + absolute timeline frames + confidence + speaker, derived silences, and audio events.
</ParamField>

<ParamField body name="paceThresholdMs" type="integer">
  Word mode: minimum pause (ms) to report as a silence / dead-air region ("Pace"). Defaults to the project's saved pace, else 500.
</ParamField>

<ParamField body name="paddingEndMs" type="integer">
  Word mode: breathing room (ms) kept before speech at a silence end edge (negative tightens). Defaults to the saved padding, else 200.
</ParamField>

<ParamField body name="paddingStartMs" type="integer">
  Word mode: breathing room (ms) kept after speech at a silence start edge (negative tightens). Defaults to the saved padding, else 200.
</ParamField>

<ParamField body name="search" type="string">
  Case-insensitive substring; returns only clip segments whose text contains it.
</ParamField>

<ParamField body name="startMs" type="integer">
  Source-media start time in ms; with endMs, returns only segments overlapping this window (and, in word mode, clips the returned words to it).
</ParamField>

### `list_templates`

<Badge>read</Badge>

List the templates you can place: ContentHero's and the user's own (the editor's Elements panel), without their code, a page at a time. Each is code, a shape or an animated emoji, with its category, version and default size. Place one with an insert\_template op in update\_timeline or update\_canvas, branded with the project's brand kit; read one whole with get\_template. Requires the editor:read scope.

<ParamField body name="archived" type="boolean">
  true lists only archived templates, to restore one with archive.
</ParamField>

<ParamField body name="category" type="string[]">
  Only templates in these categories.
</ParamField>

<ParamField body name="cursor" type="string">
  The previous page's cursor.
</ParamField>

<ParamField body name="kind" type="`code` \| `shape` \| `emoji`">
  Only templates that draw this.
</ParamField>

<ParamField body name="limit" type="integer">
  How many to return.
</ParamField>

<ParamField body name="scope" type="`system` \| `user` \| `all`">
  system is ContentHero's, user is the user's own; all (the default) is both.
</ParamField>

<ParamField body name="search" type="string">
  Every word, in any order, in the name or the tags.
</ParamField>

### `get_template`

<Badge>read</Badge>

Read one template whole: its code, props, controls (each prop's control, and the brand value it takes when placed), default size and lineage. Someone else's template is not found. Requires the editor:read scope.

<ParamField body name="templateId" type="string" required>
  The template id, from list\_templates.
</ParamField>

### `create_template`

<Badge>write</Badge>

Save a template of the user's own, only when the user asks for one, from exactly one source: fromItem (a code clip, code layer or shape placed on a project: its code, props, controls and box, as fractions of that canvas), fromTemplateId (a copy of a template, keeping its lineage), or the fields alone (exactly one of code, lottie, emoji or shape says what it draws). Fields given beside a source override what it carries. Code is checked as it is written: code that does not compile is refused, with each finding's line and column. It is saved as made by you, and appears under My templates in the Elements panel. Requires the editor:write scope.

<ParamField body name="afterId" type="string">
  Place it immediately after this item of the same list.
</ParamField>

<ParamField body name="aspect" type="number or null">
  Its own width over height, for resize scale.
</ParamField>

<ParamField body name="beforeId" type="string">
  Place it immediately before this item of the same list.
</ParamField>

<ParamField body name="category" type="string">
  Its category: one list\_templates shows, or a new one.
</ParamField>

<ParamField body name="code" type="string">
  The code: a React component, as get\_schema (kind 'code') teaches.
</ParamField>

<ParamField body name="coverage" type="`full` \| `partial`">
  full: the whole frame; partial: a box of widthFraction by heightFraction.
</ParamField>

<ParamField body name="description" type="string or null">
  What it is for.
</ParamField>

<ParamField body name="durationFrames" type="integer">
  How long it lasts when placed, in frames.
</ParamField>

<ParamField body name="emoji" type="string">
  An animated emoji's name, as the animated emoji list names it.
</ParamField>

<ParamField body name="fromItem" type="object">
  A placed code clip, code layer or shape to save: the project id and the clip or layer id.
</ParamField>

<ParamField body name="fromTemplateId" type="string">
  A template to copy, from list\_templates.
</ParamField>

<ParamField body name="heightFraction" type="number or null">
  Its default height, a fraction of the canvas's.
</ParamField>

<ParamField body name="lottie" type="object">
  A Lottie file of ours, and the colors in it that props recolor (each role names a prop).
</ParamField>

<ParamField body name="name" type="string">
  Its name in the Elements panel.
</ParamField>

<ParamField body name="position" type="`top` \| `bottom`">
  Place it at an end of the list instead of beside an item. Name neighbors or a position, not both.
</ParamField>

<ParamField body name="props" type="object">
  Its props as placed.
</ParamField>

<ParamField body name="propsSchema" type="object or null">
  Each prop's control, as get\_template shows them; brand names the brand value a prop takes when placed.
</ParamField>

<ParamField body name="resize" type="`scale` \| `reflow`">
  scale keeps its proportions in any box; reflow lays out again in the box it is given.
</ParamField>

<ParamField body name="shape" type="string">
  A shape's name, as get\_schema (kind 'layer') lists them.
</ParamField>

<ParamField body name="tags" type="string[]">
  Words it is found by.
</ParamField>

<ParamField body name="widthFraction" type="number or null">
  Its default width, a fraction of the canvas's.
</ParamField>

### `update_template`

<Badge>write</Badge>

Change one of the user's own templates: only the fields given. ContentHero's templates are read-only; copy one with create\_template fromTemplateId. Pass the version you read as expectedVersion to refuse a write over a change made since. Clips already placed from it keep what they had. Requires the editor:write scope.

<ParamField body name="templateId" type="string" required>
  The template id.
</ParamField>

<ParamField body name="afterId" type="string">
  Place it immediately after this item of the same list.
</ParamField>

<ParamField body name="aspect" type="number or null">
  Its own width over height, for resize scale.
</ParamField>

<ParamField body name="beforeId" type="string">
  Place it immediately before this item of the same list.
</ParamField>

<ParamField body name="category" type="string">
  Its category: one list\_templates shows, or a new one.
</ParamField>

<ParamField body name="code" type="string">
  The code: a React component, as get\_schema (kind 'code') teaches.
</ParamField>

<ParamField body name="coverage" type="`full` \| `partial`">
  full: the whole frame; partial: a box of widthFraction by heightFraction.
</ParamField>

<ParamField body name="description" type="string or null">
  What it is for.
</ParamField>

<ParamField body name="durationFrames" type="integer">
  How long it lasts when placed, in frames.
</ParamField>

<ParamField body name="emoji" type="string">
  An animated emoji's name, as the animated emoji list names it.
</ParamField>

<ParamField body name="expectedVersion" type="integer">
  The version you read; the write is refused if the template changed since.
</ParamField>

<ParamField body name="heightFraction" type="number or null">
  Its default height, a fraction of the canvas's.
</ParamField>

<ParamField body name="lottie" type="object">
  A Lottie file of ours, and the colors in it that props recolor (each role names a prop).
</ParamField>

<ParamField body name="name" type="string">
  Its name in the Elements panel.
</ParamField>

<ParamField body name="position" type="`top` \| `bottom`">
  Place it at an end of the list instead of beside an item. Name neighbors or a position, not both.
</ParamField>

<ParamField body name="props" type="object">
  Its props as placed.
</ParamField>

<ParamField body name="propsSchema" type="object or null">
  Each prop's control, as get\_template shows them; brand names the brand value a prop takes when placed.
</ParamField>

<ParamField body name="resize" type="`scale` \| `reflow`">
  scale keeps its proportions in any box; reflow lays out again in the box it is given.
</ParamField>

<ParamField body name="shape" type="string">
  A shape's name, as get\_schema (kind 'layer') lists them.
</ParamField>

<ParamField body name="tags" type="string[]">
  Words it is found by.
</ParamField>

<ParamField body name="widthFraction" type="number or null">
  Its default width, a fraction of the canvas's.
</ParamField>

### `delete_template`

<Badge>write</Badge>

Delete one of the user's own templates. Clips placed from it keep everything. To hide one and keep it, archive it instead (archive, assetType template). Requires the editor:write scope.

<ParamField body name="templateId" type="string" required>
  The template id.
</ParamField>


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