Даже успешная генерация может обернуться неработающим изображением в вашем продукте, если вы сохраните временный URL доставки вместо того, чтобы скопировать файл. Подписанные ссылки действуют несколько минут, а сами результаты API хранятся около двух недель.
API для медиа передаёт один и тот же файл под несколькими идентификаторами: входной референс, ключ сгенерированного результата и подписанный URL, позволяющий получить байты в течение нескольких минут. У каждого свой срок действия, и ни один не является постоянным адресом. В этом руководстве приведены точные сроки SpicyAPI и показано, как копировать ценные результаты в хранилище под вашим контролем. Настройки хранения могут сократить эти сроки.
Сначала попробуйте в Playground
Откройте страницу Z-Image для генерации изображения из текста и войдите в аккаунт:
- Введите короткое описание сцены в Prompt и выберите Aspect ratio.
- Нажмите Run model. Результат появится в Output.
- Скачайте изображение и сохраните его там, где вы контролируете хранение.
- Позже откройте сохранённый файл снова. Он откроется без страницы и временной ссылки на результат — именно это остальные разделы автоматизируют для рабочей системы.
Главное
- Копируйте после завершения в рабочем процессе. Не выполняйте большие загрузки внутри запроса обратного вызова.
- Храните подписанные ссылки в тайне. Обладание ссылкой даёт временный доступ к файлу.
- Предоставляйте свой ID ресурса и URL доставки. Запись задачи может пережить её медиа.
Что и когда истекает
| Объект | Срок действия | Что происходит в конце |
|---|---|---|
Билет загрузки (uploadUrl) | Около 20 минут; PUT и подтверждение должны завершиться в этот срок | Ответ об истёкшем билете требует нового; занятое подтверждение также может вернуть 409 |
Подтверждённый референс (spicy://f/fil_...) | 1 день с подтверждения, до expiresAt в ответе подтверждения | URI отклоняется для новых задач и повторных попыток |
Ссылка на результат (output.assets[].url) | Около 20 минут, но не позже срока самих медиа | Хранилище отвечает 403; прочитайте задачу снова для новой ссылки |
| Сгенерированные изображение, видео или аудио | Около 14 дней с завершения (меньше, если вы так настроили) | Удаляются; ссылку нельзя выдать (410 от download-url) |
| Текст промпта и пути к файлам результата в задаче | 30 дней | input и output очищаются; строка задачи остаётся |
| Запись задачи (ID, модель, состояние, стоимость, отметки времени) | Хранится бессрочно | По-прежнему доступна для запросов, но без содержимого |
Медиа коротких фильмов, созданных внутри проекта Spicy Studio, имеют свой более долгий срок — около 60 дней; для генераций через API, SDK и Playground действует указанный выше предел 14 дней. Хранение генераций также можно сократить для отдельного запроса или аккаунта, как объясняет статья о настройках хранения. Эти пределы можно только сокращать, но не увеличивать.
14 дней отсчитываются от завершения, а не отправки. До завершения задачи отметка времени предварительная и пересчитывается при завершении.
Слово «около» выбрано намеренно. Очистка хранилища выполняется по правилу жизненного цикла и может отставать от срока до одного дня, но эта задержка не даёт вам дополнительного времени: как только окно закрывается, ссылка для скачивания больше не выдаётся.
Один файл, четыре представления
local reference image
│
├─ POST /api/v1/common/upload-url → upload ticket (about 20 min)
├─ PUT to the returned uploadUrl → temporary object
├─ POST /api/v1/files/{fileId}/commit → spicy://f/fil_... (usable 1 day)
├─ POST /api/v1/jobs/createTask → task
└─ terminal callback or recordInfo
└─ output.assets[] → key + url + expiresAt (about 20 min)
└─ stream bytes to your storage → your asset ID and delivery URLSDK, CLI и Playground выполняют этапы загрузки за вас, поэтому редко приходится писать их вручную. У каждого представления своя роль. Билет разрешает прямую запись в течение своего срока; подтверждение фиксирует принятые байты. URI spicy:// позволяет задаче в течение дня ссылаться на подтверждённый входной файл, привязанный к аккаунту. Выходной key идентифицирует сгенерированный результат. url временно разрешает чтение. Ваш собственный ID ресурса — единственный долговечный идентификатор, который должен предоставлять продукт.
Загрузка референса в SpicyAPI бесплатна; вы платите за задачу генерации, которая его использует. Обновление ссылки на существующий результат не создаёт новую плату за генерацию. Если результат утрачен и вы решите сгенерировать его снова, новая задача имеет собственную плату за модель. Отдельно заложите расходы на хранение, запросы и передачу данных вашего постоянного бакета согласно тарифу поставщика хранилища.
Входные данные: загрузите и учтите истечение через 1 день
Храните исходные референсные изображение, видео или аудио в своём хранилище. Загрузите локальный входной файл через SDK, CLI или Playground и повторно используйте подтверждённый URI, пока он действителен. После истечения загрузите файл снова, когда потребуется; временный URI не является вашим архивом.
Три способа передать модели входные данные:
- Публичный HTTPS-адрес. Работает там, где поле модели принимает URL, если адрес возвращает сам файл. Ссылка на облачный диск, альбом или соцсеть возвращает веб-страницу, и задача завершается ошибкой
invalid_asset; вместо этого скачайте файл и загрузите его. - Небольшое встроенное изображение. JPEG, PNG, WebP или GIF в виде URI данных base64, до 1 MiB каждое.
- Загрузка. Для локальных файлов. Изображения (JPEG, PNG, WebP, GIF) до 10 MiB; аудио и видео WAV, MP3, WebM, MP4 или MOV до 90 MiB и 600 секунд. Модель может принимать меньше, поэтому проверьте ограничение поля на её странице. В TypeScript SDK вызов
await client.uploadFile("./reference.png")возвращает URIspicy://вfile.uri.
Никогда не раскрывайте ключ SpicyAPI в браузере; пусть сервер выдаёт билеты загрузки. Документация медиа описывает интеграцию с браузером и все ограничения.
Выходные данные: как выглядит готовый результат
Завершённая задача и доступный для получения файл — разные состояния:
- Готов: скопируйте файл в своё хранилище и запишите ID задачи, формат и размер.
- Ожидает: сохраните ту же задачу и проверьте снова; не покупайте другую генерацию ради получения файла.
- Недоступен: копии на платформе больше нет, и ожидание её не вернёт. Используйте свою сохранённую копию; иначе генерируйте заново с обычной оплатой. Не оплачивается только первоначальный сбой хранения, при котором ни один файл не был доставлен.
Временные ссылки предоставляют доступ своему обладателю. Не включайте их в публичные страницы и аналитику; получайте актуальную ссылку, когда процесс копирования готов.
Граница хранения
Запись задачи, ключ результата или обновлённая ссылка для скачивания не сохраняют байты. Скопируйте их до срока удаления медиа.
Когда окно закрывается, recordInfo сообщает contentState: "expired", а input и output очищаются не позднее чем через 30 дней. Если вы сами уничтожили содержимое задачи, состояние — purged. Состояние задачи expired означает другое: генерация превысила время ожидания и ничего не создала. Не показывайте оба состояния под одним названием в интерфейсе: одно требует повторной попытки, а другое невозможно восстановить повторной попыткой.
Назначьте срок копирования задолго до предела. Копируйте при первом событии успеха, а затем запускайте сверку, которая перечисляет успешные задачи без постоянного ресурса и подаёт сигнал, пока результату осталось ещё несколько дней. День 14 — запас для восстановления, а не расписание. Если настройка аккаунта или запроса сокращает хранение, outputsExpireAt может наступить гораздо раньше 14 дней, поэтому рассчитывайте предупреждения по этому полю, а не по константе. Одна настройка аккаунта может действовать и задним числом: сокращение срока хранения текста промпта удаляет результаты уже завершённых задач раньше назначенного им outputsExpireAt. Скачайте всё, что хотите сохранить, до изменения настроек аккаунта и ожидайте, что сверка сообщит о таких задачах как об утраченных, а не как об ошибках конвейера.
Конвейер обратных вызовов, устойчивый к повторным попыткам и большим видео
В приложении проверьте событие завершения, сохраните его и поставьте копирование в очередь до отправки ответа. Сделайте копирование идемпотентным, чтобы повторное событие не создавало ещё один элемент галереи. Предупреждайте о нескопированных результатах до их принятого срока истечения.
Этот самостоятельный рабочий процесс сохраняет файлы существующей задачи в приватном локальном каталоге. Установите spicyapi, задайте SPICY_API_KEY, TASK_ID и при необходимости ARCHIVE_DIR, затем запустите его. Он не создаёт генерацию. Разместите каталог на постоянном носителе и включите в план резервного копирования; временный диск сервера не является долговечным архивом.
import hashlib
import json
import os
from pathlib import Path
import tempfile
import urllib.request
from spicyapi import SpicyClient, output_assets
client = SpicyClient()
task_id = os.environ["TASK_ID"]
root = Path(os.environ.get("ARCHIVE_DIR", "media-archive"))
root.mkdir(parents=True, exist_ok=True, mode=0o700)
task = client.get_task(task_id)
if task["state"] != "succeeded":
raise RuntimeError("Resume the existing task before archiving it.")
for index, asset in enumerate(output_assets(task)):
if asset.get("pending") or asset.get("unavailable") or not asset.get("url"):
raise RuntimeError("Asset is not ready; retain the task ID.")
request = urllib.request.Request(asset["url"], headers={"User-Agent": "SpicyAPI-Blog-Example/1.0"})
temporary = None
try:
with urllib.request.urlopen(request, timeout=300) as response:
mime = response.headers.get_content_type()
suffix = {"image/png": ".png", "image/jpeg": ".jpg", "image/webp": ".webp",
"video/mp4": ".mp4", "video/webm": ".webm", "audio/mpeg": ".mp3", "audio/wav": ".wav"}.get(mime)
if response.status != 200 or suffix is None:
raise RuntimeError("Unexpected response format; do not guess an extension.")
digest, size = hashlib.sha256(), 0
with tempfile.NamedTemporaryFile(dir=root, delete=False) as output:
temporary = Path(output.name)
while chunk := response.read(1024 * 1024):
output.write(chunk)
digest.update(chunk)
size += len(chunk)
if not size or (asset.get("bytes") and size != int(asset["bytes"])):
raise RuntimeError("Empty or incomplete transfer.")
filename = root / (digest.hexdigest() + suffix)
temporary.replace(filename)
record = {"taskId": task_id, "index": index, "file": filename.name,
"mime": mime, "bytes": size, "sha256": digest.hexdigest()}
(root / (digest.hexdigest() + ".json")).write_text(json.dumps(record, indent=2))
print(filename)
finally:
if temporary is not None:
temporary.unlink(missing_ok=True)Для доставки из объектного хранилища передавайте данные потоком во временный приватный объект и открывайте доступ только после полной передачи. Документация медиа и webhook объясняет обновление результатов и аутентифицированные события. Оставьте периодический опрос статуса задачи (polling) как путь сверки на случай пропущенных обратных вызовов.
Постоянный ресурс должен скрывать путь генерации
После копирования объект, который выдаёт ваш продукт, имеет собственную идентичность:
asset_01k... database identity
generated/job_.../....mp4 private storage key
https://media.example.com/a/... controlled delivery URLДержите бакет приватным и выдавайте файлы через собственный CDN, аутентифицированный медиаэндпоинт или собственные короткоживущие ссылки. Не используйте подписанный URL SpicyAPI как публичный URL. Так временные ссылки и детали хранилища не попадают в HTML, журналы и экспорты; вы можете менять правила доступа или домены без переписывания истории задач, а удаление ресурса пользователя становится операцией с вашей собственной записью. Приватно храните происхождение: ID исходной задачи, исходный ключ ресурса, тип содержимого, размер, время копирования и ключ назначения.
Сбои и правильное восстановление
| Сбой | Что вы наблюдаете | Правильное восстановление |
|---|---|---|
| Билет истёк до завершения загрузки или в задаче использован неподтверждённый файл | Загрузка или подтверждение отклоняется, либо создание задачи отказывает в референсе | Запросите новый билет и загрузите снова; 409/429 при занятом подтверждении означает повторить то же подтверждение |
Сохранённый URI spicy:// использован на следующий день | 400 при создании задачи или неудачная повторная попытка | Загрузите оригинал снова; храните оригиналы у себя |
| Обратный вызов доставлен дважды | Два процесса начинают одно копирование | Убирайте дубликаты по request_id, а также по ID задачи и индексу ресурса в очереди и месте назначения |
| Эндпоинт обратного вызова медленный | Повторные доставки приходят во время скачивания | Сохраните, поставьте в очередь и верните 2xx до переноса медиа |
| Ссылка результата истекла в очереди | Получение исходного файла возвращает 403 | Прочитайте задачу снова для свежего url и повторите потоковую передачу |
Ресурс показывает pending: true | key и url пусты | Подождите несколько секунд и запросите снова |
Ресурс показывает unavailable: true или download-url возвращает 410 | Истёк срок, удалён или не удалось сохранить | Используйте проверенную приватную копию; если её нет, сообщите пользователю и генерируйте заново как новую платную задачу |
| Копирование завершилось, но запись в базу не удалась | Приватный объект есть, строки готового ресурса нет | Проверьте существующее назначение и повторите запись метаданных; никогда не удаляйте корректную копию и не генерируйте заново |
| База сообщает о готовности, но объекта нет | Неработающая страница или 404 от CDN | Сверьте строки базы с бакетом перед отметкой готовности |
| Подписанный URL появляется в журналах | Раскрыта временная возможность чтения | Скрывайте строки запросов и тела ответов; ссылки истекают примерно через 20 минут, но отозвать их нельзя |
| Большое видео буферизуется в памяти | Процесс перезапускается или исчерпывает память | Передавайте потоком с ограниченной параллельностью и обратным давлением |
Список перед запуском в рабочей среде
- Восстанавливайте до истечения срока: предупреждайте об отсутствующих копиях по принятому сроку каждой задачи.
- Явно определяйте владельца: выдавайте приватные ресурсы только владельцу и забирайте результаты до ротации API-ключей.
- Удаляйте во всех системах: включите бакет, кеш доставки и резервные копии в политику удаления.
Частые вопросы
Можно ли сохранить URL из output.assets[].url?
Только для самого копирования. Сохраните собственную запись ресурса и получайте свежую ссылку, когда нужно перенести байты.
Нужен ли по-прежнему download-url?
Только для обновления ссылки по taskId и key в пределах срока хранения; готовые ресурсы уже содержат url и expiresAt.
Продлевает ли обновление ссылки хранение медиа?
Нет. Новая ссылка только обновляет доступ для чтения; объект по-прежнему удаляется в retention.outputsExpireAt.
Можно ли передать модели публичный URL изображения вместо загрузки?
Да, если схема модели допускает URL в этом поле, адрес возвращает сам файл и остаётся доступным во время выполнения задачи. Используйте загрузку для локальных файлов, больших входных данных и всего, что не размещено публично.
То же референсное изображение нужно на следующей неделе. Что делать?
Загрузите его снова из своей копии, когда оно понадобится на следующей неделе. Подтверждённый референс работает только 1 день, но может обслуживать несколько задач, пока действителен, если не удалён раньше.
Нужно ли копировать результат внутри запроса обратного вызова?
Нет. Большая передача внутри запроса превышает 15-секундный тайм-аут обратного вызова и запускает повторные доставки; процесс в очереди может безопасно повторять попытки.
Что если я вообще не хочу сохранять результат?
Не копируйте его и дождитесь удаления по сроку, либо сократите хранение или уничтожьте содержимое задачи по запросу. Эти настройки описаны в Загрузка, хранение и доставка AI-медиа и документации хранения. Уничтожение содержимого никогда не меняет уже начисленную плату.
Поручите вызов агенту
Используйте агента, чтобы восстановить существующий результат, а не создавать его снова.
- CLI:
npx @spicyapi/cli tasks get "$TASK_ID"получает задачу. См. руководство CLI. - MCP: запросите текущий результат через
spicyapi_task_getи сохраните полученный файл. См. руководство MCP. - Skill: установите
npx skills add spicyapi-ai/spicy-skill, затем попросите$spicyapiсохранить результат этой задачи в выбранном каталоге. См. руководство Skill.







