# Ошибки

> Формат ошибок Slicer API, HTTP-статусы и стабильные коды, какие ошибки можно повторять и как обрабатывать их в коде.

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

Каждая ошибка API приходит с HTTP-статусом и телом одного формата. Решения в коде принимайте по полю `code`: коды стабильны внутри v1 и не меняются, а текст `message` написан для человека — на языке аккаунта — и может уточняться.

Ответ с ошибкой:

```json
{
  "ok": false,
  "error": {
    "code": "insufficient_scope",
    "message": "Токену не хватает доступа: Запускать монтаж — тратит токены (montage:run)"
  }
}
```

`Error`

| Поле | Тип | Описание |
| --- | --- | --- |
| `ok` _(обязательное)_ | `false` |     |
| `error` _(обязательное)_ | `object` |     |
| `error.code` _(обязательное)_ | `string` | Стабильный код ошибки в snake\_case |
| `error.message` _(обязательное)_ | `string` | Понятный текст на языке аккаунта |
| `error.fields` | `Record<string, string>` | Ошибки по полям |
| `error.retryAfterSec` | `integer` | Через сколько секунд повторить |

-   `fields` — ошибки по полям тела запроса, если запрос не прошёл проверку: `{ "uploadIds": "…" }`.
-   `retryAfterSec` — через сколько секунд повторить (при превышении лимита запросов).

## Статусы

| Статус | Когда | Что делать |
| --- | --- | --- |
| 400 | тело или параметры не прошли проверку, файл не подходит | исправить запрос; подробности — в `fields` и `message` |
| 401 | нет токена, токен неверный, отозван или истёк | проверить токен; при `token_expired` и `token_revoked` — выпустить новый |
| 402 | не хватает токенов на балансе или исчерпан лимит расходов токена | пополнить баланс в приложении или поднять лимит токена |
| 403 | у токена нет нужной области доступа | выпустить токен с нужными областями |
| 404 | ресурс не найден или принадлежит другому аккаунту | проверить id; чужие ресурсы для API не существуют |
| 409 | конфликт состояния: цена изменилась, документ устарел, проект занят | перечитать состояние и повторить |
| 410 | ссылка на загрузку истекла или уже использована | создать новую загрузку |
| 429 | превышен лимит запросов | подождать `Retry-After` секунд |
| 5xx | временный сбой сервера или ИИ | повторить позже с тем же `Idempotency-Key` |

## Частые коды

| Код | Статус | Значение |
| --- | --- | --- |
| `unauthorized` | 401 | нет заголовка `Authorization: Bearer` |
| `invalid_token` | 401 | токен не найден или содержит ошибку |
| `token_revoked` | 401 | токен отозван, приложение отключено или аккаунт удалён |
| `token_expired` | 401 | срок токена истёк |
| `insufficient_scope` | 403 | у токена нет нужной области; список недостающих — в `message` |
| `not_found` | 404 | проект, загрузка или вебхук не найдены |
| `insufficient_tokens` | 402 | на балансе меньше, чем стоит операция |
| `spend_limit_exceeded` | 402 | операция не помещается в месячный лимит расходов токена |
| `cost_changed` | 409 | `confirmCost` не совпал с текущей ценой — запросите цену заново |
| `stale_document` | 409 | ревизия документа устарела — перечитайте `rev` и повторите правку |
| `busy` | 409 | над проектом уже идёт работа (монтаж, рендер) — дождитесь окончания |
| `not_ready` | 409 | ролик ещё не готов для этой операции |
| `too_many_drafts` | 409 | слишком много черновиков [проектов без ИИ-режиссёра](https://docs.slicer.media/api/montage/#manual) — экспортируйте или удалите лишние |
| `daily_export_limit` | 429 | бесплатные экспорты проектов без ИИ-режиссёра на сегодня закончились — повторите через `retryAfterSec` |
| `manual_disabled` | 503 | проекты без ИИ-режиссёра временно недоступны |
| `request_in_progress` | 409 | запрос с этим `Idempotency-Key` ещё выполняется |
| `bad_idempotency_key` | 409 | `Idempotency-Key` не в формате: 8–128 символов `A–Z a–z 0–9 _ -` |
| `bad_cursor` | 400 | курсор страницы повреждён — начните с первой страницы |
| `platform_requires_app` | 400 | публикация в эту соцсеть возможна только из приложения Slicer |
| `rate_limit_exceeded` | 429 | слишком много запросов в минуту на токен |
| `ai_unavailable` | 503 | ИИ временно недоступен — повторите позже, токены не списаны |
| `api_disabled` | 503 | публичное API временно выключено |
| `internal_server_error` | 500 | непредвиденная ошибка — повторите позже |

Ошибки загрузки файлов (`unsupported_format`, `file_too_large`, `resolution_too_low`, `link_expired` и другие) описаны в разделе [Загрузка файлов](https://docs.slicer.media/api/uploads/).

## Все коды

| Код | HTTP | Когда |
| --- | --- | --- |
| `unauthorized` | 401 | Нет токена или он неверный |
| `token_revoked` | 401 | Токен отозван или приложение отключено от аккаунта |
| `token_expired` | 401 | Срок токена истёк |
| `insufficient_scope` | 403 | У токена нет нужной области доступа (см. `WWW-Authenticate`) |
| `forbidden` | 403 | Действие недоступно этому аккаунту |
| `not_found` | 404 | Объекта нет или он принадлежит другому аккаунту |
| `bad_request` | 400 | Неверный запрос; ошибки по полям — в `fields` |
| `conflict` | 409 | Объект в состоянии, которое не позволяет действие |
| `stale_document` | 409 | Документ изменился: получите свежую `rev` и повторите |
| `busy` | 409 | Проект занят идущим запуском |
| `not_ready` | 409 | Ролик или загрузка ещё не готовы |
| `request_in_progress` | 409 | Запрос с этим `Idempotency-Key` ещё выполняется |
| `cost_changed` | 409 | Цена изменилась: подтвердите новую `cost` |
| `insufficient_tokens` | 402 | На балансе не хватает токенов |
| `spend_limit_exceeded` | 402 | Достигнут месячный лимит расходов токена |
| `rate_limit_exceeded` | 429 | Слишком много запросов: подождите `Retry-After` секунд |
| `file_too_large` | 400 | Файл больше допустимого для своего вида |
| `unsupported_format` | 400 | Формат файла не поддерживается |
| `empty_file` | 400 | Файл пустой |
| `parts_missing` | 400 | До `complete` загружены не все части |
| `upload_not_active` | 409 | Загрузка уже завершена или отменена |
| `link_expired` | 410 | Срок одноразовой ссылки загрузки истёк |
| `link_used` | 410 | Одноразовая ссылка загрузки уже использована |
| `too_many_imports` | 429 | Слишком много одновременных загрузок по адресу |
| `platform_requires_app` | 400 | Публикация в эту платформу — только из приложения Slicer |
| `ai_unavailable` | 503 | ИИ-сервис временно недоступен: повторите позже |

## Что можно повторять

Повторять **с тем же** `Idempotency-Key` безопасно: если первый запрос дошёл до сервера, вы получите его ответ, а не вторую операцию ([Идемпотентность](https://docs.slicer.media/api/idempotency/)).

-   **Сеть оборвалась, 500, 502, 503, 504** — повтор с экспоненциальной паузой: 1, 2, 4, 8 секунд и так далее, не больше 5–6 попыток.
-   **429** — подождать столько секунд, сколько указано в `Retry-After` (или `retryAfterSec`), затем повторить.
-   **409 `request_in_progress`** — первый запрос ещё выполняется; подождите пару секунд и повторите с тем же ключом.
-   **409 `cost_changed`, `stale_document`** — не повторять вслепую: перечитать цену или документ, при необходимости показать пользователю и отправить запрос заново.
-   **400, 401, 402, 403, 404** — повтор не поможет, нужно исправить запрос, токен или баланс.

**JavaScript**

slicer.mjs:

```js
import { randomUUID } from 'node:crypto';

export class SlicerError extends Error {
  constructor(status, error) {
    super(`${status} ${error.code}: ${error.message}`);
    this.status = status;
    this.code = error.code;
    this.fields = error.fields;
    this.retryAfterSec = error.retryAfterSec;
  }
}

const RETRYABLE = new Set([429, 500, 502, 503, 504]);
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

/** Запрос к API с повторами: один Idempotency-Key на все попытки. */
export async function slicer(method, path, body, { attempts = 5 } = {}) {
  const key = method === 'POST' ? randomUUID() : undefined;
  for (let attempt = 1; ; attempt += 1) {
    let response;
    try {
      response = await fetch(`https://api.slicer.media/v1${path}`, {
        method,
        headers: {
          Authorization: `Bearer ${process.env.SLICER_TOKEN}`,
          ...(body ? { 'Content-Type': 'application/json' } : {}),
          ...(key ? { 'Idempotency-Key': key } : {}),
        },
        body: body ? JSON.stringify(body) : undefined,
      });
    } catch (networkError) {
      if (attempt >= attempts) throw networkError;
      await sleep(2 ** attempt * 500);
      continue;
    }
    const json = await response.json();
    if (json.ok) return json.data;
    const retry = RETRYABLE.has(response.status) || json.error.code === 'request_in_progress';
    if (!retry || attempt >= attempts) throw new SlicerError(response.status, json.error);
    const wait = Number(response.headers.get('Retry-After')) || 2 ** attempt * 0.5;
    await sleep(wait * 1000);
  }
}
```

**Python**

slicer.py:

```python
import os
import time
import uuid

import requests

RETRYABLE = {429, 500, 502, 503, 504}

class SlicerError(Exception):
    def __init__(self, status, error):
        super().__init__(f"{status} {error['code']}: {error['message']}")
        self.status = status
        self.code = error["code"]
        self.fields = error.get("fields")

def slicer(method, path, json=None, attempts=5):
    """Запрос к API с повторами: один Idempotency-Key на все попытки."""
    headers = {"Authorization": f"Bearer {os.environ['SLICER_TOKEN']}"}
    if method == "POST":
        headers["Idempotency-Key"] = str(uuid.uuid4())
    for attempt in range(1, attempts + 1):
        try:
            response = requests.request(
                method, f"https://api.slicer.media/v1{path}", json=json, headers=headers, timeout=60
            )
        except requests.ConnectionError:
            if attempt == attempts:
                raise
            time.sleep(2 ** attempt * 0.5)
            continue
        body = response.json()
        if body["ok"]:
            return body["data"]
        error = body["error"]
        retry = response.status_code in RETRYABLE or error["code"] == "request_in_progress"
        if not retry or attempt == attempts:
            raise SlicerError(response.status_code, error)
        time.sleep(float(response.headers.get("Retry-After") or 2 ** attempt * 0.5))
```

## Что дальше

[ИдемпотентностьКак безопасно повторять запросы.](https://docs.slicer.media/api/idempotency/)[ЛимитыСколько запросов в минуту и заголовки X-RateLimit.](https://docs.slicer.media/api/rate-limits/)
