PopNowДокументация API
Работа с API
Основное содержимое

Идемпотентность

Как повторять запрос после таймаута, не генерируя и не оплачивая дважды.

Ключ идемпотентности — это ярлык для одной работы, например «отрендерить видео для заказа 8814». Если один и тот же запрос с ярлыком дойдёт до PopNow дважды, работа будет выполнена один раз.

POST /api/v1/jobs/createTask
Idempotency-Key: order-8814-render-1

Зачем он нужен

Таймаут оставляет в неведении: возможно, запрос так и не дошёл, а возможно, дошёл и потерялся только ответ. Без ключа вы либо сдаётесь (и пользователь ждёт впустую), либо повторяете (и, возможно, генерируете и платите дважды). С ключом повтор всегда безопасен: вы получаете либо новую задачу, либо ту, что была создана мгновение назад.

Как это работает

  • Повтор с тем же ключом от того же API-ключа возвращает исходную задачу в её текущем состоянии — без второй задачи, второго резерва и второго списания.
  • Ключ привязан к тому, что вы отправили: model, весь input и callBackUrl. Порядок полей и пробелы не важны, а любое реальное изменение — важно. Изменённый запрос со старым ключом получит 409; дайте ему новый ключ.
  • Ключ запоминается на 24 часа с первого использования. После этого тот же ключ создаст новую платную задачу, поэтому автоматические повторы прекращайте задолго до этого.
  • Ключ — до 128 символов и должен быть уникален только в пределах вашего аккаунта. Другой API-ключ вашего аккаунта не может его переиспользовать: он получит 409 без ID исходной задачи.
ЭндпоинтПовтор с тем же ключом…
POST /api/v1/jobs/createTaskвозвращает исходную задачу
POST /api/v1/jobs/retryвозвращает задачу, созданную этим повтором (ключ действует в рамках исходной задачи)
POST /v1/videosвозвращает исходную задачу
POST /api/v1/assetsвозвращает уже сохранённый ассет (здесь заголовок обязателен)

Эндпоинты чтения и выдачи ссылок на загрузку и скачивание заголовок игнорируют. jobs/purge в нём не нуждается: повторный вызов и так безвреден.

Как выбирать ключи

  • Один ключ на одну работу, а не на каждую HTTP-попытку. Каждый повтор должен использовать его снова.
  • Выводите ключ из того, что у вас уже хранится: order-8814-render-1 можно пересчитать после сбоя, а случайный UUID — нет, если вы его заранее не сохранили.
  • Если используете UUID, генерируйте его до первой попытки и храните рядом с работой, к которой он относится.

Никогда не создавайте ключ внутри цикла повторов

// Wrong: a new key per attempt defeats the whole mechanism
for (let i = 0; i < 3; i++) await createTask(body, crypto.randomUUID());

// Right: one key, created once
const key = crypto.randomUUID();
for (let i = 0; i < 3; i++) await createTask(body, key);

Конфликты

Во всех этих случаях возвращается code: 409:

Что произошлоПовторять?
Два запроса с одним ключом пришли одновременноДа — подождите несколько сотен миллисекунд и отправьте с тем же ключом
Ключ уже использован для другого запросаНет — запрос изменился, нужен новый ключ
Другой API-ключ вашего аккаунта уже использовал этот ключНет — повторите с тем API-ключом, который делал первую попытку

Ваш код не должен ветвиться по msg, поэтому допускайте один-два повтора при 409, а затем передавайте ошибку человеку.

Полный пример

JavaScript
const RETRYABLE = new Set([429, 500, 503, 50301]); // business codes from the body

async function createTaskOnce(body, idempotencyKey) {
  for (let attempt = 0; attempt < 4; attempt++) {
    try {
      const res = await fetch('https://api.popnow.ai/api/v1/jobs/createTask', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.POPNOW_API_KEY}`,
          'Content-Type': 'application/json',
          'Idempotency-Key': idempotencyKey,
        },
        body: JSON.stringify(body),
      });
      const json = await res.json();
      if (json.code === 200) return json.data; // a new task, or the original one
      if (!RETRYABLE.has(json.code)) throw new Error(`${json.code} ${json.msg} (${json.request_id})`);
    } catch (err) {
      if (!(err instanceof TypeError)) throw err; // network error: safe to retry with the same key
    }
    await new Promise((r) => setTimeout(r, 2 ** attempt * 500 + Math.random() * 300));
  }
  throw new Error('createTask did not succeed after 4 attempts');
}

const task = await createTaskOnce(
  { model: 'alibaba/z-image-turbo/text-to-image', input: { prompt: 'A paper boat on a calm lake' } },
  'order-8814-render-1',
);

На этой странице