Ретраи и идемпотентность
Идемпотентных ключей в API нет. Какой из четырёх «повторов» бесплатен, какой тарифицируется как новый заказ и какой бессмысленно вызывать вообще.
Идемпотентных ключей в публичном API нет. Ни заголовка Idempotency-Key, ни поля
дедупликации в теле POST /orders, ни сервера, который узнал бы повторную отправку. Два
одинаковых запроса — два заказа и две блокировки. Всё ниже исходит из этого.
Четыре «повтора», которые нельзя путать
| Вызов | Работает с | Что делает | Стоимость |
|---|---|---|---|
POST /orders/{order}/items/retry-failed | всеми failed-чанками сразу | ставит их на повтор одним вызовом: 202 + план queued/skipped | бесплатно, из залоченного остатка |
POST /orders/{order}/items/{item}/retry | одним failed или policy_flagged чанком | повторяет чанк, опционально с новым text | бесплатно в пределах free_length |
POST /orders/{order}/items/{item}/regenerate | completed-чанком | переделывает готовый фрагмент новым текстом | платно, новая квота |
POST /orders/{order}/retry | заказом целиком | создаёт новый заказ теми же параметрами | платно, как создание |
Первые три работают по заказу в статусе partially_completed; последний, наоборот, этот
статус не принимает.
Order-level retry — это не «повторить»
POST /orders/{order}/retry не воскрешает заказ. Он читает его параметры и создаёт по ним
новый заказ: новый id, новая блокировка средств, новая проверка токен-квоты.
Тарификация полная — это второй заказ, а не вторая попытка.
Условия: исходный в статусе completed или result_delivered и без failed-чанков. Тело
такое же, как у создания: пустое в обычном случае, confirm_payg_topup + quote_token —
только после 402. Биллинг-flow тот же, коды те же: 402 payg_topup_required,
409 quote_mismatch, 429 по токен-квоте.
Уместен он в одном сценарии: «нужен ещё один такой же результат» — например, файлы удалены
по ретенции. Для доведения partially_completed он не годится ни технически, ни экономически.
Retry Order
Плейбук: partially_completed → completed
Шаг 1. Один вызов забирает все технические сбои. Тело пустое.
curl -X POST 'https://api.lumean.app/api/public/orders/{order}/items/retry-failed' \
-H 'Accept: application/json' \
-H 'X-API-KEY: YOUR_API_KEY'const res = await fetch('https://api.lumean.app/api/public/orders/{order}/items/retry-failed', {
method: 'POST',
headers: {
'Accept': 'application/json',
'X-API-KEY': 'YOUR_API_KEY',
},
});
const data = await res.json();
console.log(data);import requests
url = "https://api.lumean.app/api/public/orders/{order}/items/retry-failed"
headers = {
"Accept": "application/json",
"X-API-KEY": "YOUR_API_KEY",
}
res = requests.request("POST", url, headers=headers)
print(res.status_code, res.json())<?php
$ch = curl_init('https://api.lumean.app/api/public/orders/{order}/items/retry-failed');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: application/json',
'X-API-KEY: YOUR_API_KEY',
]);
$res = curl_exec($ch);
curl_close($ch);
echo $res;Ответ 202 — план принят, работа идёт в фоне. В data придут queued[] (что поставлено)
и skipped[] с машинной причиной на каждый пропуск:
reason в skipped[] | Что делать |
|---|---|
policy_flagged | Поштучный повтор с исправленным текстом — шаг 2 |
max_attempts_reached | Попыток было 5, шестой не будет — только новый заказ |
insufficient_funds | Залоченного остатка не осталось — только новый заказ |
active_retry_exists | Повтор уже идёт — дождаться |
superseded | У чанка уже есть успешная версия — он здоров, делать нечего |
Рядом лежит reason_label — тот же код словами, на языке аккаунта. Программно его разбирать
нельзя: формулировки меняются при правке локализации.
Шаг 2. Каждый policy_flagged — поштучно и с другим текстом.
curl -s "$BASE/orders/<ORDER>/items/<ITEM>/text" -H "X-API-KEY: $KEY"
curl -s -X POST "$BASE/orders/<ORDER>/items/<ITEM>/retry" -H "X-API-KEY: $KEY" \
-H "Content-Type: application/json" -d '{"text": "<исправленный текст>"}'GET .../items/{item}/text отдаёт вместе с текстом пределы: free_length (в его границах
повтор бесплатен), max_billable_length, max_raw_length (технический потолок куска,
за ним 422) и флаг surcharge_applies — «за длину сверх нормы будет доплата». Поле
original_text_length — синоним free_length, оставленный для совместимости.
К сведению
Справочник для LLM формулирует жёстче: «длина text ≤ original_text_length». Спека
допускает более длинный текст — просто платный; ориентируйтесь на surcharge_applies
и проверяйте его до отправки, чтобы списание не стало сюрпризом.
Шаг 3. Дождаться. Синхронного результата у массового повтора нет: опрашивайте
GET /orders/{order}/items либо слушайте событие bulk_retry.completed.
- Что произошло
- Отказ уровня заказа у массового повтора: `POST /orders/{order}/items/retry-failed` → 422 `{"success": false, "message": "…", "reason": "invalid_order_status"}`. Ни один чанк не поставлен в очередь.
- Что сделать прямо сейчас
- Проверить статус заказа перед вызовом. Соседние отказы того же эндпоинта различаются по `reason`: 409 `stitch_in_progress` (по заказу идёт склейка — подождать и повторить), 422 `generative_not_supported` (`sfx`/`music`: у них поштучного повтора нет), 422 `nothing_to_retry` (сбойных чанков не нашлось).
retry против regenerate
retry берёт только сбойный чанк (failed или policy_flagged); text необязателен, без
него повторяется тот же текст. Бесплатен в пределах уже оплаченной длины (free_length),
за более длинный текст берётся доплата.
regenerate берёт только готовый (completed) чанк, text обязателен, и он всегда
платный: блокируются новые средства, идёт pre-check квоты, при превышении 429.
Оба недоступны для sfx и music — генерация у них одноразовая.
Ловушка
Флаги can_retry / can_regenerate — не гарантия. Спека уверяет, что can_retry учитывает
статус, тип задачи и исчерпание попыток; llm.md §7.11 это опровергает: флаг смотрит только
на статус чанка. can_retry: true спокойно уживается с 400 на самом вызове.
content_blocked повторять бессмысленно
- Что произошло
- Заказ не создан. Тело: `{"success": false, "message": "…", "reason": "content_blocked"}` — БЕЗ `errors`, потому что форма запроса тут ни при чём. Тот же `reason` отдают поштучный повтор и перегенерация чанка.
- Что сделать прямо сейчас
- Повторять бессмысленно и дорого: исход детерминирован, а каждая попытка расходует ресурс и двигает счётчик к `policy_cooldown_active`. Единственное рабочее действие — отредактировать спорный фрагмент; из-за сверки по чанкам достаточно поправить его один, не переписывая текст целиком. Почему сработала политика, API не сообщает: это admin-only, поля с причиной в ответе нет и не будет — не ищите его.
Текст уже отбивался политикой и лежит в глобальном блоклисте: повтор даст тот же 422. Хуже
того, каждая попытка двигает счётчик к policy_cooldown_active — пять подряд заблокированных
задач останавливают создание новых на 5 минут, дальше на 10 и 15.
Правильно
Отредактировать спорный фрагмент. Сверка идёт по чанкам и по точному совпадению, поэтому в длинном тексте достаточно поправить один кусок, а не переписывать всё.
Неправильно
Поставить тот же текст в очередь ретраев с экспоненциальным backoff. Через пять попыток будет кулдаун на весь аккаунт, а заказ так и не создастся.
Причину блокировки API не отдаёт ни в каком поле — не ищите её.
Сетевой обрыв после POST /orders
Ответа нет — заказ мог создаться, а мог и нет. Слепой повтор здесь и есть источник дублей и двойных списаний.
Проверять нужно до повтора, через GET /orders. Ловушки две: фильтра по времени у списка
нет вовсе (только status, template_id, task_type[], page, per_page), а порядок выдачи
спека не документирует. Забирайте первую страницу и сверяйте created_at сами.
Облегчить проверку заранее можно одним способом: класть свой идентификатор запроса в поле
name. Оно ни на что, кроме имени файла, не влияет, — зато заказ узнаётся в списке однозначно.
# при создании
-d '{"template_id": "…", "input_text": "…", "name": "job-2026-09-04-0041"}'
# после обрыва, прежде чем повторять: ищем это имя в первой странице списка
curl -s "$BASE/orders?per_page=20" -H "X-API-KEY: $KEY"Серверной дедупликации по name нет — это соглашение вашего клиента. И отдельный аргумент
за подписку на private-orders.{userId}: событие created приходит независимо от того,
дошёл ли до вас HTTP-ответ.
Скормить страницу ИИ
Обновлено 04.09.2026 · спека 0.0.1