Ошибки
Формат ошибок Slicer API, HTTP-статусы и стабильные коды, какие ошибки можно повторять и как обрабатывать их в коде.
На этой странице
Каждая ошибка API приходит с HTTP-статусом и телом одного формата. Решения в коде принимайте по полю code: коды
стабильны внутри v1 и не меняются, а текст message написан для человека — на языке аккаунта — и может уточняться.
{
"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 | слишком много черновиков проектов без ИИ-режиссёра — экспортируйте или удалите лишние |
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 и другие) описаны в
разделе Загрузка файлов.
Все коды
| Код | 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 безопасно: если первый запрос дошёл до сервера, вы получите его ответ, а не
вторую операцию (Идемпотентность).
- Сеть оборвалась, 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 — повтор не поможет, нужно исправить запрос, токен или баланс.
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);
}
}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))