PopNowDokumentacja API
Budowanie z API
Treść główna

Idempotencja

Ponawianie po przekroczeniu czasu bez podwójnego generowania i podwójnej opłaty.

Klucz idempotencji to etykieta, którą przyklejasz do jednej pracy, np. „wyrenderuj wideo dla zamówienia 8814”. Jeśli to samo oznaczone zapytanie trafi do PopNow dwa razy, praca zostanie wykonana raz.

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

Po co go używać

Przekroczenie czasu zostawia cię w niepewności: może zapytanie w ogóle nie dotarło, a może dotarło i zgubiła się tylko odpowiedź. Bez klucza albo się poddajesz (a użytkownik czeka na nic), albo ponawiasz (i może generujesz oraz płacisz dwa razy). Z kluczem ponowienie jest zawsze bezpieczne: dostajesz nowe zadanie albo to, które powstało przed chwilą.

Jak działa

  • Ponowienie z tym samym kluczem z tego samego klucza API zwraca pierwotne zadanie w jego bieżącym stanie — bez drugiego zadania, drugiej blokady i drugiego obciążenia.
  • Klucz jest powiązany z tym, co wysłałeś: model, całym input i callBackUrl. Kolejność pól i białe znaki nie mają znaczenia, ale każda prawdziwa zmiana ma. Zmienione zapytanie ze starym kluczem zwraca 409; nadaj mu nowy klucz.
  • Klucz jest pamiętany przez 24 godziny od pierwszego użycia. Potem ten sam klucz utworzy nowe, płatne zadanie, więc zatrzymaj automatyczne ponowienia dużo wcześniej.
  • Klucze mają do 128 znaków i muszą być unikalne tylko w obrębie twojego konta. Inny klucz API z twojego konta nie może go użyć ponownie: dostanie 409 bez ID pierwotnego zadania.
EndpointPonowienie z tym samym kluczem…
POST /api/v1/jobs/createTaskzwraca pierwotne zadanie
POST /api/v1/jobs/retryzwraca zadanie utworzone przez to ponowienie (klucz obowiązuje w obrębie zadania źródłowego)
POST /v1/videoszwraca pierwotne zadanie
POST /api/v1/assetszwraca już zapisany zasób (tu nagłówek jest wymagany)

Endpointy odczytu i te, które wydają linki do przesyłania lub pobierania, ignorują nagłówek. jobs/purge go nie potrzebuje: jego powtórzenie i tak jest nieszkodliwe.

Jak wybierać klucze

  • Jeden klucz na jedną pracę, a nie na każdą próbę HTTP. Każde ponowienie musi go użyć jeszcze raz.
  • Wyprowadzaj go z czegoś, co już przechowujesz: order-8814-render-1 da się odtworzyć po awarii, a losowego UUID nie, chyba że wcześniej go zapiszesz.
  • Jeśli używasz UUID, wygeneruj go przed pierwszą próbą i zapisz obok pracy, do której należy.

Nigdy nie generuj klucza w pętli ponowień

// 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);

Konflikty

Wszystkie te przypadki zwracają code: 409:

Co się stałoPonawiać?
Dwa zapytania z tym samym kluczem przyszły jednocześnieTak — odczekaj kilkaset milisekund i wyślij ponownie z tym samym kluczem
Klucz został już użyty do innego zapytaniaNie — zapytanie się zmieniło i potrzebuje nowego klucza
Inny klucz API z twojego konta już użył tego kluczaNie — ponów kluczem API, który wykonał pierwszą próbę

Twój kod nie powinien rozgałęziać się według msg, więc pozwól na jedno–dwa ponowienia przy 409, a potem pokaż błąd człowiekowi.

Pełny przykład

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',
);

Na tej stronie