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-1Po 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łyminputicallBackUrl. Kolejność pól i białe znaki nie mają znaczenia, ale każda prawdziwa zmiana ma. Zmienione zapytanie ze starym kluczem zwraca409; 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
409bez ID pierwotnego zadania.
| Endpoint | Ponowienie z tym samym kluczem… |
|---|---|
POST /api/v1/jobs/createTask | zwraca pierwotne zadanie |
POST /api/v1/jobs/retry | zwraca zadanie utworzone przez to ponowienie (klucz obowiązuje w obrębie zadania źródłowego) |
POST /v1/videos | zwraca pierwotne zadanie |
POST /api/v1/assets | zwraca 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-1da 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ło | Ponawiać? |
|---|---|
| Dwa zapytania z tym samym kluczem przyszły jednocześnie | Tak — odczekaj kilkaset milisekund i wyślij ponownie z tym samym kluczem |
| Klucz został już użyty do innego zapytania | Nie — zapytanie się zmieniło i potrzebuje nowego klucza |
| Inny klucz API z twojego konta już użył tego klucza | Nie — 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
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',
);
