Aller au contenu

Idempotence

Comment ça marche

Choisissez une clé unique par opération (un UUID, ou votre propre identifiant comme un numéro de commande) et envoyez-la comme Idempotency-Key. Pendant 24 heures, la même clé sur la même route renvoie à nouveau le premier résultat, avec Idempotent-Replayed: true, au lieu de refaire le travail.

  • La même clé avec un corps différent répond 422 idempotency_conflict.
  • La même clé alors que la première requête est encore en cours répond 409 conflict : attendez et réessayez.
  • Une requête qui a échoué n’est pas conservée : la réessayer avec la même clé fait donc le travail.
  • Les clés sont limitées à l’espace de travail, à l’appelant, à la route et à son chemin. L’appelant est la clé d’API (ou, dans l’application, le membre connecté) : deux clés d’API qui envoient le même Idempotency-Key ne rejouent jamais les réponses l’une de l’autre, et une clé d’API renouvelée est un nouvel appelant. La même clé sur les routes de deux codes différents n’entre pas non plus en collision.
  • PATCH et DELETE n’acceptent pas de clé : les répéter laisse le même état.

Les rejeux ne contiennent jamais de secret

La réponse conservée n’inclut jamais de secret. Un POST qui en renvoie un (enregistrer un endpoint de webhook, créer ou renouveler une clé d’API dans l’application) ne contient le secret que dans la première réponse, avec secretAlreadyShown: false. Un rejeu pendant les 24 heures renvoie le même objet avec secret: null et secretAlreadyShown: true.

Si la première réponse a été perdue, l’objet existe toujours (son id figure dans le rejeu). Renouvelez le secret de l’endpoint dans l’application (ou supprimez l’endpoint et enregistrez-le à nouveau), ou renouvelez la clé d’API, pour obtenir un nouveau secret.

Rejeu de POST /v1/webhooks (extrait)
{  "id": "whe_3k8m2q9x7c4v1b6n",  "url": "https://hooks.example.com/dotwick",  "secret": null,  "secretAlreadyShown": true}

Réessayer sans risque

exemplePOST /v1/codes
import { randomUUID } from "node:crypto";// one key per logical operation, reused on every retry of itconst idempotencyKey = randomUUID();for (let attempt = 1; attempt <= 5; attempt++) {  const res = await fetch("https://api.dotwick.com/v1/codes", {    method: "POST",    headers: {      Authorization: `Bearer ${process.env.DOTWICK_API_KEY}`,      "Content-Type": "application/json",      "Idempotency-Key": idempotencyKey,    },    body: JSON.stringify({ name: "Spring menu", content: { type: "url", url: "https://example.com/menu" } }),  });  if (res.ok) break; // a replay answers the first result with Idempotent-Replayed: true  if (res.status !== 429 && res.status < 500) throw new Error((await res.json()).error.message);  const wait = Number(res.headers.get("Retry-After") ?? 2 ** attempt);  await new Promise((r) => setTimeout(r, wait * 1000));}

Routes qui acceptent une clé

Tout POST sauf l’envoi multipart POST /v1/assets.

MéthodeCheminScopeObjet
POST/v1/codescodes:writeCréer un codeidempotent
POST/v1/codes/movecodes:writeDéplacer des codes vers un dossieridempotent
POST/v1/codes/move-domaincodes:writeDéplacer des codes vers un autre domaineidempotent
POST/v1/codes/tagscodes:writeAjouter ou retirer des étiquettes sur plusieurs codesidempotent
POST/v1/codes/{id}/duplicatecodes:writeDupliquer un codeidempotent
POST/v1/codes/bulkcodes:writeCréer jusqu’à 500 codes en une seule sérieidempotent
POST/v1/bulk-imports/{id}/cancelcodes:writeAnnuler une série en file d’attente ou en coursidempotent
POST/v1/bulk-imports/{id}/retrycodes:writeRéessayer les lignes échouées d’une sérieidempotent
POST/v1/analytics/exportsanalytics:readScans en CSV, préparés en arrière-planidempotent
POST/v1/reportsanalytics:readGénérer un rapport statistique en PDF ou CSVidempotent
POST/v1/exportscodes:readExporter plusieurs codes dans un seul ZIPidempotent
POST/v1/tagstags:writeCréer une étiquetteidempotent
POST/v1/foldersfolders:writeCréer un dossieridempotent
POST/v1/templatestemplates:writeCréer un modèleidempotent
POST/v1/webhookswebhooks:writeEnregistrer un endpoint et ses types d’événementsidempotent
POST/v1/webhooks/{id}/testwebhooks:writeEnvoyer un événement webhook.testidempotent
POST/v1/webhooks/{id}/deliveries/{deliveryId}/redeliverwebhooks:writeRenvoyer un envoiidempotent
POST/v1/webhooks/{id}/recoverwebhooks:writeRenvoyer les envois échouésidempotent
POST/v1/hookswebhooks:writeAbonner un hook à un événementidempotent
POST/v1/cardscards:writeCréer une carte de visiteidempotent
POST/v1/cards/{id}/deactivatecards:writeDésactiver une carte de visiteidempotent
POST/v1/conversionsconversions:writeEnregistrer une conversionidempotent
POST/v1/nfc/tagscodes:writeEnregistrer un tag NFC sur un codeidempotent