Регистрация 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}.
Опрос: GET /api/media/jobs/{id}
GEThttps://plusvibeapi.ru/api/media/jobs/{id}
Параметр
Тип
Описание
status
string
submitting | processing | success | fail. submitting + media_submission_uncertain is non-terminal; within about 10–15 minutes such a submission is closed as fail (media_submission_not_started) without a charge — create a new task.
Рекомендуемый интервал опроса: 3–5 с, с экспоненциальной задержкой до 30 с. Таймаут на стороне клиента — по SLA вашего приложения (генерация видео может занимать минуты).
Регистрация webhook
GEThttps://plusvibeapi.ru/api/webhooks
PATCHhttps://plusvibeapi.ru/api/webhooks
Один URL на аккаунт: mediaWebhookUrl. Настраивается в личном кабинете → Webhooks или через API с сессией кабинета (cookie после входа). Эндпоинт не принимает sk-pv-… — только авторизованную сессию.
Отдельный 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 (до 255 символов ASCII): повтор с тем же ключом и телом в течение 24 часов вернёт тот же jobId без новой задачи; тот же ключ с другим телом — 422, пока первый запрос обрабатывается — 409. Потерянный jobId оплаченной генерации также виден в GET /v1/generations (поле job_id).
Webhook ограничен пятью попытками. Для каждого терминального перехода создаётся одно логическое событие, но попытки доставки могут повторяться. После пяти неудач событие получает статус FAILED . Обработчик клиента должен дедуплицировать пару jobId + status.