Регистрация mediaWebhookUrl, доставка POST при завершении задачи, опрос GET /api/media/jobs/{id}, коды ошибок, списание кредитов и ограничения идемпотентности.
Операционное руководство по асинхронным медиа-задачам: регистрация webhook, статусы, опрос, коды ошибок, списания и ограничения идемпотентности — строго по текущей реализации в коде.
Жизненный цикл задачи
POST /api/media/generate — при успехе 202 и { ok: true, jobId, status: "processing" }. Отдельных статусов queued / running в API нет: пока задача не завершена, клиент видит processing.
Upstream может проходить внутренние стадии (waiting, generating) — они не отражаются в публичном API; при опросе задача остаётся processing.
Терминальные статусы: success (есть resultUrls, списание при успехе) или fail (без списания).
Завершение фиксируется один раз: через callback upstream или при ленивом опросе в GET /api/media/jobs/{id}.
Рекомендуемый интервал опроса: 3–5 с, с экспоненциальной задержкой до 30 с. Таймаут на стороне клиента — по SLA вашего приложения (генерация видео может занимать минуты).
Регистрация webhook
GEThttps://plusvibeapi.ru/api/webhooks
PATCHhttps://plusvibeapi.ru/api/webhooks
Один URL на аккаунт: mediaWebhookUrl. Настраивается в личном кабинете → Webhooks или через API с сессией кабинета (cookie после входа). Эндпоинт не принимает sk-pv-… Bearer-ключ — только авторизованную сессию.
Отдельный webhook при низком балансе (не медиа). Порог — balanceWebhookThresholdRub.
Доставка webhook при завершении задачи
Когда задача переходит в терминальный статус, PlusVibe создаёт одно логическое событие и доставляет его POST на зарегистрированный mediaWebhookUrl. Доставка ограничена: сервис делает до 5 попыток всего. Один и тот же payload может прийти повторно.
// POST на ваш mediaWebhookUrl при завершении задачи
{
"jobId": "...",
"status": "success", // или "fail"
"model": "veo-3.1",
"kind": "video", // image | video | audio | music | 3d
"resultUrls": ["https://plusvibeapi.ru/api/media/file/..."],
"failMsg": null,
"createdAt": "2026-06-17T10:00:00.000Z"
}
Поведение доставки (как в коде)
Параметр
Тип
Описание
Подпись / auth
—
Исходящий webhook не подписывается и не содержит отдельного auth-заголовка. Проверяйте jobId через GET /api/media/jobs/{id} с вашим API-ключом.
Повторы
outbox
До 5 попыток всего; интервал после неудачи: 1, 2, 4, 8 минут. После исчерпания попыток событие переходит в FAILED и больше не доставляется автоматически. Опрос GET /api/media/jobs/{id} остаётся источником состояния.
Дедупликация получателя
обязательна
Дедуплицируйте по паре jobId + status и делайте обработчик идемпотентным. Не используйте webhook как единственный источник состояния.
Content-Type
application/json
Тело — JSON с полями jobId, status, model, kind, resultUrls, failMsg, createdAt.
Ошибки POST /api/media/generate
HTTP
Когда
Списание
Повтор
202
Задача принята (ok: true, jobId)
Нет (только после success)
—
400
Невалидный JSON / тело запроса
Нет
Исправить запрос
401
Нет или неверный API-ключ
Нет
Проверить Authorization
402
Недостаточно средств (admission)
Нет
Пополнить баланс
422
Неизвестная модель, невалидные opts, ok: false от facade
Нет
Исправить model/opts
502
media_submit_failed — upstream недоступен
Нет
Повтор с backoff
503
priceUnavailable, routing guard, VIDEO_DISABLED, veo без цены
Нет
Другая модель / позже
Ошибки опроса и терминальные сбои
HTTP / status
Смысл
Списание
404
job not found или чужой jobId
Нет
processing
Задача ещё выполняется
Нет
success
Готово, resultUrls заполнены
Да — priceRub в ответе
fail
Генерация не удалась
Нет (priceRub = 0)
Биллинг и идемпотентность
Списание один раз при success. В finalizeJob переход processing → success|fail атомарен: повторный callback или опрос не создают второе списание.
fail — бесплатно. При статусе fail дебет не выполняется.
Admission quote. Если при создании сохранена котировка, при success списывается ровно она (не больше фактического upstream-metering).
Идемпотентность create — отсутствует. Повторный POST /api/media/generate с тем же телом создаёт новую задачу и новое списание при успехе. Заголовка Idempotency-Key нет — при retry после таймаута дедуплицируйте на своей стороне (например, храните jobId).
Webhook ограничен пятью попытками. Для каждого терминального перехода создаётся одно логическое событие, но попытки доставки могут повторяться. После пяти неудач событие получает статус FAILED. Обработчик клиента должен дедуплицировать пару jobId + status.