PopNowDocs API
Construire avec l’API
Contenu principal

Idempotence

Réessayer après un délai dépassé sans générer deux fois ni payer deux fois.

Une clé d’idempotence est une étiquette que vous posez sur un travail, par exemple « rendre la vidéo de la commande 8814 ». Si la même requête étiquetée arrive deux fois chez PopNow, le travail n’est fait qu’une fois.

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

Pourquoi en avoir besoin

Un délai dépassé vous laisse dans le doute : peut-être que la requête n’est jamais arrivée, peut-être qu’elle est arrivée et que seule la réponse s’est perdue. Sans clé, soit vous abandonnez (et l’utilisateur attend pour rien), soit vous réessayez (et vous risquez de générer et de payer deux fois). Avec une clé, réessayer est toujours sûr : vous obtenez soit une nouvelle tâche, soit celle créée un instant plus tôt.

Comportement

  • Un nouvel envoi avec la même clé, depuis la même clé API, renvoie la tâche d’origine dans son état actuel — pas de deuxième tâche, de deuxième réservation ni de deuxième débit.
  • La clé est liée à ce que vous avez envoyé : model, tout l’input et callBackUrl. L’ordre des champs et les espaces n’ont pas d’importance, mais tout vrai changement en a. Une requête modifiée avec une ancienne clé renvoie 409 ; donnez-lui une nouvelle clé.
  • Une clé est mémorisée 24 heures à partir de sa première utilisation. Ensuite, la même clé crée une nouvelle tâche facturée : arrêtez donc les nouvelles tentatives automatiques bien avant.
  • Les clés font jusqu’à 128 caractères et doivent seulement être uniques dans votre compte. Une autre clé API de votre compte ne peut pas la réutiliser : elle reçoit 409 sans l’identifiant de la tâche d’origine.
EndpointUn nouvel envoi avec la même clé…
POST /api/v1/jobs/createTaskrenvoie la tâche d’origine
POST /api/v1/jobs/retryrenvoie la tâche créée par ce retry (la clé est limitée à la tâche source)
POST /v1/videosrenvoie la tâche d’origine
POST /api/v1/assetsrenvoie la ressource déjà enregistrée (l’en-tête est obligatoire ici)

Les endpoints de lecture et ceux qui émettent des liens d’envoi ou de téléchargement ignorent l’en-tête. jobs/purge n’en a pas besoin : le répéter est déjà sans effet.

Choisir ses clés

  • Une clé par travail, pas par tentative HTTP. Chaque nouvelle tentative doit la réutiliser.
  • Dérivez-la de quelque chose que vous stockez déjà : order-8814-render-1 se recalcule après un plantage ; un UUID aléatoire, non, sauf si vous l’avez enregistré avant.
  • Si vous utilisez un UUID, générez-le avant la première tentative et gardez-le à côté du travail auquel il correspond.

Ne générez jamais la clé dans la boucle de nouvelles tentatives

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

Conflits

Tous ces cas renvoient code: 409 :

Ce qui s’est passéRéessayer ?
Deux requêtes avec la même clé sont arrivées au même momentOui — attendez quelques centaines de millisecondes et renvoyez avec la même clé
La clé a déjà servi pour une autre requêteNon — la requête a changé, il lui faut une nouvelle clé
Une autre clé API de votre compte a déjà utilisé cette cléNon — réessayez avec la clé API qui a fait la première tentative

Votre code ne doit pas se baser sur msg : autorisez donc une ou deux nouvelles tentatives sur 409, puis remontez l’erreur à une personne.

Exemple complet

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

Sur cette page