# Editing

> Slicer API editing endpoints — video text with word ids, operations, the edit document, captions from speech, AI assistant, translation, style, own director.

Source: https://docs.slicer.media/en/api/reference/editing/

Edits change the video the same way the app's editor does and are saved immediately. All of them are free except captions from speech, the AI assistant and caption translation — those are priced at AI cost ([Captions from speech](https://docs.slicer.media/en/api/editing/#transcribe), [AI assistant](https://docs.slicer.media/en/api/editing/#assistant)). Every edit passes the revision `rev`: if the video has changed meanwhile, the response is `409 stale_document`. How to use them — in the guide [Editing the video](https://docs.slicer.media/en/api/editing/).

## The video as text with word ids

`GET /v1/projects/{id}/transcript`

- **Scopes:** `projects:read`
- **Rate limit:** default

### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `id` _(required)_ | `integer` | — ≥ 1 |

### Request

**curl**

```bash
curl "https://api.slicer.media/v1/projects/42/transcript" \
  -H "Authorization: Bearer $SLICER_TOKEN"
```

**JavaScript**

```js
const response = await fetch('https://api.slicer.media/v1/projects/42/transcript', {
  headers: {
    Authorization: `Bearer ${process.env.SLICER_TOKEN}`,
  },
});
const { ok, data, error } = await response.json();
```

**Python**

```python
import os
import requests

response = requests.get(
    "https://api.slicer.media/v1/projects/42/transcript",
    headers={"Authorization": f"Bearer {os.environ['SLICER_TOKEN']}"},
)
body = response.json()
```

### Response

**Response fields: `Transcript` (90)**

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` | Document revision: pass it to edits |
| `durationSec` _(required)_ | `number` |     |
| `text` _(required)_ | `string` | The video as text for an LLM: fragments with word ids `w<id>`, cut words in \[brackets\], graphics |
| `blocks` _(required)_ | `object[]` |     |
| `blocks[].id` _(required)_ | `string` |     |
| `blocks[].position` _(required)_ | `integer` |     |
| `blocks[].sourceIndex` _(required)_ | `integer` |     |
| `blocks[].kind` _(required)_ | `string` | — `words` · `range` |
| `blocks[].words` _(required)_ | `object[]` |     |
| `blocks[].words[].id` _(required)_ | `integer` |     |
| `blocks[].words[].text` _(required)_ | `string` |     |
| `blocks[].words[].cut` _(required)_ | `boolean` |     |
| `blocks[].words[].accent` _(required)_ | `boolean` |     |
| `elements` _(required)_ | `object` |     |
| `elements.hook` _(required)_ | `string \| null` |     |
| `elements.plaques` _(required)_ | `object[]` |     |
| `elements.plaques[].id` _(required)_ | `string` |     |
| `elements.plaques[].kind` _(required)_ | `string` |     |
| `elements.plaques[].title` _(required)_ | `string` |     |
| `elements.plaques[].fromWordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.plaques[].toWordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.plaques[].start` | `number` | Where the element starts in the video, s; absent — hidden (its words were cut) |
| `elements.plaques[].end` | `number` | Where the element ends in the video, s |
| `elements.plaques[].anchor` | `object` | Placed by time: fragment, second of its source and duration in the video, s |
| `elements.plaques[].anchor.blockId` _(required)_ | `string` |     |
| `elements.plaques[].anchor.at` _(required)_ | `number` |     |
| `elements.plaques[].anchor.duration` _(required)_ | `number` |     |
| `elements.cards` _(required)_ | `object[]` |     |
| `elements.cards[].id` _(required)_ | `string` |     |
| `elements.cards[].caption` _(required)_ | `string \| null` |     |
| `elements.cards[].fromWordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.cards[].toWordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.cards[].start` | `number` | Where the element starts in the video, s; absent — hidden (its words were cut) |
| `elements.cards[].end` | `number` | Where the element ends in the video, s |
| `elements.cards[].anchor` | `object` | Placed by time: fragment, second of its source and duration in the video, s |
| `elements.cards[].anchor.blockId` _(required)_ | `string` |     |
| `elements.cards[].anchor.at` _(required)_ | `number` |     |
| `elements.cards[].anchor.duration` _(required)_ | `number` |     |
| `elements.emoji` _(required)_ | `object[]` |     |
| `elements.emoji[].id` _(required)_ | `string` |     |
| `elements.emoji[].emoji` _(required)_ | `string` |     |
| `elements.emoji[].wordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.emoji[].start` | `number` | Where the element starts in the video, s; absent — hidden (its words were cut) |
| `elements.emoji[].end` | `number` | Where the element ends in the video, s |
| `elements.emoji[].anchor` | `object` | Placed by time: fragment, second of its source and duration in the video, s |
| `elements.emoji[].anchor.blockId` _(required)_ | `string` |     |
| `elements.emoji[].anchor.at` _(required)_ | `number` |     |
| `elements.emoji[].anchor.duration` _(required)_ | `number` |     |
| `elements.zooms` _(required)_ | `object[]` |     |
| `elements.zooms[].id` _(required)_ | `string` |     |
| `elements.zooms[].fromWordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.zooms[].toWordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.zooms[].start` | `number` | Where the element starts in the video, s; absent — hidden (its words were cut) |
| `elements.zooms[].end` | `number` | Where the element ends in the video, s |
| `elements.zooms[].anchor` | `object` | Placed by time: fragment, second of its source and duration in the video, s |
| `elements.zooms[].anchor.blockId` _(required)_ | `string` |     |
| `elements.zooms[].anchor.at` _(required)_ | `number` |     |
| `elements.zooms[].anchor.duration` _(required)_ | `number` |     |
| `elements.titles` _(required)_ | `object[]` | Free text layers (titles) |
| `elements.titles[].id` _(required)_ | `string` |     |
| `elements.titles[].text` _(required)_ | `string` |     |
| `elements.titles[].font` _(required)_ | `string` |     |
| `elements.titles[].size` _(required)_ | `number` |     |
| `elements.titles[].color` _(required)_ | `string` |     |
| `elements.titles[].background` _(required)_ | `string` | — `none` · `box` · `pill` |
| `elements.titles[].backgroundColor` | `string` |     |
| `elements.titles[].outline` | `boolean` |     |
| `elements.titles[].shadow` | `boolean` |     |
| `elements.titles[].align` _(required)_ | `string` | — `left` · `center` · `right` |
| `elements.titles[].x` _(required)_ | `number` |     |
| `elements.titles[].y` _(required)_ | `number` |     |
| `elements.titles[].animIn` | `string` | — `fade` · `slide` · `pop` · `typewriter` |
| `elements.titles[].animOut` | `string` | — `fade` · `slide` · `pop` |
| `elements.titles[].fromWordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.titles[].toWordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.titles[].start` | `number` | Where the element starts in the video, s; absent — hidden (its words were cut) |
| `elements.titles[].end` | `number` | Where the element ends in the video, s |
| `elements.titles[].anchor` | `object` | Placed by time: fragment, second of its source and duration in the video, s |
| `elements.titles[].anchor.blockId` _(required)_ | `string` |     |
| `elements.titles[].anchor.at` _(required)_ | `number` |     |
| `elements.titles[].anchor.duration` _(required)_ | `number` |     |
| `elements.markers` _(required)_ | `object[]` | Author markers on the timeline (not shown in the video); hidden markers are omitted |
| `elements.markers[].id` _(required)_ | `string` |     |
| `elements.markers[].time` _(required)_ | `number` |     |
| `elements.markers[].label` _(required)_ | `string \| null` |     |
| `elements.markers[].color` _(required)_ | `string` | — `cyan` · `yellow` · `red` · `green` · `violet` |
| `elements.sounds` _(required)_ | `object[]` | Sound effects placed by the author; cut-off ones are omitted |
| `elements.sounds[].id` _(required)_ | `string` |     |
| `elements.sounds[].kind` _(required)_ | `string` | — `whoosh` · `pop` · `ding` · `click` · `swipe` · `rise` · `boom` · `chime` |
| `elements.sounds[].time` _(required)_ | `number` |     |

Response example:

```json
{
  "ok": true,
  "data": {
    "rev": 7,
    "durationSec": 42.5,
    "text": "string",
    "blocks": [
      {
        "id": "string",
        "position": 1,
        "sourceIndex": 1,
        "kind": "words",
        "words": [
          {
            "id": null,
            "text": null,
            "cut": null,
            "accent": null
          }
        ]
      }
    ],
    "elements": {
      "hook": "string",
      "plaques": [
        {
          "id": "string",
          "kind": "string",
          "title": "Монтаж за 3 минуты",
          "fromWordId": null,
          "toWordId": null,
          "start": 1.5,
          "end": 1.5,
          "anchor": {
            "blockId": null,
            "at": null,
            "duration": null
          }
        }
      ],
      "cards": [
        {
          "id": "string",
          "caption": "string",
          "fromWordId": null,
          "toWordId": null,
          "start": 1.5,
          "end": 1.5,
          "anchor": {
            "blockId": null,
            "at": null,
            "duration": null
          }
        }
      ],
      "emoji": [
        {
          "id": "string",
          "emoji": "string",
          "wordId": null,
          "start": 1.5,
          "end": 1.5,
          "anchor": {
            "blockId": null,
            "at": null,
            "duration": null
          }
        }
      ],
      "zooms": [
        {
          "id": "string",
          "fromWordId": null,
          "toWordId": null,
          "start": 1.5,
          "end": 1.5,
          "anchor": {
            "blockId": null,
            "at": null,
            "duration": null
          }
        }
      ],
      "titles": [
        {
          "id": "string",
          "text": "string",
          "font": "string",
          "size": 104857600,
          "color": "string",
          "background": "none",
          "backgroundColor": "string",
          "outline": true,
          "shadow": true,
          "align": "left",
          "x": 1.5,
          "y": 1.5,
          "animIn": "fade",
          "animOut": "fade",
          "fromWordId": null,
          "toWordId": null,
          "start": 1.5,
          "end": 1.5,
          "anchor": {
            "blockId": null,
            "at": null,
            "duration": null
          }
        }
      ],
      "markers": [
        {
          "id": "string",
          "time": 1.5,
          "label": "string",
          "color": "cyan"
        }
      ],
      "sounds": [
        {
          "id": "string",
          "kind": "whoosh",
          "time": 1.5
        }
      ]
    }
  }
}
```

## Apply edit operations

`POST /v1/projects/{id}/edits`

- **Scopes:** `projects:write`
- **Rate limit:** default

Free. Word ids — from `GET …/transcript`. Inapplicable operations are skipped and listed in `skipped`.

### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `id` _(required)_ | `integer` | — ≥ 1 |

### Request body `EditsRequest`

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` | Revision from GET …/transcript or …/document |
| `operations` _(required)_ | `EditOperation[]` | — 1–80 |

### Request

**curl**

```bash
curl -X POST "https://api.slicer.media/v1/projects/42/edits" \
  -H "Authorization: Bearer $SLICER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"rev":7,"operations":[{"op":"cut_words","fromWordId":42,"toWordId":42}]}'
```

**JavaScript**

```js
const response = await fetch('https://api.slicer.media/v1/projects/42/edits', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SLICER_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "rev": 7,
    "operations": [
      {
        "op": "cut_words",
        "fromWordId": 42,
        "toWordId": 42
      }
    ]
  }),
});
const { ok, data, error } = await response.json();
```

**Python**

```python
import os
import requests

response = requests.post(
    "https://api.slicer.media/v1/projects/42/edits",
    headers={"Authorization": f"Bearer {os.environ['SLICER_TOKEN']}"},
    json={
        "rev": 7,
        "operations": [{
            "op": "cut_words",
            "fromWordId": 42,
            "toWordId": 42,
        }],
    },
)
body = response.json()
```

### Response

**Response fields: `EditsResult` (7)**

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` |     |
| `applied` _(required)_ | `integer` |     |
| `skipped` _(required)_ | `object[]` |     |
| `skipped[].index` _(required)_ | `integer` |     |
| `skipped[].op` _(required)_ | `string` |     |
| `skipped[].reason` _(required)_ | `string` |     |
| `durationSec` _(required)_ | `number` |     |

Response example:

```json
{
  "ok": true,
  "data": {
    "rev": 7,
    "applied": 1,
    "skipped": [
      {
        "index": 1,
        "op": "string",
        "reason": "string"
      }
    ],
    "durationSec": 42.5
  }
}
```

## Captions from speech

`POST /v1/projects/{id}/transcribe`

- **Scopes:** `montage:run`
- **Rate limit:** paid operations
- **Price:** _spends tokens_

Recognises speech in the project sources (a manual project or new sources) — at AI cost, per minute of sound. Without `confirmCost` returns the price. Runs in the background: follow it with GET …/transcribe; when ready, the words are in the transcript and captions follow the speech.

> **Important.** Without `confirmCost` it returns the price (`Quote`) and starts nothing.

### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `id` _(required)_ | `integer` | — ≥ 1 |

### Request body `TranscribeRequest`

| Field | Type | Description |
| --- | --- | --- |
| `sources` | `integer[]` | Source indexes (default — every source with sound whose speech is not recognised yet) — 1–40 |
| `language` | `string` | Speech language: auto or a two-letter code |
| `confirmCost` | `integer` | Price you agree to pay. Without it the response is a `Quote` and nothing starts |

### Request

**curl**

```bash
curl -X POST "https://api.slicer.media/v1/projects/42/transcribe" \
  -H "Authorization: Bearer $SLICER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"sources":[1],"language":"ru"}'
```

**JavaScript**

```js
const response = await fetch('https://api.slicer.media/v1/projects/42/transcribe', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SLICER_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "sources": [
      1
    ],
    "language": "ru"
  }),
});
const { ok, data, error } = await response.json();
```

**Python**

```python
import os
import requests

response = requests.post(
    "https://api.slicer.media/v1/projects/42/transcribe",
    headers={"Authorization": f"Bearer {os.environ['SLICER_TOKEN']}"},
    json={
        "sources": [1],
        "language": "ru",
    },
)
body = response.json()
```

### Response

One of: Quote, TranscribeJob

**Response fields: `Quote` (8)**

| Field | Type | Description |
| --- | --- | --- |
| `status` _(required)_ | `"quote"` |     |
| `cost` _(required)_ | `integer` | Price in tokens; repeat the request with `confirmCost` equal to it. For an operation at cost (`atCost`) — the maximum: the actual cost is charged, never more |
| `free` _(required)_ | `boolean` | The operation is free (first video, free retry) |
| `estimate` | `number` | Operation at cost: expected price in tokens (fractional) |
| `atCost` | `boolean` | Priced at the AI providers’ cost, no markup: the wallet is charged the actual cost, at most `cost` |
| `available` _(required)_ | `integer` | Tokens available now |
| `enough` _(required)_ | `boolean` | Enough tokens to run |
| `spendLimitLeft` _(required)_ | `integer \| null` | What is left of the token's monthly spend limit; `null` — no limit |

**Response fields: `TranscribeJob` (5)**

| Field | Type | Description |
| --- | --- | --- |
| `id` _(required)_ | `string` |     |
| `status` _(required)_ | `string` | — `processing` · `ready` · `failed` |
| `sources` _(required)_ | `integer[]` |     |
| `createdAt` _(required)_ | `string` |     |
| `error` | `string` |     |

Response example:

```json
{
  "ok": true,
  "data": {
    "status": "quote",
    "cost": 24,
    "free": true,
    "estimate": 1.5,
    "atCost": true,
    "available": 240,
    "enough": true,
    "spendLimitLeft": 200
  }
}
```

## Speech recognition jobs

`GET /v1/projects/{id}/transcribe`

- **Scopes:** `projects:read`
- **Rate limit:** default

### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `id` _(required)_ | `integer` | — ≥ 1 |

### Request

**curl**

```bash
curl "https://api.slicer.media/v1/projects/42/transcribe" \
  -H "Authorization: Bearer $SLICER_TOKEN"
```

**JavaScript**

```js
const response = await fetch('https://api.slicer.media/v1/projects/42/transcribe', {
  headers: {
    Authorization: `Bearer ${process.env.SLICER_TOKEN}`,
  },
});
const { ok, data, error } = await response.json();
```

**Python**

```python
import os
import requests

response = requests.get(
    "https://api.slicer.media/v1/projects/42/transcribe",
    headers={"Authorization": f"Bearer {os.environ['SLICER_TOKEN']}"},
)
body = response.json()
```

### Response

Response example:

```json
{
  "ok": true,
  "data": [
    {
      "id": "string",
      "status": "processing",
      "sources": [
        1
      ],
      "createdAt": "2026-10-16T09:30:00Z",
      "error": "string"
    }
  ]
}
```

## Command for the AI assistant

`POST /v1/projects/{id}/assistant`

- **Scopes:** `montage:run`
- **Rate limit:** paid operations
- **Price:** _spends tokens_

Charged only if something changed. The result is saved.

> **Important.** Without `confirmCost` it returns the price (`Quote`) and starts nothing.

### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `id` _(required)_ | `integer` | — ≥ 1 |

### Request body `AssistantRequest`

| Field | Type | Description |
| --- | --- | --- |
| `command` _(required)_ | `string` | What to change, in any language — ≤ 500 |
| `rev` _(required)_ | `integer` |     |
| `confirmCost` | `integer` | Price you agree to pay. Without it the response is a `Quote` and nothing starts |

### Request

**curl**

```bash
curl -X POST "https://api.slicer.media/v1/projects/42/assistant" \
  -H "Authorization: Bearer $SLICER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"command":"Сделай субтитры крупнее","rev":7}'
```

**JavaScript**

```js
const response = await fetch('https://api.slicer.media/v1/projects/42/assistant', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SLICER_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "command": "Сделай субтитры крупнее",
    "rev": 7
  }),
});
const { ok, data, error } = await response.json();
```

**Python**

```python
import os
import requests

response = requests.post(
    "https://api.slicer.media/v1/projects/42/assistant",
    headers={"Authorization": f"Bearer {os.environ['SLICER_TOKEN']}"},
    json={
        "command": "Сделай субтитры крупнее",
        "rev": 7,
    },
)
body = response.json()
```

### Response

One of: Quote, AssistantResult

**Response fields: `Quote` (8)**

| Field | Type | Description |
| --- | --- | --- |
| `status` _(required)_ | `"quote"` |     |
| `cost` _(required)_ | `integer` | Price in tokens; repeat the request with `confirmCost` equal to it. For an operation at cost (`atCost`) — the maximum: the actual cost is charged, never more |
| `free` _(required)_ | `boolean` | The operation is free (first video, free retry) |
| `estimate` | `number` | Operation at cost: expected price in tokens (fractional) |
| `atCost` | `boolean` | Priced at the AI providers’ cost, no markup: the wallet is charged the actual cost, at most `cost` |
| `available` _(required)_ | `integer` | Tokens available now |
| `enough` _(required)_ | `boolean` | Enough tokens to run |
| `spendLimitLeft` _(required)_ | `integer \| null` | What is left of the token's monthly spend limit; `null` — no limit |

**Response fields: `AssistantResult` (6)**

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` |     |
| `summary` _(required)_ | `string` |     |
| `applied` _(required)_ | `integer` |     |
| `charged` _(required)_ | `integer` | Tokens charged from the wallet (whole) |
| `costTokens` | `number` | Actual cost of the command in tokens (fractional); the remainder accumulates with the project |
| `durationSec` _(required)_ | `number` |     |

Response example:

```json
{
  "ok": true,
  "data": {
    "status": "quote",
    "cost": 24,
    "free": true,
    "estimate": 1.5,
    "atCost": true,
    "available": 240,
    "enough": true,
    "spendLimitLeft": 200
  }
}
```

## Translate captions

`POST /v1/projects/{id}/translate`

- **Scopes:** `montage:run`
- **Rate limit:** paid operations
- **Price:** _spends tokens_

> **Important.** Without `confirmCost` it returns the price (`Quote`) and starts nothing.

### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `id` _(required)_ | `integer` | — ≥ 1 |

### Request body `TranslateRequest`

| Field | Type | Description |
| --- | --- | --- |
| `language` _(required)_ | `string` | — `en` · `es` · `pt` · `de` · `fr` · `it` · `pl` · `tr` · `id` · `uk` · `ru` |
| `rev` _(required)_ | `integer` |     |
| `confirmCost` | `integer` | Price you agree to pay. Without it the response is a `Quote` and nothing starts |

### Request

**curl**

```bash
curl -X POST "https://api.slicer.media/v1/projects/42/translate" \
  -H "Authorization: Bearer $SLICER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"language":"en","rev":7}'
```

**JavaScript**

```js
const response = await fetch('https://api.slicer.media/v1/projects/42/translate', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SLICER_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "language": "en",
    "rev": 7
  }),
});
const { ok, data, error } = await response.json();
```

**Python**

```python
import os
import requests

response = requests.post(
    "https://api.slicer.media/v1/projects/42/translate",
    headers={"Authorization": f"Bearer {os.environ['SLICER_TOKEN']}"},
    json={
        "language": "en",
        "rev": 7,
    },
)
body = response.json()
```

### Response

One of: Quote, TranslateResult

**Response fields: `Quote` (8)**

| Field | Type | Description |
| --- | --- | --- |
| `status` _(required)_ | `"quote"` |     |
| `cost` _(required)_ | `integer` | Price in tokens; repeat the request with `confirmCost` equal to it. For an operation at cost (`atCost`) — the maximum: the actual cost is charged, never more |
| `free` _(required)_ | `boolean` | The operation is free (first video, free retry) |
| `estimate` | `number` | Operation at cost: expected price in tokens (fractional) |
| `atCost` | `boolean` | Priced at the AI providers’ cost, no markup: the wallet is charged the actual cost, at most `cost` |
| `available` _(required)_ | `integer` | Tokens available now |
| `enough` _(required)_ | `boolean` | Enough tokens to run |
| `spendLimitLeft` _(required)_ | `integer \| null` | What is left of the token's monthly spend limit; `null` — no limit |

**Response fields: `TranslateResult` (4)**

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` |     |
| `phrases` _(required)_ | `integer` |     |
| `charged` _(required)_ | `integer` | Tokens charged from the wallet (whole) |
| `costTokens` | `number` | Actual cost of the translation in tokens (fractional) |

Response example:

```json
{
  "ok": true,
  "data": {
    "status": "quote",
    "cost": 24,
    "free": true,
    "estimate": 1.5,
    "atCost": true,
    "available": 240,
    "enough": true,
    "spendLimitLeft": 200
  }
}
```

## Auto colour and colour matching (free)

`POST /v1/projects/{id}/auto-color`

- **Scopes:** `projects:write`
- **Rate limit:** default

No AI: statistics of 10 frames of the fragment → «Adjust» values (exposure, contrast, saturation, white balance). The result is applied and saved.

### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `id` _(required)_ | `integer` | — ≥ 1 |

### Request body `AutoColorRequest`

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` | Revision from GET …/transcript or …/document |
| `blockId` _(required)_ | `string` | Fragment id: the one to correct (`auto`) or the sample (`match`) — ≤ 64 |
| `mode` _(required)_ | `string` | `auto` — balance this fragment; `match` — make all other fragments look like it (with its filter and LUT) — `auto` · `match` |

### Request

**curl**

```bash
curl -X POST "https://api.slicer.media/v1/projects/42/auto-color" \
  -H "Authorization: Bearer $SLICER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"rev":7,"blockId":"string","mode":"auto"}'
```

**JavaScript**

```js
const response = await fetch('https://api.slicer.media/v1/projects/42/auto-color', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SLICER_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "rev": 7,
    "blockId": "string",
    "mode": "auto"
  }),
});
const { ok, data, error } = await response.json();
```

**Python**

```python
import os
import requests

response = requests.post(
    "https://api.slicer.media/v1/projects/42/auto-color",
    headers={"Authorization": f"Bearer {os.environ['SLICER_TOKEN']}"},
    json={
        "rev": 7,
        "blockId": "string",
        "mode": "auto",
    },
)
body = response.json()
```

### Response

**Response fields: `AutoColorResult` (5)**

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` |     |
| `mode` _(required)_ | `string` | — `auto` · `match` |
| `adjust` _(required)_ | `Record<string, object>` | New «Adjust» values by fragment id |
| `applied` _(required)_ | `integer` |     |
| `durationSec` _(required)_ | `number` |     |

Response example:

```json
{
  "ok": true,
  "data": {
    "rev": 7,
    "mode": "auto",
    "adjust": {},
    "applied": 1,
    "durationSec": 42.5
  }
}
```

## Change the style

`PATCH /v1/projects/{id}/style`

- **Scopes:** `projects:write`
- **Rate limit:** default

### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `id` _(required)_ | `integer` | — ≥ 1 |

### Request body `StyleRequest`

| Field | Type | Description |
| --- | --- | --- |
| `templateId` | `string` |     |
| `fontId` | `string` |     |
| `colorId` | `string` |     |
| `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` |     |
| `params.soundEffects` | `boolean` |     |
| `params.introZoom` | `boolean` |     |

### Request

**curl**

```bash
curl -X PATCH "https://api.slicer.media/v1/projects/42/style" \
  -H "Authorization: Bearer $SLICER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"templateId":"string","fontId":"string"}'
```

**JavaScript**

```js
const response = await fetch('https://api.slicer.media/v1/projects/42/style', {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${process.env.SLICER_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "templateId": "string",
    "fontId": "string"
  }),
});
const { ok, data, error } = await response.json();
```

**Python**

```python
import os
import requests

response = requests.patch(
    "https://api.slicer.media/v1/projects/42/style",
    headers={"Authorization": f"Bearer {os.environ['SLICER_TOKEN']}"},
    json={
        "templateId": "string",
        "fontId": "string",
    },
)
body = response.json()
```

### Response

**Response fields: `Project` (26)**

| Field | Type | Description |
| --- | --- | --- |
| `id` _(required)_ | `integer` |     |
| `title` _(required)_ | `string` |     |
| `status` _(required)_ | `string` | `draft` — an “edit it myself” project before its first export: edit it with operations, then export — `queued` · `processing` · `succeeded` · `failed` · `cancelled` · `draft` |
| `mode` _(required)_ | `string` | `ai` — edited by the AI director; `manual` — materials on the timeline as is, free (only AI operations cost) — `ai` · `manual` |
| `stage` _(required)_ | `string \| null` |     |
| `progress` _(required)_ | `integer` | Progress of the current run, % |
| `client` _(required)_ | `string \| null` | Where it was created: web, mobile, api, mcp |
| `settings` _(required)_ | `object` | Montage settings (see MontageSettingsInput) |
| `brief` _(required)_ | `string \| null` |     |
| `isFree` _(required)_ | `boolean` |     |
| `watermark` _(required)_ | `boolean` |     |
| `tokensCost` _(required)_ | `integer` |     |
| `error` _(required)_ | `object \| null` |     |
| `error.code` _(required)_ | `string` |     |
| `error.message` _(required)_ | `string` |     |
| `durationSec` _(required)_ | `number \| null` |     |
| `versionChoice` _(required)_ | `integer[] \| null` | Version ids awaiting your choice; a montage returns one version, so usually `null` |
| `result` _(required)_ | `object \| null` |     |
| `result.url` _(required)_ | `string (uri)` | Download with the token (Range supported) |
| `result.posterUrl` _(required)_ | `string (uri) \| null` |     |
| `result.container` _(required)_ | `string` |     |
| `result.info` _(required)_ | `object \| null` |     |
| `appUrl` _(required)_ | `string (uri)` | The project in the Slicer web app |
| `sourcesAvailable` _(required)_ | `boolean` | Sources are kept 7 days after the montage |
| `createdAt` _(required)_ | `string` | ISO 8601 date-time, UTC |
| `finishedAt` _(required)_ | `string \| null` |     |

Response example:

```json
{
  "ok": true,
  "data": {
    "id": 42,
    "title": "Монтаж за 3 минуты",
    "status": "queued",
    "mode": "ai",
    "stage": "string",
    "progress": 0.5,
    "client": "string",
    "settings": {},
    "brief": "Динамично, с акцентом на главные мысли",
    "isFree": true,
    "watermark": true,
    "tokensCost": 24,
    "error": {
      "code": "string",
      "message": "string"
    },
    "durationSec": 42.5,
    "versionChoice": [
      1
    ],
    "result": {
      "url": "https://example.com/hook",
      "posterUrl": "https://api.slicer.media/v1/…",
      "container": "string",
      "info": {}
    },
    "appUrl": "https://api.slicer.media/v1/…",
    "sourcesAvailable": true,
    "createdAt": "2026-10-16T09:30:00Z",
    "finishedAt": "2026-10-16T09:30:00Z"
  }
}
```

## Your own director

Your AI agent edits the video itself — for free: a brief with the material and the plan and observation schemas, frames of the sources and accepting the plan with observations from the frames. The rules of the Slicer AI director are applied to the plan on the server; the answer has fixes and issues. How to use them — [Your own director](https://docs.slicer.media/en/api/editing/#own-director).

### Director brief for your AI assistant (free)

`POST /v1/projects/{id}/director/brief`

- **Scopes:** `projects:write`
- **Rate limit:** default

The material (speech by word ids, pauses, retakes, wishes), general tips and the schemas of the plan and of scenes. Your assistant looks at the footage with sourceFrames and sends the plan with what it saw to submitDirectorPlan; the rules of the Slicer director run on the server there. Optional settings are saved to the project.

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `id` _(required)_ | `integer` | — ≥ 1 |

#### Request body `DirectorBriefRequest`

| Field | Type | Description |
| --- | --- | --- |
| `mode` | `string` | Video type — `auto` · `talking` · `story` · `product` · `aesthetic` |
| `pace` | `string` | — `calm` · `balanced` · `dynamic` |
| `effects` | `string` | — `off` · `light` · `bold` |
| `targetDurationSec` | `integer \| null` | Target length, s; null — the director decides (up to 90 s) — 10–180 |
| `hook` | `boolean` | Open with a hook and an on-screen title |
| `plaques` | `boolean` |     |
| `emoji` | `boolean` |     |
| `cameraMotion` | `boolean` | Zooms and camera moves |

#### Request

**curl**

```bash
curl -X POST "https://api.slicer.media/v1/projects/42/director/brief" \
  -H "Authorization: Bearer $SLICER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode":"auto","pace":"calm"}'
```

**JavaScript**

```js
const response = await fetch('https://api.slicer.media/v1/projects/42/director/brief', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SLICER_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "mode": "auto",
    "pace": "calm"
  }),
});
const { ok, data, error } = await response.json();
```

**Python**

```python
import os
import requests

response = requests.post(
    "https://api.slicer.media/v1/projects/42/director/brief",
    headers={"Authorization": f"Bearer {os.environ['SLICER_TOKEN']}"},
    json={
        "mode": "auto",
        "pace": "calm",
    },
)
body = response.json()
```

#### Response

**Response fields: `DirectorBrief` (15)**

| Field | Type | Description |
| --- | --- | --- |
| `projectId` _(required)_ | `integer` |     |
| `rev` _(required)_ | `integer` |     |
| `settings` _(required)_ | `Record<string, object>` |     |
| `sources` _(required)_ | `object[]` |     |
| `sources[].index` _(required)_ | `integer` |     |
| `sources[].kind` _(required)_ | `string` |     |
| `sources[].durationSec` _(required)_ | `number` |     |
| `sources[].fileName` _(required)_ | `string \| null` |     |
| `sources[].words` _(required)_ | `integer` |     |
| `speechRecognized` _(required)_ | `boolean` |     |
| `material` _(required)_ | `string` | The material: sources, speech by word ids, pauses, retakes, wishes |
| `guide` _(required)_ | `string[]` | General tips (the Slicer director rules run on the server when you submit the plan) |
| `planSchema` _(required)_ | `Record<string, object>` | JSON schema of the plan for submitDirectorPlan |
| `scenesSchema` _(required)_ | `Record<string, object>` | JSON schema of what you saw in the frames |
| `next` _(required)_ | `string[]` |     |

Response example:

```json
{
  "ok": true,
  "data": {
    "projectId": 42,
    "rev": 7,
    "settings": {},
    "sources": [
      {
        "index": 1,
        "kind": "string",
        "durationSec": 42.5,
        "fileName": "interview.mp4",
        "words": 1
      }
    ],
    "speechRecognized": true,
    "material": "string",
    "guide": [
      "string"
    ],
    "planSchema": {},
    "scenesSchema": {},
    "next": [
      "string"
    ]
  }
}
```

### Frames of a source (free)

`GET /v1/projects/{id}/sources/{index}/frames`

- **Scopes:** `projects:read`
- **Rate limit:** default

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `id` _(required)_ | `integer` | — ≥ 1 |
| `index` _(required)_ | `integer` |     |

#### Query parameters

| Field | Type | Description |
| --- | --- | --- |
| `times` | `string` |     |
| `from` | `number` |     |
| `to` | `number` |     |
| `count` | `integer` | — 1–16 |

#### Request

**curl**

```bash
curl "https://api.slicer.media/v1/projects/42/sources/1/frames" \
  -H "Authorization: Bearer $SLICER_TOKEN"
```

**JavaScript**

```js
const response = await fetch('https://api.slicer.media/v1/projects/42/sources/1/frames', {
  headers: {
    Authorization: `Bearer ${process.env.SLICER_TOKEN}`,
  },
});
const { ok, data, error } = await response.json();
```

**Python**

```python
import os
import requests

response = requests.get(
    "https://api.slicer.media/v1/projects/42/sources/1/frames",
    headers={"Authorization": f"Bearer {os.environ['SLICER_TOKEN']}"},
)
body = response.json()
```

#### Response

**Response fields: `SourceFrames` (6)**

| Field | Type | Description |
| --- | --- | --- |
| `sourceIndex` _(required)_ | `integer` |     |
| `durationSec` _(required)_ | `number` |     |
| `frames` _(required)_ | `object[]` |     |
| `frames[].time` _(required)_ | `number` |     |
| `frames[].mimeType` _(required)_ | `"image/jpeg"` |     |
| `frames[].data` _(required)_ | `string` | JPEG, base64, 512 px on the longer side |

Response example:

```json
{
  "ok": true,
  "data": {
    "sourceIndex": 1,
    "durationSec": 42.5,
    "frames": [
      {
        "time": 1.5,
        "mimeType": "image/jpeg",
        "data": "string"
      }
    ]
  }
}
```

### Apply a director plan (free)

`POST /v1/projects/{id}/director/plan`

- **Scopes:** `projects:write`
- **Rate limit:** default

The rules of the Slicer director are applied to the plan on the server (word boundaries, retakes, footage by your scenes, restraint, length): the plan becomes the video document — a new revision, visible in the editor — and the answer lists fixes and issues to address. Media cards from a description are not accepted (their picture is a paid generation).

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `id` _(required)_ | `integer` | — ≥ 1 |

#### Request body `DirectorPlanRequest`

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` | rev from directorBrief / transcript |
| `plan` _(required)_ | `Record<string, object>` | The plan in DirectorBrief.planSchema |
| `scenes` | `Record<string, object>[]` | What you saw in the frames, in DirectorBrief.scenesSchema — 0–400 |

#### Request

**curl**

```bash
curl -X POST "https://api.slicer.media/v1/projects/42/director/plan" \
  -H "Authorization: Bearer $SLICER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"rev":7,"plan":{}}'
```

**JavaScript**

```js
const response = await fetch('https://api.slicer.media/v1/projects/42/director/plan', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.SLICER_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "rev": 7,
    "plan": {}
  }),
});
const { ok, data, error } = await response.json();
```

**Python**

```python
import os
import requests

response = requests.post(
    "https://api.slicer.media/v1/projects/42/director/plan",
    headers={"Authorization": f"Bearer {os.environ['SLICER_TOKEN']}"},
    json={
        "rev": 7,
        "plan": {},
    },
)
body = response.json()
```

#### Response

**Response fields: `DirectorPlanResult` (10)**

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` |     |
| `durationSec` _(required)_ | `number` |     |
| `planSegments` _(required)_ | `integer` |     |
| `fragments` _(required)_ | `integer` |     |
| `fixes` _(required)_ | `string[]` | What Slicer corrected by its director rules |
| `issues` _(required)_ | `object[]` | What you should fix and submit again |
| `issues[].code` _(required)_ | `string` |     |
| `issues[].segment` | `integer` |     |
| `issues[].message` _(required)_ | `string` |     |
| `video` _(required)_ | `string` | The resulting video as text (fragments, words, elements) — check it against the plan |

Response example:

```json
{
  "ok": true,
  "data": {
    "rev": 7,
    "durationSec": 42.5,
    "planSegments": 1,
    "fragments": 1,
    "fixes": [
      "string"
    ],
    "issues": [
      {
        "code": "string",
        "segment": 1,
        "message": "string"
      }
    ],
    "video": "string"
  }
}
```

## Edit document

### Edit document

`GET /v1/projects/{id}/document`

- **Scopes:** `projects:read`
- **Rate limit:** default

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `id` _(required)_ | `integer` | — ≥ 1 |

#### Request

**curl**

```bash
curl "https://api.slicer.media/v1/projects/42/document" \
  -H "Authorization: Bearer $SLICER_TOKEN"
```

**JavaScript**

```js
const response = await fetch('https://api.slicer.media/v1/projects/42/document', {
  headers: {
    Authorization: `Bearer ${process.env.SLICER_TOKEN}`,
  },
});
const { ok, data, error } = await response.json();
```

**Python**

```python
import os
import requests

response = requests.get(
    "https://api.slicer.media/v1/projects/42/document",
    headers={"Authorization": f"Bearer {os.environ['SLICER_TOKEN']}"},
)
body = response.json()
```

#### Response

**Response fields: `DocumentState` (3)**

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` |     |
| `durationSec` _(required)_ | `number` |     |
| `document` _(required)_ | `object` | Edit document (EditDocument v1). Fields are only added within v1 |

Response example:

```json
{
  "ok": true,
  "data": {
    "rev": 7,
    "durationSec": 42.5,
    "document": {}
  }
}
```

### Replace the edit document (advanced)

`PUT /v1/projects/{id}/document`

- **Scopes:** `projects:write`
- **Rate limit:** default

#### Path parameters

| Field | Type | Description |
| --- | --- | --- |
| `id` _(required)_ | `integer` | — ≥ 1 |

#### Request body `PutDocumentRequest`

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` |     |
| `document` _(required)_ | `object` |     |

#### Request

**curl**

```bash
curl -X PUT "https://api.slicer.media/v1/projects/42/document" \
  -H "Authorization: Bearer $SLICER_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"rev":7,"document":{}}'
```

**JavaScript**

```js
const response = await fetch('https://api.slicer.media/v1/projects/42/document', {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${process.env.SLICER_TOKEN}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    "rev": 7,
    "document": {}
  }),
});
const { ok, data, error } = await response.json();
```

**Python**

```python
import os
import requests

response = requests.put(
    "https://api.slicer.media/v1/projects/42/document",
    headers={"Authorization": f"Bearer {os.environ['SLICER_TOKEN']}"},
    json={
        "rev": 7,
        "document": {},
    },
)
body = response.json()
```

#### Response

**Response fields: `DocumentState` (3)**

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` |     |
| `durationSec` _(required)_ | `number` |     |
| `document` _(required)_ | `object` | Edit document (EditDocument v1). Fields are only added within v1 |

Response example:

```json
{
  "ok": true,
  "data": {
    "rev": 7,
    "durationSec": 42.5,
    "document": {}
  }
}
```

## Schemas

`Transcript`

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` | Document revision: pass it to edits |
| `durationSec` _(required)_ | `number` |     |
| `text` _(required)_ | `string` | The video as text for an LLM: fragments with word ids `w<id>`, cut words in \[brackets\], graphics |
| `blocks` _(required)_ | `object[]` |     |
| `blocks[].id` _(required)_ | `string` |     |
| `blocks[].position` _(required)_ | `integer` |     |
| `blocks[].sourceIndex` _(required)_ | `integer` |     |
| `blocks[].kind` _(required)_ | `string` | — `words` · `range` |
| `blocks[].words` _(required)_ | `object[]` |     |
| `blocks[].words[].id` _(required)_ | `integer` |     |
| `blocks[].words[].text` _(required)_ | `string` |     |
| `blocks[].words[].cut` _(required)_ | `boolean` |     |
| `blocks[].words[].accent` _(required)_ | `boolean` |     |
| `elements` _(required)_ | `object` |     |
| `elements.hook` _(required)_ | `string \| null` |     |
| `elements.plaques` _(required)_ | `object[]` |     |
| `elements.plaques[].id` _(required)_ | `string` |     |
| `elements.plaques[].kind` _(required)_ | `string` |     |
| `elements.plaques[].title` _(required)_ | `string` |     |
| `elements.plaques[].fromWordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.plaques[].toWordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.plaques[].start` | `number` | Where the element starts in the video, s; absent — hidden (its words were cut) |
| `elements.plaques[].end` | `number` | Where the element ends in the video, s |
| `elements.plaques[].anchor` | `object` | Placed by time: fragment, second of its source and duration in the video, s |
| `elements.plaques[].anchor.blockId` _(required)_ | `string` |     |
| `elements.plaques[].anchor.at` _(required)_ | `number` |     |
| `elements.plaques[].anchor.duration` _(required)_ | `number` |     |
| `elements.cards` _(required)_ | `object[]` |     |
| `elements.cards[].id` _(required)_ | `string` |     |
| `elements.cards[].caption` _(required)_ | `string \| null` |     |
| `elements.cards[].fromWordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.cards[].toWordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.cards[].start` | `number` | Where the element starts in the video, s; absent — hidden (its words were cut) |
| `elements.cards[].end` | `number` | Where the element ends in the video, s |
| `elements.cards[].anchor` | `object` | Placed by time: fragment, second of its source and duration in the video, s |
| `elements.cards[].anchor.blockId` _(required)_ | `string` |     |
| `elements.cards[].anchor.at` _(required)_ | `number` |     |
| `elements.cards[].anchor.duration` _(required)_ | `number` |     |
| `elements.emoji` _(required)_ | `object[]` |     |
| `elements.emoji[].id` _(required)_ | `string` |     |
| `elements.emoji[].emoji` _(required)_ | `string` |     |
| `elements.emoji[].wordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.emoji[].start` | `number` | Where the element starts in the video, s; absent — hidden (its words were cut) |
| `elements.emoji[].end` | `number` | Where the element ends in the video, s |
| `elements.emoji[].anchor` | `object` | Placed by time: fragment, second of its source and duration in the video, s |
| `elements.emoji[].anchor.blockId` _(required)_ | `string` |     |
| `elements.emoji[].anchor.at` _(required)_ | `number` |     |
| `elements.emoji[].anchor.duration` _(required)_ | `number` |     |
| `elements.zooms` _(required)_ | `object[]` |     |
| `elements.zooms[].id` _(required)_ | `string` |     |
| `elements.zooms[].fromWordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.zooms[].toWordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.zooms[].start` | `number` | Where the element starts in the video, s; absent — hidden (its words were cut) |
| `elements.zooms[].end` | `number` | Where the element ends in the video, s |
| `elements.zooms[].anchor` | `object` | Placed by time: fragment, second of its source and duration in the video, s |
| `elements.zooms[].anchor.blockId` _(required)_ | `string` |     |
| `elements.zooms[].anchor.at` _(required)_ | `number` |     |
| `elements.zooms[].anchor.duration` _(required)_ | `number` |     |
| `elements.titles` _(required)_ | `object[]` | Free text layers (titles) |
| `elements.titles[].id` _(required)_ | `string` |     |
| `elements.titles[].text` _(required)_ | `string` |     |
| `elements.titles[].font` _(required)_ | `string` |     |
| `elements.titles[].size` _(required)_ | `number` |     |
| `elements.titles[].color` _(required)_ | `string` |     |
| `elements.titles[].background` _(required)_ | `string` | — `none` · `box` · `pill` |
| `elements.titles[].backgroundColor` | `string` |     |
| `elements.titles[].outline` | `boolean` |     |
| `elements.titles[].shadow` | `boolean` |     |
| `elements.titles[].align` _(required)_ | `string` | — `left` · `center` · `right` |
| `elements.titles[].x` _(required)_ | `number` |     |
| `elements.titles[].y` _(required)_ | `number` |     |
| `elements.titles[].animIn` | `string` | — `fade` · `slide` · `pop` · `typewriter` |
| `elements.titles[].animOut` | `string` | — `fade` · `slide` · `pop` |
| `elements.titles[].fromWordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.titles[].toWordId` _(required)_ | `integer \| null` | Word id; `null` — the element is placed by time (`anchor`) |
| `elements.titles[].start` | `number` | Where the element starts in the video, s; absent — hidden (its words were cut) |
| `elements.titles[].end` | `number` | Where the element ends in the video, s |
| `elements.titles[].anchor` | `object` | Placed by time: fragment, second of its source and duration in the video, s |
| `elements.titles[].anchor.blockId` _(required)_ | `string` |     |
| `elements.titles[].anchor.at` _(required)_ | `number` |     |
| `elements.titles[].anchor.duration` _(required)_ | `number` |     |
| `elements.markers` _(required)_ | `object[]` | Author markers on the timeline (not shown in the video); hidden markers are omitted |
| `elements.markers[].id` _(required)_ | `string` |     |
| `elements.markers[].time` _(required)_ | `number` |     |
| `elements.markers[].label` _(required)_ | `string \| null` |     |
| `elements.markers[].color` _(required)_ | `string` | — `cyan` · `yellow` · `red` · `green` · `violet` |
| `elements.sounds` _(required)_ | `object[]` | Sound effects placed by the author; cut-off ones are omitted |
| `elements.sounds[].id` _(required)_ | `string` |     |
| `elements.sounds[].kind` _(required)_ | `string` | — `whoosh` · `pop` · `ding` · `click` · `swipe` · `rise` · `boom` · `chime` |
| `elements.sounds[].time` _(required)_ | `number` |     |

`EditOperation`

| Field | Type | Description |
| --- | --- | --- |

`EditsResult`

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` |     |
| `applied` _(required)_ | `integer` |     |
| `skipped` _(required)_ | `object[]` |     |
| `skipped[].index` _(required)_ | `integer` |     |
| `skipped[].op` _(required)_ | `string` |     |
| `skipped[].reason` _(required)_ | `string` |     |
| `durationSec` _(required)_ | `number` |     |

`TranscribeRequest`

| Field | Type | Description |
| --- | --- | --- |
| `sources` | `integer[]` | Source indexes (default — every source with sound whose speech is not recognised yet) — 1–40 |
| `language` | `string` | Speech language: auto or a two-letter code |
| `confirmCost` | `integer` | Price you agree to pay. Without it the response is a `Quote` and nothing starts |

`TranscribeJob`

| Field | Type | Description |
| --- | --- | --- |
| `id` _(required)_ | `string` |     |
| `status` _(required)_ | `string` | — `processing` · `ready` · `failed` |
| `sources` _(required)_ | `integer[]` |     |
| `createdAt` _(required)_ | `string` |     |
| `error` | `string` |     |

`AssistantResult`

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` |     |
| `summary` _(required)_ | `string` |     |
| `applied` _(required)_ | `integer` |     |
| `charged` _(required)_ | `integer` | Tokens charged from the wallet (whole) |
| `costTokens` | `number` | Actual cost of the command in tokens (fractional); the remainder accumulates with the project |
| `durationSec` _(required)_ | `number` |     |

`TranslateResult`

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` |     |
| `phrases` _(required)_ | `integer` |     |
| `charged` _(required)_ | `integer` | Tokens charged from the wallet (whole) |
| `costTokens` | `number` | Actual cost of the translation in tokens (fractional) |

`StyleRequest`

| Field | Type | Description |
| --- | --- | --- |
| `templateId` | `string` |     |
| `fontId` | `string` |     |
| `colorId` | `string` |     |
| `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` |     |
| `params.soundEffects` | `boolean` |     |
| `params.introZoom` | `boolean` |     |

`DirectorBriefRequest`

| Field | Type | Description |
| --- | --- | --- |
| `mode` | `string` | Video type — `auto` · `talking` · `story` · `product` · `aesthetic` |
| `pace` | `string` | — `calm` · `balanced` · `dynamic` |
| `effects` | `string` | — `off` · `light` · `bold` |
| `targetDurationSec` | `integer \| null` | Target length, s; null — the director decides (up to 90 s) — 10–180 |
| `hook` | `boolean` | Open with a hook and an on-screen title |
| `plaques` | `boolean` |     |
| `emoji` | `boolean` |     |
| `cameraMotion` | `boolean` | Zooms and camera moves |

`DirectorBrief`

| Field | Type | Description |
| --- | --- | --- |
| `projectId` _(required)_ | `integer` |     |
| `rev` _(required)_ | `integer` |     |
| `settings` _(required)_ | `Record<string, object>` |     |
| `sources` _(required)_ | `object[]` |     |
| `sources[].index` _(required)_ | `integer` |     |
| `sources[].kind` _(required)_ | `string` |     |
| `sources[].durationSec` _(required)_ | `number` |     |
| `sources[].fileName` _(required)_ | `string \| null` |     |
| `sources[].words` _(required)_ | `integer` |     |
| `speechRecognized` _(required)_ | `boolean` |     |
| `material` _(required)_ | `string` | The material: sources, speech by word ids, pauses, retakes, wishes |
| `guide` _(required)_ | `string[]` | General tips (the Slicer director rules run on the server when you submit the plan) |
| `planSchema` _(required)_ | `Record<string, object>` | JSON schema of the plan for submitDirectorPlan |
| `scenesSchema` _(required)_ | `Record<string, object>` | JSON schema of what you saw in the frames |
| `next` _(required)_ | `string[]` |     |

`SourceFrames`

| Field | Type | Description |
| --- | --- | --- |
| `sourceIndex` _(required)_ | `integer` |     |
| `durationSec` _(required)_ | `number` |     |
| `frames` _(required)_ | `object[]` |     |
| `frames[].time` _(required)_ | `number` |     |
| `frames[].mimeType` _(required)_ | `"image/jpeg"` |     |
| `frames[].data` _(required)_ | `string` | JPEG, base64, 512 px on the longer side |

`DirectorPlanRequest`

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` | rev from directorBrief / transcript |
| `plan` _(required)_ | `Record<string, object>` | The plan in DirectorBrief.planSchema |
| `scenes` | `Record<string, object>[]` | What you saw in the frames, in DirectorBrief.scenesSchema — 0–400 |

`DirectorPlanResult`

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` |     |
| `durationSec` _(required)_ | `number` |     |
| `planSegments` _(required)_ | `integer` |     |
| `fragments` _(required)_ | `integer` |     |
| `fixes` _(required)_ | `string[]` | What Slicer corrected by its director rules |
| `issues` _(required)_ | `object[]` | What you should fix and submit again |
| `issues[].code` _(required)_ | `string` |     |
| `issues[].segment` | `integer` |     |
| `issues[].message` _(required)_ | `string` |     |
| `video` _(required)_ | `string` | The resulting video as text (fragments, words, elements) — check it against the plan |

`DocumentState`

| Field | Type | Description |
| --- | --- | --- |
| `rev` _(required)_ | `integer` |     |
| `durationSec` _(required)_ | `number` |     |
| `document` _(required)_ | `object` | Edit document (EditDocument v1). Fields are only added within v1 |
