Skip to content

Editing the video

How to edit a video through the Slicer API — word ids in the video text, one operation catalog, elements by time, revisions, captions from speech, AI at cost.

On this page

A finished video or the draft of a project without the AI director can be edited just like in the app's editor: cut words and pauses, split, trim, reorder and duplicate fragments, add a freeze frame, fix captions, add a hook, plaques, titles, emoji, zooms and markers, change speed (and the speed curve), sound, transitions, effects and colour, framing, mask, chroma key and keyframes, set music and voice, put your own photos and videos over the video — full frame or in a window, change the cover and the frame format. Edits are free and show up in the editor right away; the video file is updated by export. Only Slicer's AI features are paid: captions from speech, the AI assistant and caption translation, all at cost. Your agent can also edit the whole video itself, for free: it writes the plan, and Slicer applies the rules of its AI director to it — Your own director.

How edits work

  1. Get the video as text

    GET /v1/projects/{id}/transcript returns the video as text: fragments in order, words with id inside them, marks for cut and highlighted words, and elements — placed on words or by video time.

    Response (shortened)
    {
      "rev": 7,
      "durationSec": 31.4,
      "text": "…",
      "blocks": [
        {
          "id": "b1",
          "position": 0,
          "sourceIndex": 0,
          "kind": "words",
          "words": [
            { "id": 12, "text": "Today", "cut": false, "accent": false },
            { "id": 13, "text": "I'll", "cut": false, "accent": false },
            { "id": 14, "text": "um", "cut": true, "accent": false },
            { "id": 15, "text": "show", "cut": false, "accent": true }
          ]
        },
        { "id": "b2", "position": 1, "sourceIndex": 2, "kind": "range", "words": [] }
      ],
      "elements": {
        "hook": "A reel in 3 minutes",
        "plaques": [],
        "cards": [],
        "emoji": [{ "id": "e1", "emoji": "🚀", "wordId": 15, "start": 1.2, "end": 2.4 }],
        "zooms": [{ "id": "z1", "fromWordId": 12, "toWordId": 15, "start": 0, "end": 2.4 }],
        "titles": [
          {
            "id": "t1", "text": "Step 1", "font": "punch", "size": 96, "color": "#FFFFFF", "background": "pill",
            "align": "center", "x": 540, "y": 420, "fromWordId": null, "toWordId": null,
            "start": 8, "end": 11, "anchor": { "blockId": "b2", "at": 3.5, "duration": 3 }
          }
        ],
        "markers": [{ "id": "m1", "time": 12.5, "label": "Music here", "color": "yellow" }]
      }
    }
    • kind: "words" is a speech fragment with words; kind: "range" is a fragment by source time: b-roll, a photo or a clip without recognized speech. It may have no words.
    • start and end of an element are where it sits in the video, in seconds. No start — the element isn't visible right now (its words are cut).
    • An element placed by time rather than on words comes with fromWordId: null and an anchor — the fragment, the second of its source and the duration (Elements by time).
    • markers are the author's notes on the timeline; they don't appear in the video.

    The text field is the same video as a single string in a form convenient for a language model: you can pass it to your own AI agent so it can suggest edits. Fragments without speech are shown in it with their time in the video.

  2. Send operations

    POST /v1/projects/{id}/edits with the rev from step 1 and a list of operations (up to 80 at a time):

    Request body
    {
      "rev": 7,
      "operations": [
        { "op": "cut_fillers" },
        { "op": "set_hook", "text": "A reel in 3 minutes" },
        { "op": "set_accent", "wordId": 18 },
        { "op": "set_emoji", "wordId": 18, "emoji": "🔥" },
        { "op": "add_plaque", "fromWordId": 20, "toWordId": 26, "kind": "list", "text": "Three steps", "caption": "Upload\nPick a style\nExport" },
        { "op": "add_title", "start": 8, "duration": 3, "text": "Step 1", "titleBackground": "pill", "titleIn": "pop" },
        { "op": "tune_block", "id": "b3", "transition": { "kind": "dissolve", "duration": 0.4 }, "animIn": { "kind": "zoomIn", "duration": 0.5 } }
      ]
    }
    Response
    { "ok": true, "data": { "rev": 8, "applied": 7, "skipped": [], "durationSec": 29.8 } }
  3. Export when you're done

    Edits are saved immediately. To get an updated file, run an export — it's free.

Operations

Slicer has a single operation catalog: the same operations are used by manual edits in the editor, the AI assistant, the director in the Chat, the API and the edit_project MCP tool. So the result matches what you'd get in the editor.

Words are addressed by their id from the video text, fragments and elements by their own id. A word range is fromWordId…toWordId, inclusive; fragment positions start at 0.

Speech and fragments

Operation Fields What it does
cut_wordsfromWordId, toWordIdcut words out of the video
restore_wordsfromWordId, toWordIdbring back cut or unused source words
cut_fillers—cut filler words
cut_silencesminPause, blockIdsremove pauses by silence, free and without speech recognition: splits fragments by time (kind: "range") at pauses longer than minPause — 0.3, 0.5 (default) or 1 second; without blockIds — in all such fragments
add_sourcesourceIndex, position, srcStart, srcEndput a project source into the video as a new fragment: without srcStart/srcEnd — its speech by words or the whole source; without position — at the end
remove_blockiddelete a fragment together with the elements placed on it by time
move_blockid, positionmove a fragment
slip_blockid, deltashow another piece of the same source in a b-roll fragment: delta in seconds, + — later footage; the fragment's length and place stay the same
split_blockid, atsplit a fragment in two at the video moment at (s) inside it; both parts keep its settings
trim_blockid, edge, deltatrim or extend a fragment: edge start or end, delta — how far to move the edge, in seconds of the video (+ — later, − — earlier), −60…60. start +1 cuts the first second, end +1 adds a second of the source
duplicate_blockida copy of the fragment right after it
freeze_frameid, at, durationa freeze frame: a new silent fragment with a held frame. at — a video moment inside the fragment (default — its last frame), duration — 0.3–10 s (default 2)
set_holdid, durationthe length of a freeze frame, 0.3–10 s
replace_sourceid, sourceIndex, srcStartshow another source in a fragment from second srcStart (default 0): in b-roll the picture is replaced in place, the length stays; in a fragment with speech the speech stays and the new picture goes over it as an insert

Fragment and video settings

Operation Fields What it does
tune_blockid, speed, volume, fadeIn, fadeOut, effect, transition, filter, adjust, lut, animIn, animOut, backgroundspeed, volume, sound fade-in and fade-out, effect, transition into this fragment, color — filter, Adjust and LUT, entrance and exit animations, the background behind a fitted or masked picture — blur (a blurred frame) or a colour #RRGGBB; null resets a field
set_processid, processfree fragment processing — a list of reverse, stabilize, denoise (noise reduction), enhance (audio enhancement); replaces these four, an empty list — no processing. The server prepares the result in the background, usually within a minute
apply_keyframe_presetid, preseta smooth camera move across the whole fragment by keyframes: zoomIn, zoomOut, panLeft, panRight, panUp, panDown; replaces the fragment's keyframes
apply_lookid, blockIds, partscopy the look of fragment id to others: without blockIds — to all other fragments; parts — what to copy: speed, audio, color, effect, transition, animations, mask, background, framing (default — everything except framing)
set_all_transitionstransitionone transition on every cut (null — remove all)
set_all_effectseffectone effect on every fragment (null — remove all)
set_all_colorfilter, adjust, lutone color on every fragment: filter, Adjust and LUT; whatever the request doesn't include is removed (filter: null alone removes all color)
set_formatformatframe format: 9:16, 4:5, 1:1, 16:9
set_coverstart, coverTitle, coverStyle, coverIntrothe cover: the frame (a moment of the video, s), a title on it up to 60 characters (null — no title), the title look hook or plaque, and whether to show the cover as the first frame

Frame, mask, speed and sound over time

Since API 1.5.0.

Operation Fields What it does
set_framingid, mode, cx, cy, zoomfragment framing: auto — automatic, crop — fill the frame (window centre cx, cy — shares of the source, 0–1), fit — the whole picture with a background, track — the frame follows the face; zoom — 1–3. track works only if faces in this part were already found — by the AI montage or by “Follow the face” in the editor; otherwise the operation is skipped
set_keyframeid, t, x, y, scale, rotate, opacity, easea keyframe t seconds from the fragment start: shift x, y (shares of the frame, −1…1), scale 0.2–4, clockwise rotation −180…180°, opacity 0–1 (below 1 — fades to black), easing to the next keyframe smooth (default), linear, in (speeds up), out (slows down). A keyframe at that moment is changed; fields not in the request take the frame's look at t. Up to 24 per fragment; a move needs two keyframes
remove_keyframeid, tremove the keyframe at t
set_maskid, shape, size, feather, cx, cy, rotate, invert, maskKeysthe mask: shape circle, rounded, heart, stripe (none — remove), size 0.2–1, feather 0–1, centre shift cx, cy — −0.5…0.5 shares of the frame from the centre, rotation in degrees, invert — video outside the shape, background inside; maskKeys — animation [{ "t", "cx", "cy", "size", "rotate" }], up to 24 points (null — no animation). Fields not in the request stay as they were
set_chromakeyid, keyColor, similarity, blend, spill, keyBackground, keyImagechroma key, free: the screen colour #rrggbb (null — off), similarity 0.05–0.5 (0.15 by default), edge softness 0–0.3 (0.1), spill — remove the coloured fringe (true by default); behind — the colour keyBackground or a project photo keyImage (an image upload id, null — colour)
set_speed_curveid, preset or speedPointsa speed curve of a footage fragment without speech: a ready one — montage, hero, bullet, flash, rampUp, rampDown — or your own [{ "t", "speed" }]: 2–8 points, t — share of the fragment 0–1, speed 0.1–8, linear in between. The fragment's sound is muted on a curve; preset: null — normal speed
set_smoothid, onsmooth slow motion of a footage fragment slower than 1× (a source piece up to 10 s): the server interpolates frames in the background, free
set_volume_keysid, volumeKeysfragment volume by points [{ "t", "v" }]: t — seconds from the fragment start, v — 0–1, linear in between, up to 24 points; replaces the constant volume, null — constant again
A speed ramp with smooth slow motion and a smooth frame turn
[
  { "op": "set_speed_curve", "id": "b4", "preset": "bullet" },
  { "op": "set_smooth", "id": "b4", "on": true },
  { "op": "set_keyframe", "id": "b5", "t": 0, "rotate": 0 },
  { "op": "set_keyframe", "id": "b5", "t": 2.5, "rotate": 12, "scale": 1.2 }
]

Captions

Operation Fields What it does
set_textwordId, textfix a caption word (up to 200 characters)
set_accent, clear_accentwordIdhighlight a word with the accent color or remove the highlight
set_caption_positionplacementcaption position: top, center, bottom — like a manual move in the editor: every phrase goes to this height, and smart Auto doesn't move them (style)
set_phrase_textfromWordId, toWordId, textrewrite the caption text of a whole phrase — typos, names, numbers; the speech doesn't change
reset_phrase_textfromWordId, toWordIdthe caption text of these words — back to the recognized speech

Elements

Elements are placed on words (fromWordId, toWordId or wordId) or — where there is no speech — by video time (start, duration).

Operation Fields What it does
set_hook, remove_hooktext (up to 120 characters)a catchy title for the first seconds
add_plaque, remove_plaquewords or time, text, caption, kind / ida plaque: a title up to 60 characters and lines separated by \n; kind — title, number, list, quote, name
add_title, update_title, remove_titlewords or time, text and look / ida title — a free text layer, up to 3 lines separated by \n (up to 200 characters); its look is described below
set_emoji, remove_emojiwordId or start + duration, emoji / id or wordIdan emoji over the video; only from Slicer's set (GET /v1/styles → emoji)
add_zoom, remove_zoomwords or time, scale, motion / ida camera zoom: scale 1.05–1.6 (default 1.3), motion push — a smooth push-in (default), pull — a pull-out, punch — a sharp jump
add_card, remove_cardwords or time, text, caption / ida media card: text — an image description in English, caption — a label
add_marker, update_marker, remove_markerwordId or start, label, color / ida marker — a note for the author on the timeline, up to 40 characters, not shown in the video; color — cyan, yellow, red, green, violet
add_sound, update_sound, remove_soundwordId or start, kind / id, kind / ida sound at a moment of the video: whoosh, pop, ding, click, swipe, rise, boom, chime; it moves with its fragment and plays even with sound effects turned off; up to 60 sounds per video. To move a sound — move_element with element: "sound"
update_plaqueid, text, caption, kindchange a plaque's title, lines or kind in place
update_cardid, caption, uploadIda media card: the label ("" — none) and your own photo from the project media instead of the picture from a description, free (null — back to the description)
update_zoomid, scale, motionchange a zoom's scale and motion in place
move_elementelement, id, start, durationmove an element to another moment of the video: element — plaque, card, zoom, emoji, insert, title, sound or marker; over speech it sticks to the words there
set_element_positionelement, id, x, ymove an emoji, card or plaque (element — emoji, card, plaque) around the frame: its centre in the 1080×1920 frame; null — back to the default place

Music and voice

Since API 1.7.0. A track or a voice recording is a project source (sourceIndex) or a file from the project media (uploadId — your own files added to the project in the editor). Voice recording ids are in the edit document, the voices list.

Operation Fields What it does
set_musicsourceIndex or uploadId, volume, sourceVolume, ducking, offset, fadeIn, fadeOutbackground music: an audio source or audio from the project media; without sourceIndex and uploadId — change the current music. Music volume 0–1 (usually 0.2–0.35), sourceVolume — fragment sound under the music 0–1, ducking — music gets quieter under speech, offset — the track second to start from, fade-in and fade-out — 0–5 s
remove_music—remove the background music
fit_musicontrue — fit the track to the video length: it ends together with the video on a bar line; false — play it as is
fit_to_beatseverysnap the cuts of footage fragments to the music beats: every beat (1, default), every 2nd or 4th
add_voicesourceIndex or uploadId, start, volumea voice recording or an audio source on the voice track from video second start; volume 0–2 (default 1). Fragment sound and music get quieter under the voice
move_voiceid, startmove a recording to another moment of the video
trim_voiceid, edge, attrim a recording: edge start or end and its new time in the video at (s)
set_voice_volumeid, volumerecording volume, 0–2
remove_voiceidremove a recording from the track
A quieter track from the sources, fitted to the length, cuts on every second beat
[
  { "op": "set_music", "sourceIndex": 3, "volume": 0.25, "ducking": true, "fadeOut": 1.5 },
  { "op": "fit_music", "on": true },
  { "op": "fit_to_beats", "every": 2 }
]

Your photos and videos over the video

Since API 1.7.0. An insert covers the picture with a photo or video while the speech goes on — for example, to show what the author is talking about. It is placed on words or by time, like elements; insert ids are in the edit document, the inserts list.

Operation Fields What it does
add_insertsourceIndex or uploadId, fromWordId + toWordId or start + duration, fit, srcStart, modea photo or video from the sources or project media over the video: fit — cover (fills the frame) or contain (the whole picture), srcStart — for a video, from this second, mode — full (full frame, default) or pip (in a window; the window look — set_pip)
update_insertid, fit, srcStartthe insert's fit and the start of its video
remove_insertidremove an insert
A photo of the result in a window over a phrase
{ "op": "add_insert", "sourceIndex": 2, "fromWordId": 40, "toWordId": 52, "mode": "pip" }

Picture in picture

Since API 1.6.0. An insert — your own photo or video over the video (its id is in the edit document, the inserts list) — can cover the whole frame or sit in a window.

Operation Fields What it does
set_pipid, mode, x, y, scale, rotate, opacity, shape, radius, borderColor, borderWidth, volumemode: "pip" (default) — the insert as a window over the video, "full" — back to full frame. Window centre x, y — in the 1080×1920 frame (may be partly past the edge), width scale — share of the frame width 0.1–1, clockwise rotation −180…180°, opacity 0–1, shape rect or circle, corner rounding radius — share of the shorter side 0–0.5, border — colour #rrggbb (null — no border) and width 0–0.1, volume — sound of the video in the window 0–1 (default 0). Fields not in the request stay as they were; for a window with keyframes the position is changed in the first keyframe. Up to 10 windows per video: an 11th is skipped
An insert as a round window with a white border, bottom right
{ "op": "set_pip", "id": "i4k2m9q", "x": 800, "y": 1500, "scale": 0.4, "shape": "circle", "borderColor": "#ffffff", "borderWidth": 0.03 }

Window keyframes are set in the editor; in the edit document they are inserts[].pip.keyframes.

Values

  • speed — 0.25 to 4; volume — 0 to 1; fadeIn, fadeOut — 0 to 3 seconds;
  • effect — shake, pulse, flash, cinema, vignette, grain, blur, glitch, vhs;
  • transition — { "kind", "duration" }, duration — 0.2 to 1.2 seconds; kind — fade, flash, zoom, dissolve, blur, slides slide, slideRight, slideUp, slideDown, wipes wipe, wipeRight, wipeUp, wipeDown, circle circleOpen and circleClose, zoomIn, pixelize, radial, scatter;
  • filter — { "id", "strength" }, filter — bw, warm, cool, vintage, sepia, vivid, pastel, noir, cinema, strength — 0 to 1;
  • adjust — Adjust: exposure, brightness, contrast, saturation, temperature, tint, each from −1 to 1 (0.15 is a noticeable step); replaces all of the fragment's sliders at once;
  • lut — { "preset", "strength" } or { "uploadId", "strength" }: a built-in LUT — teal-orange, film, matte, bleach, golden, moody, nordic, cross, mono-film, vibrant; uploadId — your own .cube from My LUTs (its id is in the edit document on a fragment that uses it); strength — 0 to 1;
  • animIn, animOut — { "kind", "duration" }, kind — fade, zoomIn, zoomOut, slideLeft, slideRight, slideUp, slideDown, duration — 0.2 to 2 seconds.

A title's look uses fields with the title prefix; in update_title, null resets a field:

Field Value
titleFonta font from GET /v1/styles → fonts
titleSizefont size, 24–200 px on a 1080×1920 frame
titleColor, titleBackgroundColortext and background colour, #RRGGBB
titleBackgroundbackground: none, box (per line), pill (rounded)
titleOutline, titleShadowletter outline and shadow, true / false
titleAlignalignment: left, center, right
titleX, titleYposition on a 1080×1920 frame: the horizontal anchor (edge or centre, by alignment) and the vertical centre
titleIn, titleOutentrance fade, slide, pop, typewriter (letter by letter) and exit fade, slide, pop

The full operation schema:

EditOperation

Field Type Description

Elements by time

A video without speech — b-roll, photos, a draft without recognized speech — has no words, so plaques, titles, emoji, zooms, cards and markers are placed by time: start is a second of the video, duration is the length in seconds of the video (0.3–60).

A title and a zoom by time
{ "op": "add_title", "start": 4, "duration": 2.5, "text": "Morning at the coffee shop", "titleIn": "typewriter" }
{ "op": "add_zoom", "start": 9, "duration": 2, "scale": 1.2, "motion": "pull" }

An element holds on to the fragment it sits over, like a connected clip: move the fragment — the element moves with it; delete the fragment — the element goes too. Over speech, prefer placing elements on words: that placement survives text edits. If an operation has both words and time, the words win.

Skipped operations

Operations are applied in order. If an operation changes nothing — the word isn't found, the fragment doesn't exist, the emoji isn't in the set — it's skipped and the rest are applied. Skipped operations are listed in skipped:

Response with skips
{ "rev": 9, "applied": 2, "skipped": [{ "index": 1, "op": "remove_plaque", "reason": "element_not_found" }], "durationSec": 29.8 }
reason Why
word_not_found_or_nothing_to_changeno such word, or the edit is already applied
words_or_start_requiredthe element has neither words nor start, there are no such words, or there is no fragment at that moment of the video
block_not_foundno fragment with this id
source_not_foundno source with this index
element_not_foundno element or fragment with this id
emoji_not_allowed_or_word_not_foundthe emoji isn't in Slicer's set, or no such word
bad_argumentsthe operation's fields failed validation
unknown_operationthere is no such operation
block_not_footagea speed curve and smooth slow motion work only on a time-based footage fragment, without speech and not a freeze frame
keyframe_not_foundthere is no keyframe at that moment of the fragment
block_not_found_or_no_face_datano fragment with this id, or for track no faces have been found in this part yet
block_or_source_not_foundno fragment with this id, or no suitable source
audio_not_foundno such audio in the sources or project media
no_music_or_beatsthe video has no music, or the track's beats haven't been found yet
recording_not_foundno such voice recording or audio
media_or_place_not_foundno such photo or video, or no place for the insert: no such words, or no fragment at that moment of the video
word_not_foundno such word
nothing_to_changethe operation changes nothing

Revisions

A video has a revision number rev; every saved edit increases it. An edit with an outdated revision is rejected with 409 stale_document — the video was changed in the editor or by another request. Re-read the video text and repeat the edit with the new rev. This way edits from the API and the app never overwrite each other.

Captions from speech

In a project without the AI director the speech isn't recognised, so there are no captions, and the fragments in the video text are by time. POST /v1/projects/{id}/transcribe recognises the speech in the sources — like Captions from speech in the editor (Captions from speech). It also recognises sources added to a project after the montage.

Terminal
curl https://api.slicer.media/v1/projects/318/transcribe \
  -H "Authorization: Bearer $SLICER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"language":"auto"}'
Field What it sets
sourcessource indexes; by default — every source with sound whose speech isn't recognised yet
languagethe speech language: a two-letter code or auto — the language from the project settings, or, if none is set, Slicer detects it
confirmCostthe price you agree to; without it the response is a Quote and nothing starts

Recognition is priced at cost per minute of sound of the chosen sources: without confirmCost you get a Quote with estimate (the expected price) and cost (the maximum), atCost: true — as with the AI assistant. Repeat the request with "confirmCost" equal to cost: the tokens are reserved, and the actual cost is charged, never more than cost. Requires the montage:run scope.

Recognition runs in the background. The response to the start is a job { id, status, sources, createdAt }; follow it with GET /v1/projects/{id}/transcribe, the project's job list:

status Meaning
processingthe speech is being recognised
readydone: the words are in the video text, the captions follow the speech
failedit didn't work, error has a readable message; the reserved tokens came back

When the job is ready, re-read GET /v1/projects/{id}/transcript: the fragments now have words with ids, you can place elements on them and edit captions (set_text, set_accent, set_phrase_text). The edit document itself doesn't change. POST /v1/estimate with kind: "transcribe" and projectId names the price in advance.

Response Meaning
400 nothing_to_transcribethe chosen sources have no sound or their speech is already recognised
409 busyspeech in the project is already being recognised — wait until it's done
409 not_readythe project isn't ready for edits yet
409 sources_expiredthe sources were deleted after the retention period
503 ai_unavailablespeech recognition is temporarily unavailable

AI assistant

A command in plain words — the assistant picks the operations from the same catalog itself:

Terminal
curl https://api.slicer.media/v1/projects/318/assistant \
  -H "Authorization: Bearer $SLICER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"command":"Make the video more dynamic and add emoji on the key words","rev":9}'

The assistant is priced at cost: a command costs exactly what its AI calls cost, with no markup. Without confirmCost you get the price:

Response without confirmCost
{ "ok": true, "data": { "status": "quote", "cost": 2, "free": false, "estimate": 0.4, "atCost": true, "available": 240, "enough": true, "spendLimitLeft": 176 } }
  • atCost: true — the operation is priced at cost;
  • estimate — the expected price in tokens, fractional;
  • cost — the maximum price: repeat the request with "confirmCost" equal to it. The actual cost is charged, never more than cost.

With confirmCost the response is { rev, summary, applied, charged, costTokens, durationSec }: summary describes what was done, costTokens is the actual cost of the command (fractional), charged is how many whole tokens were taken from the balance now. The fractional remainder accumulates with the project and is charged once it adds up to a whole token. Tokens are charged only if the assistant changed something. Requires the montage:run scope.

Caption translation

POST /v1/projects/{id}/translate with language — en, es, pt, de, fr, it, pl, tr, id, uk, ru — and rev. The translation is laid out word by word, so accents and emoji stay in place. Translation is priced at cost too: the price (Quote with estimate and cost) and confirmation work as for the assistant. The response is { rev, phrases, charged, costTokens }; you're charged only if the translation succeeded.

Auto color

POST /v1/projects/{id}/auto-color balances color without AI and for free — from 10 frames of the fragment, like the Auto and Match all to this buttons in the editor. The body has rev, blockId and mode:

  • auto — correct fragment blockId: exposure, contrast, saturation and white balance;
  • match — make the other fragments look like blockId: they get its filter and LUT, and each gets its own Adjust values.
Body of POST /v1/projects/318/auto-color
{ "rev": 7, "blockId": "b2", "mode": "match" }

The result is saved right away. The response has the new revision, Adjust values by fragment id (adjust), how many fragments changed (applied) and the video's duration.

Your own director

Your AI agent can edit the video itself — on its side, for free in Slicer: it looks at the frames and writes the plan, and Slicer applies the rules of its AI director to the plan on the server and answers with fixes and issues. The rules themselves are not passed to the agent. How it works step by step — Your own director in the MCP section: the get_director_brief, get_frames and submit_director_plan tools are built on these routes.

Route What it does Scope
POST /v1/projects/{id}/director/briefthe material — speech by word ids, pauses, retakes, wishes (material), the sources (sources), general tips (guide), the plan schema (planSchema), the schema of observations from the frames (scenesSchema) and revprojects:write
GET /v1/projects/{id}/sources/{index}/framesframes of a source: JPEG in base64, 512 px on the longer side, up to 16 per requestprojects:read
POST /v1/projects/{id}/director/plana plan (plan) and observations from the frames (scenes) → the video, a new revision; the answer has fixes (fixes) and issues (issues)projects:write

All three are free. If the video has speech, recognise it first — Captions from speech (at cost): without words the director has nothing to cut.

Brief

The body is optional: director settings — mode (auto, talking, story, product, aesthetic), pace (calm, balanced, dynamic), effects (off, light, bold), targetDurationSec (10–180, null — the director decides, up to 90 seconds), hook, plaques, emoji, cameraMotion. Fields you pass are saved to the project: Slicer applies the rules to the plan by them.

Terminal
curl https://api.slicer.media/v1/projects/318/director/brief \
  -H "Authorization: Bearer $SLICER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pace":"dynamic","targetDurationSec":30}'
Response field What it holds
projectId, revthe project and the video revision — rev is needed for the plan
settingsthe project's director settings
sourcesthe sources: number (index), kind (kind), duration, file name and how many recognised words it has
speechRecognizedwhether the speech is recognised
materialthe material as text: the sources, speech by word ids w<ID>, pauses, [RETAKE …] takes, the author's wishes
guidea few general tips — the brief has no rules of the Slicer AI director
planSchemathe JSON schema of the plan
scenesSchemathe JSON schema of observations from the frames
nextwhat to do next

Frames

times — seconds of the source, comma-separated (up to 16), or evenly: from, to, count (8 frames over the whole source by default). Seconds outside the source are clamped to its edge. A photo returns one image; audio has no frames (400 no_picture), a missing source — 404 source_not_found.

Terminal
curl "https://api.slicer.media/v1/projects/318/sources/0/frames?times=2.5,7,12.4" \
  -H "Authorization: Bearer $SLICER_TOKEN"
Response
{ "ok": true, "data": { "sourceIndex": 0, "durationSec": 48.2, "frames": [{ "time": 2.5, "mimeType": "image/jpeg", "data": "/9j/4AAQ…" }] } }

Plan

The body has rev (from the brief or from the previous answer), plan in planSchema and, optionally, scenes — up to 400 observations in scenesSchema: sourceIndex, start and end in seconds of the source, kind (talking_head, demo, people, screen, broll, unusable), interest from 1 to 5 and description. What the kinds mean and how Slicer places footage by them — Observations from the frames.

Body of POST /v1/projects/318/director/plan (abridged)
{
  "rev": 3,
  "plan": {
    "concept": "Three steps from milk to pattern",
    "segments": [
      { "kind": "speech", "role": "hook", "fromWordId": 41, "toWordId": 47, "why": "A strong opening" },
      { "kind": "footage", "role": "body", "sourceIndex": 0, "fromSec": 14.5, "toSec": 19, "why": "Showing the pattern" }
    ],
    "hook": "Latte art in a minute"
  },
  "scenes": [
    { "sourceIndex": 0, "start": 0, "end": 14.5, "kind": "talking_head", "interest": 3, "description": "The barista talks at the counter" },
    { "sourceIndex": 0, "start": 14.5, "end": 21, "kind": "demo", "interest": 5, "description": "Draws a heart on a latte" }
  ]
}

On the server the rules of the Slicer director are applied to the plan: speech by words, footage by the observations, restrained editing, length. The plan becomes the video right away — a new revision, even if it has issues. Media cards from a description are not accepted — their picture is paid; the update_card operation sets your own photo. Fragments are framed in the centre; fix the framing with the set_framing operation.

Response Meaning
200{ rev, durationSec, planSegments, fragments, fixes, issues, video } — the video is saved: fixes — what Slicer corrected itself, issues — plan issues, video — the video as text to check
400 bad_planthe plan doesn't match the schema; message lists the first errors
400 empty_planthe plan has no fragments
409 stale_documentthe video changed after the brief or the previous plan — re-read it and send the plan with the new rev
409 busy / 409 not_ready / 409 sources_expiredthe project is rendering, isn't ready for edits yet, or its sources were deleted

Plan issues

An issue is { code, segment, message }: a code, the plan fragment number from zero (only if the issue is about a fragment) and what to fix — in English, for the model to read. Fix the plan and send the whole plan again with the new rev; when issues is empty, the plan is fine.

Code What's wrong
cuts_thoughtthe fragment stops in the middle of a sentence
starts_mid_thoughtthe fragment starts in the middle of a sentence
single_wordthe fragment is a single word — make it a complete thought or remove it
two_takesthe video has two takes of the same thought — keep the best one
weak_endingthe video ends mid-phrase rather than on a complete sentence
too_longthe video is noticeably longer than the target length or, without one, longer than 90 seconds
too_shortthe video is noticeably shorter than the target length
unused_sourcea source without speech — a video or a photo — isn't used in the plan: show it where it belongs in the story
footage_is_talking_headthe footage shows a silent talking head instead of an action, a product or a result (by the observations)
unusable_footagethe footage comes from a part marked unusable in the observations
nothing_shownthe observations have demo moments or b-roll, but the plan has neither footage nor cutaways
hook_longthe hook is longer than 8 seconds
hook_title_longthe hook title is longer than 6 words

Style

PATCH /v1/projects/{id}/style changes the template, font, accent color, caption look, sound effects and intro zoom — for free. The change is partial: only the fields you pass change, everything else — including the other fields of subtitles and params — stays as it was.

Body of PATCH /v1/projects/318/style
{ "templateId": "clean", "fontId": "editorial", "colorId": "mint", "subtitles": { "design": "karaoke", "size": "large" } }

The new style shows in the editor right away and in the file after export. To have the AI rebuild the video with different montage settings, use a rebuild.

Caption look

The subtitles fields are the same as in the editor's Caption style sheet (Captions). Word highlight, entrance, words on screen, letter case and colour by speaker — since API 1.8.0.

Field Values
enabledshow captions: true or false
designthe design: dynamic, karaoke, classic, minimal, pop — Pop, box — Box, word — One word
sizethe size: small, medium, large
positionthe position: auto — each phrase where the frame has the most free space: off the face, hook, plaques, titles and picture-in-picture windows; top, center, bottom — every phrase in one place
speakerColorseach speaker's words in their own color: true or false
highlightthe spoken-word highlight: color — the accent color, pop — the word grows while the others make room, box — an accent box, underline — an underline, none — no highlight
animationthe phrase entrance: fade, pop — a springy pop, slide — from below, words — words as they are spoken, none — at once
wordswords on screen: one, few — 2–3, line — a line of up to six words, phrase — a phrase of up to two lines
letterCaseletter case: upper — capitals, normal — as spoken

highlight, animation, words and letterCase also take auto, the default: the way the design intends (for letterCase — the font). The templates are bold, clean and pop.

The spoken word pops — the rest of the style stays the same
{ "subtitles": { "highlight": "pop" } }

How a subtitles change behaves:

  • only the fields you pass change, the others stay as they were. Before API 1.8.0 a partial subtitles change reset the other fields to their defaults;
  • a new design without highlight, animation and words resets them to auto — like picking a design in the editor: the design looks the way it is meant to right away. Size, position, letter case and colour by speaker stay. To change the design and set the highlight at once, pass both fields:
Karaoke where the spoken word grows
{ "subtitles": { "design": "karaoke", "highlight": "pop" } }

Names and descriptions of the values in both languages are in the GET /v1/styles catalog: designs — subtitleDesigns, positions — subtitlePositions, word highlight, entrance, words on screen and letter case — subtitleOptions:

GET /v1/styles → subtitleOptions (abridged)
[
  {
    "id": "highlight",
    "title": { "ru": "Выделение слова", "en": "Word highlight" },
    "help": { "ru": "Как выделяется слово, которое сейчас звучит.", "en": "How the word being spoken is highlighted." },
    "values": [
      { "id": "auto", "title": { "ru": "Как в оформлении", "en": "As in the style" }, "description": { "ru": "Так, как задумано выбранным оформлением.", "en": "As the chosen style intends." } },
      { "id": "pop", "title": { "ru": "Увеличение", "en": "Pop" }, "description": { "ru": "Слово, которое звучит, увеличивается и подсвечивается, соседние слова плавно расступаются.", "en": "The spoken word grows and lights up, the others smoothly make room." } }
    ]
  }
]

The same fields are accepted by settings.subtitles when you create a project (Video settings) and by the set_style MCP tool.

The edit document (advanced)

GET /v1/projects/{id}/document returns the whole edit document — the complete description of the video that the preview and the render are built from — with its revision. A PUT to the same path with { rev, document } replaces it entirely. The document goes through the same checks as edits from the editor; unknown fields are dropped.

Example: remove fillers and add a hook

JavaScript
Python
polish.mjs
// node polish.mjs 318
const API = 'https://api.slicer.media/v1';
const headers = { Authorization: `Bearer ${process.env.SLICER_TOKEN}`, 'Content-Type': 'application/json' };
const id = process.argv[2];

async function call(method, path, body) {
  const response = await fetch(`${API}${path}`, { method, headers, body: body && JSON.stringify(body) });
  const json = await response.json();
  if (!json.ok) throw Object.assign(new Error(json.error.message), { code: json.error.code });
  return json.data;
}

for (let attempt = 0; attempt < 3; attempt += 1) {
  const { rev, blocks } = await call('GET', `/projects/${id}/transcript`);
  const words = blocks.flatMap((block) => block.words).filter((word) => !word.cut);
  try {
    const result = await call('POST', `/projects/${id}/edits`, {
      rev,
      operations: [
        { op: 'cut_fillers' },
        { op: 'set_hook', text: 'A reel in 3 minutes' },
        { op: 'set_accent', wordId: words[0].id },
      ],
    });
    console.log(`Applied: ${result.applied}, length: ${result.durationSec} s`);
    break;
  } catch (error) {
    if (error.code !== 'stale_document') throw error; // the video changed — re-read and retry
  }
}

What's next