Naar de inhoud

Idempotentie

Hoe het werkt

Kies een unieke sleutel per bewerking (een UUID, of je eigen id zoals een bestelnummer) en stuur hem mee als Idempotency-Key. 24 uur lang geeft dezelfde sleutel op dezelfde route opnieuw het eerste resultaat terug, met Idempotent-Replayed: true, in plaats van het werk opnieuw te doen.

  • Dezelfde sleutel met een andere body antwoordt 422 idempotency_conflict.
  • Dezelfde sleutel terwijl het eerste verzoek nog loopt antwoordt 409 conflict: wacht en probeer opnieuw.
  • Een mislukt verzoek wordt niet opgeslagen, dus opnieuw proberen met dezelfde sleutel doet het werk.
  • Sleutels gelden voor de werkruimte, de aanroeper, de route en zijn pad. De aanroeper is de API-sleutel (of, in de app, het ingelogde lid): twee API-sleutels die dezelfde Idempotency-Key sturen, spelen elkaars antwoorden nooit af, en een geroteerde API-sleutel is een nieuwe aanroeper. Dezelfde sleutel op de routes van twee verschillende codes botst ook niet.
  • PATCH en DELETE nemen geen sleutel aan: herhalen laat dezelfde toestand achter.

Herhalingen bevatten nooit een geheim

Het opgeslagen antwoord bevat nooit een geheim. Een POST die er een teruggeeft (een webhook-endpoint registreren, en een API-sleutel maken of roteren in de app) draagt het geheim alleen in het eerste antwoord, met secretAlreadyShown: false. Een herhaling binnen de 24 uur antwoordt hetzelfde object met secret: null en secretAlreadyShown: true.

Is het eerste antwoord verloren gegaan, dan bestaat het object nog (zijn id staat in de herhaling). Roteer het geheim van het endpoint in de app (of verwijder het endpoint en registreer het opnieuw), of roteer de API-sleutel, om een nieuw geheim te krijgen.

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

Veilig opnieuw proberen

voorbeeldPOST /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 die een sleutel aannemen

Elke POST behalve de multipart-upload POST /v1/assets.

MethodePadScopeDoel
POST/v1/codescodes:writeEen code aanmakenidempotent
POST/v1/codes/movecodes:writeCodes naar een map verplaatsenidempotent
POST/v1/codes/move-domaincodes:writeCodes naar een ander domein verplaatsenidempotent
POST/v1/codes/tagscodes:writeTags toevoegen of verwijderen bij veel codesidempotent
POST/v1/codes/{id}/duplicatecodes:writeEen code duplicerenidempotent
POST/v1/codes/bulkcodes:writeTot 500 codes aanmaken als één runidempotent
POST/v1/bulk-imports/{id}/cancelcodes:writeEen run in de wachtrij of lopende run annulerenidempotent
POST/v1/bulk-imports/{id}/retrycodes:writeDe mislukte rijen van een run opnieuw proberenidempotent
POST/v1/analytics/exportsanalytics:readScans als CSV, op de achtergrond voorbereididempotent
POST/v1/reportsanalytics:readEen PDF- of CSV-analyserapport genererenidempotent
POST/v1/exportscodes:readVeel codes exporteren als één ZIPidempotent
POST/v1/tagstags:writeEen tag aanmakenidempotent
POST/v1/foldersfolders:writeEen map aanmakenidempotent
POST/v1/templatestemplates:writeEen sjabloon aanmakenidempotent
POST/v1/webhookswebhooks:writeEen endpoint en eventtypen registrerenidempotent
POST/v1/webhooks/{id}/testwebhooks:writeEen webhook.test-event sturenidempotent
POST/v1/webhooks/{id}/deliveries/{deliveryId}/redeliverwebhooks:writeEen levering opnieuw sturenidempotent
POST/v1/webhooks/{id}/recoverwebhooks:writeMislukte leveringen opnieuw sturenidempotent
POST/v1/hookswebhooks:writeEen hook abonneren op één eventidempotent
POST/v1/cardscards:writeEen visitekaartje aanmakenidempotent
POST/v1/cards/{id}/deactivatecards:writeEen visitekaartje deactiverenidempotent
POST/v1/conversionsconversions:writeEen conversie registrerenidempotent
POST/v1/nfc/tagscodes:writeEen NFC-tag registreren op een codeidempotent