# Montage and pricing

> How to run an AI montage through the Slicer API — the price and confirmCost, progress, video versions, rebuild and a free project without the AI director.

Source: https://docs.slicer.media/en/api/montage/

Montage is Slicer's main operation: the AI director watches the materials, picks the best moments, places captions, emoji, plaques and camera motion, and assembles a vertical video. Through the API a montage starts with a single `POST /v1/projects` request — with mandatory price confirmation. If you build the video yourself, create a project without the AI director — it's free.

## Video settings

All settings are optional: anything missing falls back to the defaults, just like in the app. Allowed values and their descriptions in both languages are in the catalog `GET /v1/styles`.

Body of POST /v1/projects:

```json
{
  "uploadIds": ["7d0c6a4e-2f7b-4f5e-9a0e-3b8f2c1d9e61", "a1b2c3d4-0000-4c1e-8f00-5e6f7a8b9c0d"],
  "title": "New collection overview",
  "brief": "Open with the brightest look, end with a call to subscribe",
  "settings": {
    "templateId": "bold",
    "fontId": "punch",
    "colorId": "azure",
    "subtitles": { "enabled": true, "design": "dynamic", "size": "medium", "position": "auto" },
    "params": { "emoji": true, "soundEffects": true, "mediaCards": false, "plaques": true, "cameraMotion": true, "hook": true },
    "smartCut": true,
    "targetDurationSec": 30,
    "language": "auto",
    "direction": { "mode": "product", "pace": "dynamic", "effects": "light", "narration": false }
  }
}
```

| Field | What it sets |
| --- | --- |
| `uploadIds` | ready uploads in material order, 1 to 20 |
| `title` | project title, up to 120 characters; without it — "Montage" and the date |
| `brief` | wishes for the director in your own words, up to 600 characters |
| `settings.templateId`, `fontId`, `colorId` | design template, caption font and accent color |
| `settings.subtitles` | whether captions are on, their design, size, position, colour by speaker, word highlight, entrance, words on screen and letter case ([fields](https://docs.slicer.media/en/api/editing/#style-subtitles)) |
| `settings.params` | emoji, sound effects, media cards, plaques, camera motion, intro zoom, hook |
| `settings.smartCut` | Smart cut: remove pauses and retakes |
| `settings.targetDurationSec` | length: 15, 30, 60, 90 seconds or `null` — AI decides |
| `settings.direction` | direction: video type, pace, effects, AI voiceover and its voice |

The full settings schema:

`MontageSettingsInput` — Any subset of montage settings; missing values — defaults, unknown ids — defaults

| Field | Type | Description |
| --- | --- | --- |
| `templateId` | `string` | Template: `bold`, `clean`, `pop` |
| `fontId` | `string` | Caption font: punch, poster, block, wide, editorial, rounded, script, mono |
| `colorId` | `string` | Accent: amber, azure, lime, crimson, fuchsia, mint, violet, white |
| `subtitles` | `object` | Caption look; a partial change keeps the other fields. A new `design` without `highlight`/`animation`/`words` resets them to `auto` |
| `subtitles.enabled` | `boolean` |     |
| `subtitles.design` | `string` | — `dynamic` · `karaoke` · `classic` · `minimal` · `pop` · `box` · `word` |
| `subtitles.size` | `string` | — `small` · `medium` · `large` |
| `subtitles.position` | `string` | `auto` — each phrase where the frame has the most free space (off the face, hook, plaques, titles) — `auto` · `top` · `center` · `bottom` |
| `subtitles.speakerColors` | `boolean` | Each speaker in its own colour |
| `subtitles.highlight` | `string` | The spoken word: colour, pop (grows), box, underline; `auto` — as the design intends — `auto` · `none` · `color` · `pop` · `box` · `underline` |
| `subtitles.animation` | `string` | Phrase entrance; `words` — word by word as spoken — `auto` · `none` · `fade` · `pop` · `slide` · `words` |
| `subtitles.words` | `string` | Words on screen at once — `auto` · `one` · `few` · `line` · `phrase` |
| `subtitles.letterCase` | `string` | Letter case; `auto` — as the font intends — `auto` · `upper` · `normal` |
| `params` | `object` | Montage parameters, see `GET /v1/styles` → `params` |
| `params.emoji` | `boolean` |     |
| `params.soundEffects` | `boolean` |     |
| `params.mediaCards` | `boolean` |     |
| `params.plaques` | `boolean` |     |
| `params.cameraMotion` | `boolean` |     |
| `params.introZoom` | `boolean` |     |
| `params.hook` | `boolean` |     |
| `smartCut` | `boolean` | Smart cut: remove pauses and retakes |
| `targetDurationSec` | `15 \| 30 \| 60 \| 90 \| null` | Target length; `null` — AI decides (up to 90 s) |
| `language` | `string` | — `auto` · `ru` · `en` |
| `direction` | `object` |     |
| `direction.mode` | `string` | — `auto` · `talking` · `story` · `product` · `aesthetic` |
| `direction.pace` | `string` | — `calm` · `balanced` · `dynamic` |
| `direction.effects` | `string` | — `off` · `light` · `bold` |
| `direction.narration` | `boolean` | AI voiceover |
| `direction.voice` | `string` | — `warm` · `soft` · `confident` · `deep` |

Unknown values (`"fontId": "comic"`) are replaced with defaults — no error. The `brief` is data for the director, not commands: it can't change the output format or the montage rules.

## The price before starting

Paid API operations all work the same way: **the first request names the price, the second — with your consent — starts the operation.**

### 1. Request without confirmCost — the price

Response:

```json
{ "ok": true, "data": { "status": "quote", "cost": 24, "free": false, "available": 240, "enough": true, "spendLimitLeft": 176 } }
```

Nothing was created or charged. `enough` accounts for both the balance and what's left of the token's [spend limit](https://docs.slicer.media/en/api/authentication/). If the free first video is still available, you get `"cost": 0, "free": true` — it also has to be confirmed: `"confirmCost": 0`.

### 2. The same request with confirmCost — the start

Add `"confirmCost": 24` and an `Idempotency-Key` header. The response is the created project with the `queued` status.

| Response to the start | Meaning |
| --- | --- |
| `409 cost_changed` | the price changed between requests — request and confirm it again |
| `402 insufficient_tokens` | the balance is lower than the price — top up in the app |
| `402 spend_limit_exceeded` | the operation doesn't fit into the token's monthly limit |

To get a price without preparing a request body, use `POST /v1/estimate` with `kind`: `montage`, `remount`, `retry` (with `projectId`), `assistant`, `translate` or `transcribe` — a project's [captions from speech](https://docs.slicer.media/en/api/editing/#transcribe) (with `projectId`; by default — every source whose speech isn't recognised yet). A montage costs 24 tokens per video up to 90 seconds; the full price list is in `GET /v1/styles` → `pricing` and in [Operation prices](https://docs.slicer.media/en/guide/help/prices/).

Captions from speech, the AI assistant and caption translation are priced at AI cost with no markup: their price has an expected amount (`estimate`) and a maximum (`cost`), and `atCost: true`. You confirm the maximum; the actual cost is charged, never more ([Editing the video](https://docs.slicer.media/en/api/editing/#assistant)).

> **Important.** If you are building a service for other people, show them the price and start the montage only after they agree. This is exactly why the API separates the price from the start.

## Progress

`GET /v1/projects/{id}` shows what's happening. Poll every 5 seconds or subscribe to [webhooks](https://docs.slicer.media/en/api/webhooks/).

| Field | Value |
| --- | --- |
| `status` | `queued` → `processing` → `succeeded`, `failed` or `cancelled`; a project without the AI director is `draft` until its first export |
| `mode` | `ai` — edited by the AI director, `manual` — a project without the AI director |
| `stage`, `progress` | the current stage and run progress in percent (0 to 100 for every run) |
| `stages[]` | the run's stages: status, timing, progress and each stage's share of the price |
| `run.tokensState` | what happened to the tokens: `free`, `reserved`, `charged`, `refunded` |
| `error` | code and a readable message if the run failed |
| `result` | links to the video file and cover, the container, file parameters |
| `appUrl` | the project in the Slicer web app |

When a run starts, tokens are **reserved**; on success they are charged, and on failure or stop the reservation returns to your balance.

## Video versions

A montage produces one version. After editing the video, the director reviews the finished result itself and fixes the order and logic — only then does the project become `succeeded`. There is nothing to choose: `versionChoice` is `null`, and the finished version is current right away.

Every rebuild, retry or style change adds a new version, and the previous ones stay in `versions[]` (newest first):

Excerpt of GET /v1/projects/318 after a rebuild:

```json
{
  "status": "succeeded",
  "versionChoice": null,
  "versions": [
    { "id": 905, "version": 2, "idea": null, "durationSec": 29.6, "current": true, "awaitingChoice": false, "hasFile": true },
    { "id": 901, "version": 1, "idea": null, "durationSec": 31.4, "current": false, "awaitingChoice": false, "hasFile": true }
  ]
}
```

-   Watch a version — `GET /v1/projects/{id}/result?version=901`.
-   Bring back an earlier one — `POST /v1/projects/{id}/versions/901/choose`: the version becomes current, further edits and exports start from it, and the video that was current stays in the history. **Make current** in the app works the same way.

The last 10 versions of a project are kept with their files (`hasFile`); the list is `GET /v1/projects/{id}/versions`. A version without a file can't be brought back — the answer is `404 version_not_found`.

## Rebuild, retry and stop

| Operation | Request | Price |
| --- | --- | --- |
| Rebuild with new settings | `POST /v1/projects/{id}/remount` with `settings`, `brief`, `confirmCost` | same as a montage; a new version, the previous one stays in the history |
| Retry a failed or stopped run | `POST /v1/projects/{id}/retry` with `confirmCost` | same as the original run; a free run is retried for free |
| Stop a running run | `POST /v1/projects/{id}/cancel` | free, the reservation is returned |

A retry continues from the first stage that failed, not from scratch. If an export or a style change fails, the project stays ready with the previous video.

While a project is being worked on, a new start returns `409 busy`. The log of all work on a project is `GET /v1/projects/{id}/history`.

## Project without the AI director

If you build the video yourself — with your code or your own AI assistant — create a project in `manual` mode, like **Edit it myself** in the app. It's free: the director doesn't edit, the materials go onto the timeline as they are, and then you edit the video with [operations](https://docs.slicer.media/en/api/editing/). The MCP tool `create_project` creates exactly this kind of project by default ([MCP server](https://docs.slicer.media/en/mcp/#free)); for `POST /v1/projects` the default mode is `ai`.

POST /v1/projects body:

```json
{
  "uploadIds": ["7d0c6a4e-2f7b-4f5e-9a0e-3b8f2c1d9e61", "a1b2c3d4-0000-4c1e-8f00-5e6f7a8b9c0d"],
  "mode": "manual",
  "format": "9:16",
  "title": "Morning at the coffee shop"
}
```

-   No `confirmCost` is needed — the response is the project right away. `settings` and `brief` aren't used in this mode.
-   `format` is the frame format: `9:16` (default), `4:5`, `1:1`, `16:9`.
-   Materials are laid out in order: videos whole, photos for 3 seconds each, music on the music track, voiceover on the voice track. If a material's proportions don't match the format, the frame is fitted whole over a blurred background.
-   The project goes through a short preparation without AI (`queued` → `processing`, usually seconds) and gets the `draft` status. Wait for it by polling `GET /v1/projects/{id}`.

### 1. Read the video

`GET /v1/projects/{id}/transcript`. Speech isn't recognized in a draft: fragments are by time (`kind: "range"`), and elements are placed by video time ([Elements by time](https://docs.slicer.media/en/api/editing/#by-time)). Need captions? [Recognise the speech](https://docs.slicer.media/en/api/editing/#transcribe) (`POST /v1/projects/{id}/transcribe`, at cost per minute of sound): after that the captions follow the speech, and words with `id`s appear in the video text.

### 2. Build the video with operations

`POST /v1/projects/{id}/edits` — split, trim, reorder and delete fragments, remove pauses (`cut_silences`), add a hook, titles, plaques, zooms, transitions and colour, set music and voice. Edits are free.

### 3. Export

`POST /v1/projects/{id}/export` ([Export and publishing](https://docs.slicer.media/en/api/export/)). The first successful export moves the project to `succeeded`; if the export fails, the project stays a draft.

| What | How it works |
| --- | --- |
| Downloading the video and cover | after the first export; before it — `not_ready` |
| Export | free; the number of free exports of projects without the AI director per day is limited — beyond it, `429 daily_export_limit` with `retryAfterSec` |
| Drafts | the number of drafts at a time is limited — beyond it, `409 too_many_drafts`: export or delete some |
| Mode unavailable | `503 manual_disabled` — the mode is temporarily off |
| Captions from speech, AI assistant and translation | work here too, at cost |
| AI montage on the same materials | `POST /v1/projects/{id}/remount` at the montage price: the project switches to `ai` mode and the director edits and checks the video |

## What's next

[Editing the videoCut words, add a hook and plaques, change the style.](https://docs.slicer.media/en/api/editing/)[Export and publishingThe final file and publishing to social networks.](https://docs.slicer.media/en/api/export/)[How the director worksWhat happens during a montage and how the director checks the video.](https://docs.slicer.media/en/guide/ai/director/)
