# Правки

> Эндпоинты правок в Slicer API — текст ролика с id слов, операции, документ монтажа, субтитры из речи, ИИ-помощник, перевод, стиль, свой режиссёр.

Источник: https://docs.slicer.media/api/reference/editing/

Правки меняют ролик так же, как редактор приложения, и сохраняются сразу. Бесплатны все, кроме субтитров из речи, ИИ-помощника и перевода субтитров — они стоят по себестоимости ИИ ([Субтитры из речи](https://docs.slicer.media/api/editing/#transcribe), [ИИ-помощник](https://docs.slicer.media/api/editing/#assistant)). Каждая правка передаёт ревизию `rev`: если ролик успели изменить, ответ — `409 stale_document`. Как этим пользоваться — в руководстве [Правки ролика](https://docs.slicer.media/api/editing/).

## Ролик текстом с id слов

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

- **Области доступа:** `projects:read`
- **Лимит:** обычный

### Параметры пути

| Поле | Тип | Описание |
| --- | --- | --- |
| `id` _(обязательное)_ | `integer` | — ≥ 1 |

### Запрос

**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()
```

### Ответ

**Поля ответа: `Transcript` (90)**

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` | Ревизия документа: передайте её в правки |
| `durationSec` _(обязательное)_ | `number` |     |
| `text` _(обязательное)_ | `string` | Ролик текстом для LLM: фрагменты со словами `w<id>`, вырезанное — в \[скобках\], графика |
| `blocks` _(обязательное)_ | `object[]` |     |
| `blocks[].id` _(обязательное)_ | `string` |     |
| `blocks[].position` _(обязательное)_ | `integer` |     |
| `blocks[].sourceIndex` _(обязательное)_ | `integer` |     |
| `blocks[].kind` _(обязательное)_ | `string` | — `words` · `range` |
| `blocks[].words` _(обязательное)_ | `object[]` |     |
| `blocks[].words[].id` _(обязательное)_ | `integer` |     |
| `blocks[].words[].text` _(обязательное)_ | `string` |     |
| `blocks[].words[].cut` _(обязательное)_ | `boolean` |     |
| `blocks[].words[].accent` _(обязательное)_ | `boolean` |     |
| `elements` _(обязательное)_ | `object` |     |
| `elements.hook` _(обязательное)_ | `string \| null` |     |
| `elements.plaques` _(обязательное)_ | `object[]` |     |
| `elements.plaques[].id` _(обязательное)_ | `string` |     |
| `elements.plaques[].kind` _(обязательное)_ | `string` |     |
| `elements.plaques[].title` _(обязательное)_ | `string` |     |
| `elements.plaques[].fromWordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.plaques[].toWordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.plaques[].start` | `number` | Где элемент начинается в ролике, с; нет — не показывается (его слова вырезаны) |
| `elements.plaques[].end` | `number` | Где элемент заканчивается в ролике, с |
| `elements.plaques[].anchor` | `object` | Привязка ко времени: фрагмент, секунда его исходника и длительность в ролике, с |
| `elements.plaques[].anchor.blockId` _(обязательное)_ | `string` |     |
| `elements.plaques[].anchor.at` _(обязательное)_ | `number` |     |
| `elements.plaques[].anchor.duration` _(обязательное)_ | `number` |     |
| `elements.cards` _(обязательное)_ | `object[]` |     |
| `elements.cards[].id` _(обязательное)_ | `string` |     |
| `elements.cards[].caption` _(обязательное)_ | `string \| null` |     |
| `elements.cards[].fromWordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.cards[].toWordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.cards[].start` | `number` | Где элемент начинается в ролике, с; нет — не показывается (его слова вырезаны) |
| `elements.cards[].end` | `number` | Где элемент заканчивается в ролике, с |
| `elements.cards[].anchor` | `object` | Привязка ко времени: фрагмент, секунда его исходника и длительность в ролике, с |
| `elements.cards[].anchor.blockId` _(обязательное)_ | `string` |     |
| `elements.cards[].anchor.at` _(обязательное)_ | `number` |     |
| `elements.cards[].anchor.duration` _(обязательное)_ | `number` |     |
| `elements.emoji` _(обязательное)_ | `object[]` |     |
| `elements.emoji[].id` _(обязательное)_ | `string` |     |
| `elements.emoji[].emoji` _(обязательное)_ | `string` |     |
| `elements.emoji[].wordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.emoji[].start` | `number` | Где элемент начинается в ролике, с; нет — не показывается (его слова вырезаны) |
| `elements.emoji[].end` | `number` | Где элемент заканчивается в ролике, с |
| `elements.emoji[].anchor` | `object` | Привязка ко времени: фрагмент, секунда его исходника и длительность в ролике, с |
| `elements.emoji[].anchor.blockId` _(обязательное)_ | `string` |     |
| `elements.emoji[].anchor.at` _(обязательное)_ | `number` |     |
| `elements.emoji[].anchor.duration` _(обязательное)_ | `number` |     |
| `elements.zooms` _(обязательное)_ | `object[]` |     |
| `elements.zooms[].id` _(обязательное)_ | `string` |     |
| `elements.zooms[].fromWordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.zooms[].toWordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.zooms[].start` | `number` | Где элемент начинается в ролике, с; нет — не показывается (его слова вырезаны) |
| `elements.zooms[].end` | `number` | Где элемент заканчивается в ролике, с |
| `elements.zooms[].anchor` | `object` | Привязка ко времени: фрагмент, секунда его исходника и длительность в ролике, с |
| `elements.zooms[].anchor.blockId` _(обязательное)_ | `string` |     |
| `elements.zooms[].anchor.at` _(обязательное)_ | `number` |     |
| `elements.zooms[].anchor.duration` _(обязательное)_ | `number` |     |
| `elements.titles` _(обязательное)_ | `object[]` | Свободные текстовые слои (титры) |
| `elements.titles[].id` _(обязательное)_ | `string` |     |
| `elements.titles[].text` _(обязательное)_ | `string` |     |
| `elements.titles[].font` _(обязательное)_ | `string` |     |
| `elements.titles[].size` _(обязательное)_ | `number` |     |
| `elements.titles[].color` _(обязательное)_ | `string` |     |
| `elements.titles[].background` _(обязательное)_ | `string` | — `none` · `box` · `pill` |
| `elements.titles[].backgroundColor` | `string` |     |
| `elements.titles[].outline` | `boolean` |     |
| `elements.titles[].shadow` | `boolean` |     |
| `elements.titles[].align` _(обязательное)_ | `string` | — `left` · `center` · `right` |
| `elements.titles[].x` _(обязательное)_ | `number` |     |
| `elements.titles[].y` _(обязательное)_ | `number` |     |
| `elements.titles[].animIn` | `string` | — `fade` · `slide` · `pop` · `typewriter` |
| `elements.titles[].animOut` | `string` | — `fade` · `slide` · `pop` |
| `elements.titles[].fromWordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.titles[].toWordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.titles[].start` | `number` | Где элемент начинается в ролике, с; нет — не показывается (его слова вырезаны) |
| `elements.titles[].end` | `number` | Где элемент заканчивается в ролике, с |
| `elements.titles[].anchor` | `object` | Привязка ко времени: фрагмент, секунда его исходника и длительность в ролике, с |
| `elements.titles[].anchor.blockId` _(обязательное)_ | `string` |     |
| `elements.titles[].anchor.at` _(обязательное)_ | `number` |     |
| `elements.titles[].anchor.duration` _(обязательное)_ | `number` |     |
| `elements.markers` _(обязательное)_ | `object[]` | Маркеры автора на таймлайне (в ролик не попадают); отрезанные не показываются |
| `elements.markers[].id` _(обязательное)_ | `string` |     |
| `elements.markers[].time` _(обязательное)_ | `number` |     |
| `elements.markers[].label` _(обязательное)_ | `string \| null` |     |
| `elements.markers[].color` _(обязательное)_ | `string` | — `cyan` · `yellow` · `red` · `green` · `violet` |
| `elements.sounds` _(обязательное)_ | `object[]` | Звуковые эффекты, которые поставил автор; отрезанные не показываются |
| `elements.sounds[].id` _(обязательное)_ | `string` |     |
| `elements.sounds[].kind` _(обязательное)_ | `string` | — `whoosh` · `pop` · `ding` · `click` · `swipe` · `rise` · `boom` · `chime` |
| `elements.sounds[].time` _(обязательное)_ | `number` |     |

Пример ответа:

```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
        }
      ]
    }
  }
}
```

## Применить правки

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

- **Области доступа:** `projects:write`
- **Лимит:** обычный

Бесплатно. Id слов — из `GET …/transcript`. Неприменимые операции пропускаются и перечисляются в `skipped`.

### Параметры пути

| Поле | Тип | Описание |
| --- | --- | --- |
| `id` _(обязательное)_ | `integer` | — ≥ 1 |

### Тело запроса `EditsRequest`

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` | Ревизия из GET …/transcript или …/document |
| `operations` _(обязательное)_ | `EditOperation[]` | — 1–80 |

### Запрос

**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()
```

### Ответ

**Поля ответа: `EditsResult` (7)**

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` |     |
| `applied` _(обязательное)_ | `integer` |     |
| `skipped` _(обязательное)_ | `object[]` |     |
| `skipped[].index` _(обязательное)_ | `integer` |     |
| `skipped[].op` _(обязательное)_ | `string` |     |
| `skipped[].reason` _(обязательное)_ | `string` |     |
| `durationSec` _(обязательное)_ | `number` |     |

Пример ответа:

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

## Субтитры из речи

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

- **Области доступа:** `montage:run`
- **Лимит:** платные операции
- **Цена:** _тратит токены_

Распознаёт речь исходников проекта (ручной проект или новые исходники) — по себестоимости ИИ за минуту звука. Без `confirmCost` — цена. Идёт в фоне: ход — GET …/transcribe; когда готово, слова — в тексте ролика, субтитры идут за речью.

> **Важно.** Без `confirmCost` отвечает ценой (`Quote`) и ничего не запускает.

### Параметры пути

| Поле | Тип | Описание |
| --- | --- | --- |
| `id` _(обязательное)_ | `integer` | — ≥ 1 |

### Тело запроса `TranscribeRequest`

| Поле | Тип | Описание |
| --- | --- | --- |
| `sources` | `integer[]` | Номера исходников (по умолчанию — все со звуком, где речь ещё не распознана) — 1–40 |
| `language` | `string` | Язык речи: auto или двухбуквенный код |
| `confirmCost` | `integer` | Цена, на которую вы согласны. Без неё ответ — `Quote`, ничего не запускается |

### Запрос

**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()
```

### Ответ

Один из вариантов: Quote, TranscribeJob

**Поля ответа: `Quote` (8)**

| Поле | Тип | Описание |
| --- | --- | --- |
| `status` _(обязательное)_ | `"quote"` |     |
| `cost` _(обязательное)_ | `integer` | Цена в токенах; повторите запрос с `confirmCost`, равным ей. У операции по себестоимости (`atCost`) — предельная: спишется фактическая себестоимость, не больше |
| `free` _(обязательное)_ | `boolean` | Операция бесплатна (первый ролик, бесплатный повтор) |
| `estimate` | `number` | Операция по себестоимости: ожидаемая цена в токенах (дробно) |
| `atCost` | `boolean` | Цена — себестоимость ИИ-сервисов без наценки: списывается фактическая, не больше `cost` |
| `available` _(обязательное)_ | `integer` | Сколько токенов доступно сейчас |
| `enough` _(обязательное)_ | `boolean` | Токенов хватает |
| `spendLimitLeft` _(обязательное)_ | `integer \| null` | Остаток месячного лимита расходов токена; `null` — без лимита |

**Поля ответа: `TranscribeJob` (5)**

| Поле | Тип | Описание |
| --- | --- | --- |
| `id` _(обязательное)_ | `string` |     |
| `status` _(обязательное)_ | `string` | — `processing` · `ready` · `failed` |
| `sources` _(обязательное)_ | `integer[]` |     |
| `createdAt` _(обязательное)_ | `string` |     |
| `error` | `string` |     |

Пример ответа:

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

## Задачи распознавания речи

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

- **Области доступа:** `projects:read`
- **Лимит:** обычный

### Параметры пути

| Поле | Тип | Описание |
| --- | --- | --- |
| `id` _(обязательное)_ | `integer` | — ≥ 1 |

### Запрос

**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()
```

### Ответ

Пример ответа:

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

## Команда ИИ-помощнику

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

- **Области доступа:** `montage:run`
- **Лимит:** платные операции
- **Цена:** _тратит токены_

Списывается, только если что-то изменилось. Результат сохраняется.

> **Важно.** Без `confirmCost` отвечает ценой (`Quote`) и ничего не запускает.

### Параметры пути

| Поле | Тип | Описание |
| --- | --- | --- |
| `id` _(обязательное)_ | `integer` | — ≥ 1 |

### Тело запроса `AssistantRequest`

| Поле | Тип | Описание |
| --- | --- | --- |
| `command` _(обязательное)_ | `string` | Что поменять, на любом языке — ≤ 500 |
| `rev` _(обязательное)_ | `integer` |     |
| `confirmCost` | `integer` | Цена, на которую вы согласны. Без неё ответ — `Quote`, ничего не запускается |

### Запрос

**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()
```

### Ответ

Один из вариантов: Quote, AssistantResult

**Поля ответа: `Quote` (8)**

| Поле | Тип | Описание |
| --- | --- | --- |
| `status` _(обязательное)_ | `"quote"` |     |
| `cost` _(обязательное)_ | `integer` | Цена в токенах; повторите запрос с `confirmCost`, равным ей. У операции по себестоимости (`atCost`) — предельная: спишется фактическая себестоимость, не больше |
| `free` _(обязательное)_ | `boolean` | Операция бесплатна (первый ролик, бесплатный повтор) |
| `estimate` | `number` | Операция по себестоимости: ожидаемая цена в токенах (дробно) |
| `atCost` | `boolean` | Цена — себестоимость ИИ-сервисов без наценки: списывается фактическая, не больше `cost` |
| `available` _(обязательное)_ | `integer` | Сколько токенов доступно сейчас |
| `enough` _(обязательное)_ | `boolean` | Токенов хватает |
| `spendLimitLeft` _(обязательное)_ | `integer \| null` | Остаток месячного лимита расходов токена; `null` — без лимита |

**Поля ответа: `AssistantResult` (6)**

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` |     |
| `summary` _(обязательное)_ | `string` |     |
| `applied` _(обязательное)_ | `integer` |     |
| `charged` _(обязательное)_ | `integer` | Списано токенов с кошелька (целые) |
| `costTokens` | `number` | Фактическая себестоимость команды в токенах (дробно); остаток копится у проекта |
| `durationSec` _(обязательное)_ | `number` |     |

Пример ответа:

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

## Перевести субтитры

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

- **Области доступа:** `montage:run`
- **Лимит:** платные операции
- **Цена:** _тратит токены_

> **Важно.** Без `confirmCost` отвечает ценой (`Quote`) и ничего не запускает.

### Параметры пути

| Поле | Тип | Описание |
| --- | --- | --- |
| `id` _(обязательное)_ | `integer` | — ≥ 1 |

### Тело запроса `TranslateRequest`

| Поле | Тип | Описание |
| --- | --- | --- |
| `language` _(обязательное)_ | `string` | — `en` · `es` · `pt` · `de` · `fr` · `it` · `pl` · `tr` · `id` · `uk` · `ru` |
| `rev` _(обязательное)_ | `integer` |     |
| `confirmCost` | `integer` | Цена, на которую вы согласны. Без неё ответ — `Quote`, ничего не запускается |

### Запрос

**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()
```

### Ответ

Один из вариантов: Quote, TranslateResult

**Поля ответа: `Quote` (8)**

| Поле | Тип | Описание |
| --- | --- | --- |
| `status` _(обязательное)_ | `"quote"` |     |
| `cost` _(обязательное)_ | `integer` | Цена в токенах; повторите запрос с `confirmCost`, равным ей. У операции по себестоимости (`atCost`) — предельная: спишется фактическая себестоимость, не больше |
| `free` _(обязательное)_ | `boolean` | Операция бесплатна (первый ролик, бесплатный повтор) |
| `estimate` | `number` | Операция по себестоимости: ожидаемая цена в токенах (дробно) |
| `atCost` | `boolean` | Цена — себестоимость ИИ-сервисов без наценки: списывается фактическая, не больше `cost` |
| `available` _(обязательное)_ | `integer` | Сколько токенов доступно сейчас |
| `enough` _(обязательное)_ | `boolean` | Токенов хватает |
| `spendLimitLeft` _(обязательное)_ | `integer \| null` | Остаток месячного лимита расходов токена; `null` — без лимита |

**Поля ответа: `TranslateResult` (4)**

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` |     |
| `phrases` _(обязательное)_ | `integer` |     |
| `charged` _(обязательное)_ | `integer` | Списано токенов с кошелька (целые) |
| `costTokens` | `number` | Фактическая себестоимость перевода в токенах (дробно) |

Пример ответа:

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

## Автобаланс и подбор цвета (бесплатно)

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

- **Области доступа:** `projects:write`
- **Лимит:** обычный

Без ИИ: статистика 10 кадров фрагмента → значения «Настроить» (экспозиция, контраст, насыщенность, баланс белого). Результат применяется и сохраняется.

### Параметры пути

| Поле | Тип | Описание |
| --- | --- | --- |
| `id` _(обязательное)_ | `integer` | — ≥ 1 |

### Тело запроса `AutoColorRequest`

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` | Ревизия из GET …/transcript или …/document |
| `blockId` _(обязательное)_ | `string` | Id фрагмента: который поправить (`auto`) или образец (`match`) — ≤ 64 |
| `mode` _(обязательное)_ | `string` | `auto` — выровнять этот фрагмент; `match` — сделать остальные похожими на него (с его фильтром и LUT) — `auto` · `match` |

### Запрос

**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()
```

### Ответ

**Поля ответа: `AutoColorResult` (5)**

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` |     |
| `mode` _(обязательное)_ | `string` | — `auto` · `match` |
| `adjust` _(обязательное)_ | `Record<string, object>` | Новые значения «Настроить» по id фрагмента |
| `applied` _(обязательное)_ | `integer` |     |
| `durationSec` _(обязательное)_ | `number` |     |

Пример ответа:

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

## Сменить стиль

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

- **Области доступа:** `projects:write`
- **Лимит:** обычный

### Параметры пути

| Поле | Тип | Описание |
| --- | --- | --- |
| `id` _(обязательное)_ | `integer` | — ≥ 1 |

### Тело запроса `StyleRequest`

| Поле | Тип | Описание |
| --- | --- | --- |
| `templateId` | `string` |     |
| `fontId` | `string` |     |
| `colorId` | `string` |     |
| `subtitles` | `object` | Вид субтитров; частичная правка сохраняет остальные поля. Новое `design` без `highlight`/`animation`/`words` возвращает их к `auto` |
| `subtitles.enabled` | `boolean` |     |
| `subtitles.design` | `string` | — `dynamic` · `karaoke` · `classic` · `minimal` · `pop` · `box` · `word` |
| `subtitles.size` | `string` | — `small` · `medium` · `large` |
| `subtitles.position` | `string` | `auto` — каждая фраза там, где в кадре свободнее (не на лице, хуке, плашках, титрах) — `auto` · `top` · `center` · `bottom` |
| `subtitles.speakerColors` | `boolean` | Слова каждого говорящего — своим цветом |
| `subtitles.highlight` | `string` | Слово, которое звучит: цвет, увеличение, плашка, подчёркивание; `auto` — как задумано оформлением — `auto` · `none` · `color` · `pop` · `box` · `underline` |
| `subtitles.animation` | `string` | Появление фразы; `words` — по словам, по мере речи — `auto` · `none` · `fade` · `pop` · `slide` · `words` |
| `subtitles.words` | `string` | Сколько слов на экране сразу — `auto` · `one` · `few` · `line` · `phrase` |
| `subtitles.letterCase` | `string` | Регистр; `auto` — как задумано шрифтом — `auto` · `upper` · `normal` |
| `params` | `object` |     |
| `params.soundEffects` | `boolean` |     |
| `params.introZoom` | `boolean` |     |

### Запрос

**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()
```

### Ответ

**Поля ответа: `Project` (26)**

| Поле | Тип | Описание |
| --- | --- | --- |
| `id` _(обязательное)_ | `integer` |     |
| `title` _(обязательное)_ | `string` |     |
| `status` _(обязательное)_ | `string` | `draft` — проект «Сам в редакторе» до первого экспорта: правьте операциями и экспортируйте — `queued` · `processing` · `succeeded` · `failed` · `cancelled` · `draft` |
| `mode` _(обязательное)_ | `string` | `ai` — монтаж ИИ-режиссёром; `manual` — материалы на таймлайне как есть, бесплатно (платны только ИИ-операции) — `ai` · `manual` |
| `stage` _(обязательное)_ | `string \| null` |     |
| `progress` _(обязательное)_ | `integer` | Прогресс текущего запуска, % |
| `client` _(обязательное)_ | `string \| null` | Где создан: web, mobile, api, mcp |
| `settings` _(обязательное)_ | `object` | Настройки монтажа (см. MontageSettingsInput) |
| `brief` _(обязательное)_ | `string \| null` |     |
| `isFree` _(обязательное)_ | `boolean` |     |
| `watermark` _(обязательное)_ | `boolean` |     |
| `tokensCost` _(обязательное)_ | `integer` |     |
| `error` _(обязательное)_ | `object \| null` |     |
| `error.code` _(обязательное)_ | `string` |     |
| `error.message` _(обязательное)_ | `string` |     |
| `durationSec` _(обязательное)_ | `number \| null` |     |
| `versionChoice` _(обязательное)_ | `integer[] \| null` | Id версий, ждущих выбора; монтаж отдаёт одну версию, поэтому обычно `null` |
| `result` _(обязательное)_ | `object \| null` |     |
| `result.url` _(обязательное)_ | `string (uri)` | Скачать с токеном (поддерживается Range) |
| `result.posterUrl` _(обязательное)_ | `string (uri) \| null` |     |
| `result.container` _(обязательное)_ | `string` |     |
| `result.info` _(обязательное)_ | `object \| null` |     |
| `appUrl` _(обязательное)_ | `string (uri)` | Проект в веб-приложении Slicer |
| `sourcesAvailable` _(обязательное)_ | `boolean` | Исходники хранятся 7 дней после монтажа |
| `createdAt` _(обязательное)_ | `string` | Дата и время ISO 8601, UTC |
| `finishedAt` _(обязательное)_ | `string \| null` |     |

Пример ответа:

```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"
  }
}
```

## Свой режиссёр

Ваш ИИ-агент монтирует ролик сам — бесплатно: бриф с материалом и схемами плана и наблюдений, кадры исходников и приём плана с наблюдениями по кадрам. Правила ИИ-режиссёра Slicer применяются к плану на сервере, в ответе — исправления и замечания. Как этим пользоваться — [Свой режиссёр](https://docs.slicer.media/api/editing/#own-director).

### Бриф режиссёра для вашего ИИ-ассистента (бесплатно)

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

- **Области доступа:** `projects:write`
- **Лимит:** обычный

Материал (речь по id слов, паузы, дубли, пожелания), общие советы и схемы плана и наблюдений по кадрам. Ассистент смотрит кадры через sourceFrames и присылает план вместе с тем, что увидел, в submitDirectorPlan — правила режиссёра Slicer работают там, на сервере. Настройки, если переданы, сохраняются в проекте.

#### Параметры пути

| Поле | Тип | Описание |
| --- | --- | --- |
| `id` _(обязательное)_ | `integer` | — ≥ 1 |

#### Тело запроса `DirectorBriefRequest`

| Поле | Тип | Описание |
| --- | --- | --- |
| `mode` | `string` | Тип ролика — `auto` · `talking` · `story` · `product` · `aesthetic` |
| `pace` | `string` | — `calm` · `balanced` · `dynamic` |
| `effects` | `string` | — `off` · `light` · `bold` |
| `targetDurationSec` | `integer \| null` | Длина ролика, с; null — решает режиссёр (до 90 с) — 10–180 |
| `hook` | `boolean` | Хук и заголовок в начале |
| `plaques` | `boolean` |     |
| `emoji` | `boolean` |     |
| `cameraMotion` | `boolean` | Наезды и движение камеры |

#### Запрос

**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()
```

#### Ответ

**Поля ответа: `DirectorBrief` (15)**

| Поле | Тип | Описание |
| --- | --- | --- |
| `projectId` _(обязательное)_ | `integer` |     |
| `rev` _(обязательное)_ | `integer` |     |
| `settings` _(обязательное)_ | `Record<string, object>` |     |
| `sources` _(обязательное)_ | `object[]` |     |
| `sources[].index` _(обязательное)_ | `integer` |     |
| `sources[].kind` _(обязательное)_ | `string` |     |
| `sources[].durationSec` _(обязательное)_ | `number` |     |
| `sources[].fileName` _(обязательное)_ | `string \| null` |     |
| `sources[].words` _(обязательное)_ | `integer` |     |
| `speechRecognized` _(обязательное)_ | `boolean` |     |
| `material` _(обязательное)_ | `string` | Материал: исходники, речь по id слов, паузы, дубли, пожелания |
| `guide` _(обязательное)_ | `string[]` | Общие советы (правила режиссёра Slicer работают на сервере при приёме плана) |
| `planSchema` _(обязательное)_ | `Record<string, object>` | JSON-схема плана для submitDirectorPlan |
| `scenesSchema` _(обязательное)_ | `Record<string, object>` | JSON-схема того, что вы увидели в кадрах |
| `next` _(обязательное)_ | `string[]` |     |

Пример ответа:

```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"
    ]
  }
}
```

### Кадры исходника (бесплатно)

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

- **Области доступа:** `projects:read`
- **Лимит:** обычный

#### Параметры пути

| Поле | Тип | Описание |
| --- | --- | --- |
| `id` _(обязательное)_ | `integer` | — ≥ 1 |
| `index` _(обязательное)_ | `integer` |     |

#### Параметры запроса

| Поле | Тип | Описание |
| --- | --- | --- |
| `times` | `string` |     |
| `from` | `number` |     |
| `to` | `number` |     |
| `count` | `integer` | — 1–16 |

#### Запрос

**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()
```

#### Ответ

**Поля ответа: `SourceFrames` (6)**

| Поле | Тип | Описание |
| --- | --- | --- |
| `sourceIndex` _(обязательное)_ | `integer` |     |
| `durationSec` _(обязательное)_ | `number` |     |
| `frames` _(обязательное)_ | `object[]` |     |
| `frames[].time` _(обязательное)_ | `number` |     |
| `frames[].mimeType` _(обязательное)_ | `"image/jpeg"` |     |
| `frames[].data` _(обязательное)_ | `string` | JPEG, base64, 512 px по большей стороне |

Пример ответа:

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

### Применить план режиссёра (бесплатно)

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

- **Области доступа:** `projects:write`
- **Лимит:** обычный

К плану на сервере применяются правила режиссёра Slicer (границы слов, дубли, видеоряд по вашим наблюдениям, сдержанность, длительность): план становится документом ролика — новая ревизия, видна в редакторе, — в ответе исправления и замечания. Медиа-карточки по описанию не принимаются (их картинка — платная генерация).

#### Параметры пути

| Поле | Тип | Описание |
| --- | --- | --- |
| `id` _(обязательное)_ | `integer` | — ≥ 1 |

#### Тело запроса `DirectorPlanRequest`

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` | rev из directorBrief или текста ролика |
| `plan` _(обязательное)_ | `Record<string, object>` | План в схеме DirectorBrief.planSchema |
| `scenes` | `Record<string, object>[]` | Что вы увидели в кадрах, в схеме DirectorBrief.scenesSchema — 0–400 |

#### Запрос

**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()
```

#### Ответ

**Поля ответа: `DirectorPlanResult` (10)**

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` |     |
| `durationSec` _(обязательное)_ | `number` |     |
| `planSegments` _(обязательное)_ | `integer` |     |
| `fragments` _(обязательное)_ | `integer` |     |
| `fixes` _(обязательное)_ | `string[]` | Что Slicer поправил по своим правилам режиссёра |
| `issues` _(обязательное)_ | `object[]` | Что поправить и прислать план снова |
| `issues[].code` _(обязательное)_ | `string` |     |
| `issues[].segment` | `integer` |     |
| `issues[].message` _(обязательное)_ | `string` |     |
| `video` _(обязательное)_ | `string` | Получившийся ролик текстом (фрагменты, слова, элементы) — сверьте с замыслом |

Пример ответа:

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

## Документ монтажа

### Документ монтажа

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

- **Области доступа:** `projects:read`
- **Лимит:** обычный

#### Параметры пути

| Поле | Тип | Описание |
| --- | --- | --- |
| `id` _(обязательное)_ | `integer` | — ≥ 1 |

#### Запрос

**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()
```

#### Ответ

**Поля ответа: `DocumentState` (3)**

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` |     |
| `durationSec` _(обязательное)_ | `number` |     |
| `document` _(обязательное)_ | `object` | Документ монтажа (EditDocument v1). Внутри v1 поля только добавляются |

Пример ответа:

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

### Заменить документ монтажа (для продвинутых)

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

- **Области доступа:** `projects:write`
- **Лимит:** обычный

#### Параметры пути

| Поле | Тип | Описание |
| --- | --- | --- |
| `id` _(обязательное)_ | `integer` | — ≥ 1 |

#### Тело запроса `PutDocumentRequest`

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` |     |
| `document` _(обязательное)_ | `object` |     |

#### Запрос

**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()
```

#### Ответ

**Поля ответа: `DocumentState` (3)**

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` |     |
| `durationSec` _(обязательное)_ | `number` |     |
| `document` _(обязательное)_ | `object` | Документ монтажа (EditDocument v1). Внутри v1 поля только добавляются |

Пример ответа:

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

## Схемы

`Transcript`

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` | Ревизия документа: передайте её в правки |
| `durationSec` _(обязательное)_ | `number` |     |
| `text` _(обязательное)_ | `string` | Ролик текстом для LLM: фрагменты со словами `w<id>`, вырезанное — в \[скобках\], графика |
| `blocks` _(обязательное)_ | `object[]` |     |
| `blocks[].id` _(обязательное)_ | `string` |     |
| `blocks[].position` _(обязательное)_ | `integer` |     |
| `blocks[].sourceIndex` _(обязательное)_ | `integer` |     |
| `blocks[].kind` _(обязательное)_ | `string` | — `words` · `range` |
| `blocks[].words` _(обязательное)_ | `object[]` |     |
| `blocks[].words[].id` _(обязательное)_ | `integer` |     |
| `blocks[].words[].text` _(обязательное)_ | `string` |     |
| `blocks[].words[].cut` _(обязательное)_ | `boolean` |     |
| `blocks[].words[].accent` _(обязательное)_ | `boolean` |     |
| `elements` _(обязательное)_ | `object` |     |
| `elements.hook` _(обязательное)_ | `string \| null` |     |
| `elements.plaques` _(обязательное)_ | `object[]` |     |
| `elements.plaques[].id` _(обязательное)_ | `string` |     |
| `elements.plaques[].kind` _(обязательное)_ | `string` |     |
| `elements.plaques[].title` _(обязательное)_ | `string` |     |
| `elements.plaques[].fromWordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.plaques[].toWordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.plaques[].start` | `number` | Где элемент начинается в ролике, с; нет — не показывается (его слова вырезаны) |
| `elements.plaques[].end` | `number` | Где элемент заканчивается в ролике, с |
| `elements.plaques[].anchor` | `object` | Привязка ко времени: фрагмент, секунда его исходника и длительность в ролике, с |
| `elements.plaques[].anchor.blockId` _(обязательное)_ | `string` |     |
| `elements.plaques[].anchor.at` _(обязательное)_ | `number` |     |
| `elements.plaques[].anchor.duration` _(обязательное)_ | `number` |     |
| `elements.cards` _(обязательное)_ | `object[]` |     |
| `elements.cards[].id` _(обязательное)_ | `string` |     |
| `elements.cards[].caption` _(обязательное)_ | `string \| null` |     |
| `elements.cards[].fromWordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.cards[].toWordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.cards[].start` | `number` | Где элемент начинается в ролике, с; нет — не показывается (его слова вырезаны) |
| `elements.cards[].end` | `number` | Где элемент заканчивается в ролике, с |
| `elements.cards[].anchor` | `object` | Привязка ко времени: фрагмент, секунда его исходника и длительность в ролике, с |
| `elements.cards[].anchor.blockId` _(обязательное)_ | `string` |     |
| `elements.cards[].anchor.at` _(обязательное)_ | `number` |     |
| `elements.cards[].anchor.duration` _(обязательное)_ | `number` |     |
| `elements.emoji` _(обязательное)_ | `object[]` |     |
| `elements.emoji[].id` _(обязательное)_ | `string` |     |
| `elements.emoji[].emoji` _(обязательное)_ | `string` |     |
| `elements.emoji[].wordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.emoji[].start` | `number` | Где элемент начинается в ролике, с; нет — не показывается (его слова вырезаны) |
| `elements.emoji[].end` | `number` | Где элемент заканчивается в ролике, с |
| `elements.emoji[].anchor` | `object` | Привязка ко времени: фрагмент, секунда его исходника и длительность в ролике, с |
| `elements.emoji[].anchor.blockId` _(обязательное)_ | `string` |     |
| `elements.emoji[].anchor.at` _(обязательное)_ | `number` |     |
| `elements.emoji[].anchor.duration` _(обязательное)_ | `number` |     |
| `elements.zooms` _(обязательное)_ | `object[]` |     |
| `elements.zooms[].id` _(обязательное)_ | `string` |     |
| `elements.zooms[].fromWordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.zooms[].toWordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.zooms[].start` | `number` | Где элемент начинается в ролике, с; нет — не показывается (его слова вырезаны) |
| `elements.zooms[].end` | `number` | Где элемент заканчивается в ролике, с |
| `elements.zooms[].anchor` | `object` | Привязка ко времени: фрагмент, секунда его исходника и длительность в ролике, с |
| `elements.zooms[].anchor.blockId` _(обязательное)_ | `string` |     |
| `elements.zooms[].anchor.at` _(обязательное)_ | `number` |     |
| `elements.zooms[].anchor.duration` _(обязательное)_ | `number` |     |
| `elements.titles` _(обязательное)_ | `object[]` | Свободные текстовые слои (титры) |
| `elements.titles[].id` _(обязательное)_ | `string` |     |
| `elements.titles[].text` _(обязательное)_ | `string` |     |
| `elements.titles[].font` _(обязательное)_ | `string` |     |
| `elements.titles[].size` _(обязательное)_ | `number` |     |
| `elements.titles[].color` _(обязательное)_ | `string` |     |
| `elements.titles[].background` _(обязательное)_ | `string` | — `none` · `box` · `pill` |
| `elements.titles[].backgroundColor` | `string` |     |
| `elements.titles[].outline` | `boolean` |     |
| `elements.titles[].shadow` | `boolean` |     |
| `elements.titles[].align` _(обязательное)_ | `string` | — `left` · `center` · `right` |
| `elements.titles[].x` _(обязательное)_ | `number` |     |
| `elements.titles[].y` _(обязательное)_ | `number` |     |
| `elements.titles[].animIn` | `string` | — `fade` · `slide` · `pop` · `typewriter` |
| `elements.titles[].animOut` | `string` | — `fade` · `slide` · `pop` |
| `elements.titles[].fromWordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.titles[].toWordId` _(обязательное)_ | `integer \| null` | Id слова; `null` — элемент стоит по времени (`anchor`) |
| `elements.titles[].start` | `number` | Где элемент начинается в ролике, с; нет — не показывается (его слова вырезаны) |
| `elements.titles[].end` | `number` | Где элемент заканчивается в ролике, с |
| `elements.titles[].anchor` | `object` | Привязка ко времени: фрагмент, секунда его исходника и длительность в ролике, с |
| `elements.titles[].anchor.blockId` _(обязательное)_ | `string` |     |
| `elements.titles[].anchor.at` _(обязательное)_ | `number` |     |
| `elements.titles[].anchor.duration` _(обязательное)_ | `number` |     |
| `elements.markers` _(обязательное)_ | `object[]` | Маркеры автора на таймлайне (в ролик не попадают); отрезанные не показываются |
| `elements.markers[].id` _(обязательное)_ | `string` |     |
| `elements.markers[].time` _(обязательное)_ | `number` |     |
| `elements.markers[].label` _(обязательное)_ | `string \| null` |     |
| `elements.markers[].color` _(обязательное)_ | `string` | — `cyan` · `yellow` · `red` · `green` · `violet` |
| `elements.sounds` _(обязательное)_ | `object[]` | Звуковые эффекты, которые поставил автор; отрезанные не показываются |
| `elements.sounds[].id` _(обязательное)_ | `string` |     |
| `elements.sounds[].kind` _(обязательное)_ | `string` | — `whoosh` · `pop` · `ding` · `click` · `swipe` · `rise` · `boom` · `chime` |
| `elements.sounds[].time` _(обязательное)_ | `number` |     |

`EditOperation`

| Поле | Тип | Описание |
| --- | --- | --- |

`EditsResult`

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` |     |
| `applied` _(обязательное)_ | `integer` |     |
| `skipped` _(обязательное)_ | `object[]` |     |
| `skipped[].index` _(обязательное)_ | `integer` |     |
| `skipped[].op` _(обязательное)_ | `string` |     |
| `skipped[].reason` _(обязательное)_ | `string` |     |
| `durationSec` _(обязательное)_ | `number` |     |

`TranscribeRequest`

| Поле | Тип | Описание |
| --- | --- | --- |
| `sources` | `integer[]` | Номера исходников (по умолчанию — все со звуком, где речь ещё не распознана) — 1–40 |
| `language` | `string` | Язык речи: auto или двухбуквенный код |
| `confirmCost` | `integer` | Цена, на которую вы согласны. Без неё ответ — `Quote`, ничего не запускается |

`TranscribeJob`

| Поле | Тип | Описание |
| --- | --- | --- |
| `id` _(обязательное)_ | `string` |     |
| `status` _(обязательное)_ | `string` | — `processing` · `ready` · `failed` |
| `sources` _(обязательное)_ | `integer[]` |     |
| `createdAt` _(обязательное)_ | `string` |     |
| `error` | `string` |     |

`AssistantResult`

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` |     |
| `summary` _(обязательное)_ | `string` |     |
| `applied` _(обязательное)_ | `integer` |     |
| `charged` _(обязательное)_ | `integer` | Списано токенов с кошелька (целые) |
| `costTokens` | `number` | Фактическая себестоимость команды в токенах (дробно); остаток копится у проекта |
| `durationSec` _(обязательное)_ | `number` |     |

`TranslateResult`

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` |     |
| `phrases` _(обязательное)_ | `integer` |     |
| `charged` _(обязательное)_ | `integer` | Списано токенов с кошелька (целые) |
| `costTokens` | `number` | Фактическая себестоимость перевода в токенах (дробно) |

`StyleRequest`

| Поле | Тип | Описание |
| --- | --- | --- |
| `templateId` | `string` |     |
| `fontId` | `string` |     |
| `colorId` | `string` |     |
| `subtitles` | `object` | Вид субтитров; частичная правка сохраняет остальные поля. Новое `design` без `highlight`/`animation`/`words` возвращает их к `auto` |
| `subtitles.enabled` | `boolean` |     |
| `subtitles.design` | `string` | — `dynamic` · `karaoke` · `classic` · `minimal` · `pop` · `box` · `word` |
| `subtitles.size` | `string` | — `small` · `medium` · `large` |
| `subtitles.position` | `string` | `auto` — каждая фраза там, где в кадре свободнее (не на лице, хуке, плашках, титрах) — `auto` · `top` · `center` · `bottom` |
| `subtitles.speakerColors` | `boolean` | Слова каждого говорящего — своим цветом |
| `subtitles.highlight` | `string` | Слово, которое звучит: цвет, увеличение, плашка, подчёркивание; `auto` — как задумано оформлением — `auto` · `none` · `color` · `pop` · `box` · `underline` |
| `subtitles.animation` | `string` | Появление фразы; `words` — по словам, по мере речи — `auto` · `none` · `fade` · `pop` · `slide` · `words` |
| `subtitles.words` | `string` | Сколько слов на экране сразу — `auto` · `one` · `few` · `line` · `phrase` |
| `subtitles.letterCase` | `string` | Регистр; `auto` — как задумано шрифтом — `auto` · `upper` · `normal` |
| `params` | `object` |     |
| `params.soundEffects` | `boolean` |     |
| `params.introZoom` | `boolean` |     |

`DirectorBriefRequest`

| Поле | Тип | Описание |
| --- | --- | --- |
| `mode` | `string` | Тип ролика — `auto` · `talking` · `story` · `product` · `aesthetic` |
| `pace` | `string` | — `calm` · `balanced` · `dynamic` |
| `effects` | `string` | — `off` · `light` · `bold` |
| `targetDurationSec` | `integer \| null` | Длина ролика, с; null — решает режиссёр (до 90 с) — 10–180 |
| `hook` | `boolean` | Хук и заголовок в начале |
| `plaques` | `boolean` |     |
| `emoji` | `boolean` |     |
| `cameraMotion` | `boolean` | Наезды и движение камеры |

`DirectorBrief`

| Поле | Тип | Описание |
| --- | --- | --- |
| `projectId` _(обязательное)_ | `integer` |     |
| `rev` _(обязательное)_ | `integer` |     |
| `settings` _(обязательное)_ | `Record<string, object>` |     |
| `sources` _(обязательное)_ | `object[]` |     |
| `sources[].index` _(обязательное)_ | `integer` |     |
| `sources[].kind` _(обязательное)_ | `string` |     |
| `sources[].durationSec` _(обязательное)_ | `number` |     |
| `sources[].fileName` _(обязательное)_ | `string \| null` |     |
| `sources[].words` _(обязательное)_ | `integer` |     |
| `speechRecognized` _(обязательное)_ | `boolean` |     |
| `material` _(обязательное)_ | `string` | Материал: исходники, речь по id слов, паузы, дубли, пожелания |
| `guide` _(обязательное)_ | `string[]` | Общие советы (правила режиссёра Slicer работают на сервере при приёме плана) |
| `planSchema` _(обязательное)_ | `Record<string, object>` | JSON-схема плана для submitDirectorPlan |
| `scenesSchema` _(обязательное)_ | `Record<string, object>` | JSON-схема того, что вы увидели в кадрах |
| `next` _(обязательное)_ | `string[]` |     |

`SourceFrames`

| Поле | Тип | Описание |
| --- | --- | --- |
| `sourceIndex` _(обязательное)_ | `integer` |     |
| `durationSec` _(обязательное)_ | `number` |     |
| `frames` _(обязательное)_ | `object[]` |     |
| `frames[].time` _(обязательное)_ | `number` |     |
| `frames[].mimeType` _(обязательное)_ | `"image/jpeg"` |     |
| `frames[].data` _(обязательное)_ | `string` | JPEG, base64, 512 px по большей стороне |

`DirectorPlanRequest`

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` | rev из directorBrief или текста ролика |
| `plan` _(обязательное)_ | `Record<string, object>` | План в схеме DirectorBrief.planSchema |
| `scenes` | `Record<string, object>[]` | Что вы увидели в кадрах, в схеме DirectorBrief.scenesSchema — 0–400 |

`DirectorPlanResult`

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` |     |
| `durationSec` _(обязательное)_ | `number` |     |
| `planSegments` _(обязательное)_ | `integer` |     |
| `fragments` _(обязательное)_ | `integer` |     |
| `fixes` _(обязательное)_ | `string[]` | Что Slicer поправил по своим правилам режиссёра |
| `issues` _(обязательное)_ | `object[]` | Что поправить и прислать план снова |
| `issues[].code` _(обязательное)_ | `string` |     |
| `issues[].segment` | `integer` |     |
| `issues[].message` _(обязательное)_ | `string` |     |
| `video` _(обязательное)_ | `string` | Получившийся ролик текстом (фрагменты, слова, элементы) — сверьте с замыслом |

`DocumentState`

| Поле | Тип | Описание |
| --- | --- | --- |
| `rev` _(обязательное)_ | `integer` |     |
| `durationSec` _(обязательное)_ | `number` |     |
| `document` _(обязательное)_ | `object` | Документ монтажа (EditDocument v1). Внутри v1 поля только добавляются |
