PopNowAPI-Doku
Mit der API bauen
Hauptinhalt

Idempotenz

Nach einem Timeout gefahrlos wiederholen – ohne doppelt zu generieren oder doppelt zu zahlen.

Ein Idempotency-Key ist ein Etikett für eine Arbeit, etwa „das Video für Bestellung 8814 rendern“. Kommt dieselbe etikettierte Anfrage zweimal bei PopNow an, wird die Arbeit nur einmal erledigt.

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

Warum du ihn willst

Ein Timeout lässt dich raten: Vielleicht kam die Anfrage nie an, vielleicht kam sie an und nur die Antwort ging verloren. Ohne Schlüssel gibst du entweder auf (und der Nutzer wartet umsonst) oder wiederholst (und generierst und zahlst vielleicht doppelt). Mit Schlüssel ist eine Wiederholung immer sicher: Du bekommst entweder einen neuen Task oder den, der gerade erstellt wurde.

Wie er sich verhält

  • Eine Wiederholung mit demselben Schlüssel vom selben API-Schlüssel liefert den ursprünglichen Task in seinem aktuellen Zustand – kein zweiter Task, keine zweite Reservierung, keine zweite Abbuchung.
  • Der Schlüssel ist an das Gesendete gebunden: model, das ganze input und callBackUrl. Feldreihenfolge und Leerraum spielen keine Rolle, echte Änderungen schon. Eine geänderte Anfrage mit altem Schlüssel liefert 409; gib ihr einen neuen.
  • Ein Schlüssel gilt 24 Stunden ab der ersten Verwendung. Danach erzeugt derselbe Schlüssel einen neuen, kostenpflichtigen Task – beende automatische Wiederholungen also deutlich vorher.
  • Schlüssel sind bis zu 128 Zeichen lang und müssen nur innerhalb deines Kontos eindeutig sein. Ein anderer API-Schlüssel deines Kontos kann ihn nicht wiederverwenden: Er bekommt 409 ohne die ursprüngliche Task-ID.
EndpunktEine Wiederholung mit demselben Schlüssel …
POST /api/v1/jobs/createTaskliefert den ursprünglichen Task
POST /api/v1/jobs/retryliefert den Task, den dieser Retry erzeugt hat (der Schlüssel gilt pro Quell-Task)
POST /v1/videosliefert den ursprünglichen Task
POST /api/v1/assetsliefert das bereits gespeicherte Asset (hier ist der Header Pflicht)

Lese-Endpunkte und die Endpunkte für Upload- und Download-Links ignorieren den Header. jobs/purge braucht keinen: Eine Wiederholung ist dort ohnehin harmlos.

Schlüssel wählen

  • Ein Schlüssel pro Arbeit, nicht pro HTTP-Versuch. Jede Wiederholung muss ihn wiederverwenden.
  • Leite ihn aus etwas ab, das du ohnehin speicherst: order-8814-render-1 lässt sich nach einem Absturz neu berechnen, eine zufällige UUID nur, wenn du sie vorher gespeichert hast.
  • Nutzt du eine UUID, erzeuge sie vor dem ersten Versuch und speichere sie neben der Arbeit, zu der sie gehört.

Den Schlüssel nie in der Wiederholungsschleife erzeugen

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

Konflikte

All diese Fälle liefern code: 409:

Was passiert istWiederholen?
Zwei Anfragen mit demselben Schlüssel kamen gleichzeitig anJa – ein paar hundert Millisekunden warten und mit demselben Schlüssel erneut senden
Der Schlüssel wurde schon für eine andere Anfrage verwendetNein – die Anfrage hat sich geändert und braucht einen neuen Schlüssel
Ein anderer API-Schlüssel deines Kontos hat diesen Schlüssel schon benutztNein – mit dem API-Schlüssel wiederholen, der den ersten Versuch gemacht hat

Dein Code sollte nicht nach msg verzweigen; erlaube also ein, zwei 409-Wiederholungen und reich den Fehler dann an einen Menschen weiter.

Vollständiges Beispiel

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

Auf dieser Seite