Загрузка файлов
Как загрузить видео, фото и аудио в Slicer через API — одноразовая ссылка для curl -T, файл целиком, по частям или импорт по публичному адресу.
На этой странице
Монтаж собирается из загруженных материалов: видео, фото и аудио. Любой файл сначала становится загрузкой — у неё
есть id, статус и сведения о медиа. Готовые загрузки (status: ready) передаются в uploadIds при
создании проекта.
Четыре способа
| Способ | Когда удобен | Токен при отправке файла |
|---|---|---|
Одноразовая ссылка uploadUrl | проще всего; файл отправляет другая программа или ИИ-агент | не нужен |
| Файл целиком | файл у вашего сервиса, загрузка одним запросом | нужен |
| По частям | большие файлы и нестабильная сеть: части параллельно, повтор части | нужен |
| Импорт по адресу | файл уже лежит по публичному https-адресу | не нужен — файл скачает Slicer |
Создать загрузку
Для первых трёх способов загрузка создаётся заранее — сервер должен знать имя и размер файла:
curl https://api.slicer.media/v1/uploads \
-H "Authorization: Bearer $SLICER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"fileName":"interview.mp4","size":48211337}'{
"ok": true,
"data": {
"upload": {
"id": "7d0c6a4e-2f7b-4f5e-9a0e-3b8f2c1d9e61",
"fileName": "interview.mp4",
"purpose": "source",
"origin": "client",
"sizeBytes": 48211337,
"status": "uploading",
"partSize": 8388608,
"partCount": 6,
"uploadedParts": [],
"progress": null,
"media": null,
"error": null,
"createdAt": "2026-10-16T09:12:04.000Z"
},
"uploadUrl": "https://api.slicer.media/v1/upload-links/slc_ul_…",
"uploadUrlExpiresAt": "2026-10-16T10:12:04.000Z"
}
}fileName— с расширением: по нему определяется вид материала (видео, фото или аудио).size— точный размер файла в байтах.purpose— назначение; для материалов монтажа —source(по умолчанию).
Одноразовая ссылка
uploadUrl принимает PUT всего файла без токена — разрешение заключено в самой ссылке:
curl -T interview.mp4 "$UPLOAD_URL"Свойства ссылки:
- одна попытка — ссылка гаснет при первом запросе, даже если он оборвался; для повтора создайте новую загрузку или отправьте на ту же загрузку файл целиком с токеном (ниже);
- не дольше часа — время окончания в
uploadUrlExpiresAt; - только запись — по ссылке нельзя ничего прочитать или узнать об аккаунте;
- размер тела должен совпадать с заявленным
size, заголовокContent-Lengthобязателен (curl -Tставит его сам).
После приёма файла загрузка завершается сама, ответ — объект загрузки. Ссылку удобно передать ИИ-агенту или скрипту на другой машине, не раскрывая токен.
Файл целиком
Тот же приём, но с токеном и на постоянный адрес загрузки:
curl -X PUT "https://api.slicer.media/v1/uploads/$UPLOAD_ID/content" \
-H "Authorization: Bearer $SLICER_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @interview.mp4Сервер сам режет поток на части, шифрует их и завершает загрузку. Content-Length должен быть равен заявленному
размеру.
По частям
Для больших файлов: части по partSize байт (8 МиБ), нумерация с 1, последняя — остаток. Части можно отправлять
параллельно и повторять — уже принятые перечислены в uploadedParts. Когда все части на месте, вызовите complete.
// Node.js 20+: node upload-parts.mjs video.mp4
import { open, stat } from 'node:fs/promises';
import { basename } from 'node:path';
const API = 'https://api.slicer.media/v1';
const auth = { Authorization: `Bearer ${process.env.SLICER_TOKEN}` };
const file = process.argv[2];
async function call(method, path, body, headers = {}) {
const response = await fetch(`${API}${path}`, { method, headers: { ...auth, ...headers }, body });
const json = await response.json();
if (!json.ok) throw new Error(`${response.status} ${json.error.code}: ${json.error.message}`);
return json.data;
}
const { size } = await stat(file);
const { upload } = await call('POST', '/uploads', JSON.stringify({ fileName: basename(file), size }), {
'Content-Type': 'application/json',
});
const handle = await open(file);
const queue = Array.from({ length: upload.partCount }, (_, i) => i + 1);
async function worker() {
for (let part = queue.shift(); part != null; part = queue.shift()) {
const offset = (part - 1) * upload.partSize;
const length = Math.min(upload.partSize, size - offset);
const { buffer } = await handle.read(Buffer.alloc(length), 0, length, offset);
for (let attempt = 1; ; attempt += 1) {
try {
await call('PUT', `/uploads/${upload.id}/parts/${part}`, buffer, { 'Content-Type': 'application/octet-stream' });
break;
} catch (error) {
if (attempt === 3) throw error;
}
}
}
}
await Promise.all(Array.from({ length: 4 }, worker)); // 4 части одновременно
await handle.close();
const ready = await call('POST', `/uploads/${upload.id}/complete`);
console.log(ready.status, ready.media);# python upload_parts.py video.mp4
import os
import sys
from concurrent.futures import ThreadPoolExecutor
import requests
API = "https://api.slicer.media/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['SLICER_TOKEN']}"}
path = sys.argv[1]
size = os.path.getsize(path)
def call(method, url, **kwargs):
body = requests.request(method, API + url, headers=HEADERS, timeout=300, **kwargs).json()
if not body["ok"]:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
return body["data"]
upload = call("POST", "/uploads", json={"fileName": os.path.basename(path), "size": size})["upload"]
def send(part):
offset = (part - 1) * upload["partSize"]
with open(path, "rb") as file:
file.seek(offset)
chunk = file.read(min(upload["partSize"], size - offset))
for attempt in range(3):
try:
return call("PUT", f"/uploads/{upload['id']}/parts/{part}", data=chunk)
except (RuntimeError, requests.RequestException):
if attempt == 2:
raise
with ThreadPoolExecutor(max_workers=4) as pool: # 4 части одновременно
list(pool.map(send, range(1, upload["partCount"] + 1)))
ready = call("POST", f"/uploads/{upload['id']}/complete")
print(ready["status"], ready["media"])Части считаются в отдельной корзине лимита — 1200 запросов в минуту (Лимиты). Начатую по частям
загрузку нельзя продолжить целиком (409 upload_in_parts) — продолжайте частями.
Импорт по адресу
Если файл уже лежит в интернете, Slicer скачает его сам:
curl https://api.slicer.media/v1/uploads/import \
-H "Authorization: Bearer $SLICER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"url":"https://media.example.com/clips/product-demo.mp4"}'- Только
httpsи только публичные адреса: адреса внутренних сетей не принимаются, каждое перенаправление проверяется заново. - Имя файла берётся из адреса. Если в адресе нет расширения (
…/download?id=42), передайтеfileName:"fileName": "product-demo.mp4". - Импорт идёт в фоне: загрузка сразу приходит со статусом
uploading, полеprogressпоказывает долю скачанного (0–1). Затем —processingиreadyилиfailedс причиной вerror. - Одновременно — до 3 импортов на аккаунт, скачивание — до 30 минут. Ограничения размера и формата — те же, что у обычной загрузки.
Ссылки на страницы видеохостингов (не на сам файл) не подходят: нужен прямой адрес файла.
Статусы загрузки
| Статус | Что происходит |
|---|---|
uploading | ждём файл или части; у импорта — скачиваем |
processing | файл получен, сервер проверяет формат и готовит обложку |
ready | можно использовать в монтаже; сведения о файле — в media |
failed | файл не принят; причина — в error |
aborted | загрузка отменена (DELETE /v1/uploads/{id}) |
Узнать статус — GET /v1/uploads/{id} или вебхуки upload.ready и upload.failed. Поле media
готовой загрузки: kind (video, image, audio), durationSec, width, height, hasAudio.
Форматы и ограничения
Ограничения те же, что в приложении, и приходят в каталоге GET /v1/styles → materialLimits:
- видео —
mp4,mov,m4v,webm,mkv,avi; фото —jpg,jpeg,png,webp; аудио —mp3,m4a,aac,wav,ogg,opus,flac; - до 20 материалов в проекте, из них видео, фото и аудио — каждого вида в своих пределах;
- формат проверяется не только по расширению, но и по содержимому файла.
Подробнее о размерах, длительности и разрешении — Форматы и ограничения файлов. Незавершённые
загрузки удаляются через сутки, исходники проекта хранятся 7 дней после монтажа (sourcesAvailable в проекте), готовые
ролики — пока вы их не удалите.
Ошибки загрузки
| Код | Статус | Причина |
|---|---|---|
unsupported_format | 400 | расширение или содержимое файла не поддерживается |
file_too_large | 400 | файл больше допустимого для своего вида |
empty_file | 400 | файл пустой |
not_a_video, not_an_image, not_audio, media_unreadable | 400 | содержимое не совпадает с расширением или файл повреждён |
resolution_too_low, resolution_too_high, image_too_large | 400 | разрешение вне допустимых пределов |
audio_too_long | 400 | аудио длиннее допустимого |
too_many_uploads, daily_upload_limit | 400 | много незавершённых загрузок или исчерпан суточный объём |
bad_part, bad_part_size | 400 | неверный номер или размер части |
parts_missing | 400 | complete вызван до отправки всех частей |
length_required | 411 | нет заголовка Content-Length |
bad_content_length, body_too_short, body_too_long | 400 | размер тела не совпадает с заявленным size |
upload_not_active | 409 | загрузка уже завершена или отменена |
upload_in_parts | 409 | загрузка уже идёт по частям |
link_not_found | 404 | ссылка на загрузку не найдена |
link_expired, link_used | 410 | срок ссылки истёк или она уже использована |
bad_url, file_name_required | 400 | адрес импорта не https или в нём нет имени файла |
too_many_imports | 429 | уже идут 3 импорта — дождитесь окончания |