Zum Inhalt springen

Idempotenz

So funktioniert es

Wählen Sie pro Vorgang einen eindeutigen Schlüssel (eine UUID oder Ihre eigene ID wie eine Bestellnummer) und senden Sie ihn als Idempotency-Key. 24 Stunden lang beantwortet derselbe Schlüssel auf derselben Route erneut das erste Ergebnis, mit Idempotent-Replayed: true, statt die Arbeit noch einmal zu tun.

  • Derselbe Schlüssel mit einem anderen Body wird mit 422 idempotency_conflict beantwortet.
  • Derselbe Schlüssel, während die erste Anfrage noch läuft, wird mit 409 conflict beantwortet: Warten Sie und versuchen Sie es erneut.
  • Eine fehlgeschlagene Anfrage wird nicht gespeichert, ein erneuter Versuch mit demselben Schlüssel führt die Arbeit also aus.
  • Schlüssel gelten für Arbeitsbereich, Aufrufer, Route und ihren Pfad. Aufrufer ist der API-Schlüssel (oder in der App das angemeldete Mitglied): Zwei API-Schlüssel, die denselben Idempotency-Key senden, spielen sich nie gegenseitig Antworten zurück, und ein rotierter API-Schlüssel ist ein neuer Aufrufer. Derselbe Schlüssel auf den Routen zweier verschiedener Codes kollidiert ebenfalls nicht.
  • PATCH und DELETE nehmen keinen Schlüssel: Sie zu wiederholen lässt denselben Zustand zurück.

Replays enthalten nie ein Secret

Die gespeicherte Antwort enthält nie ein Secret. Ein POST, der eines zurückgibt – einen Webhook-Endpunkt registrieren sowie einen API-Schlüssel in der App erstellen oder rotieren –, enthält das Secret nur in der ersten Antwort, mit secretAlreadyShown: false. Ein Replay innerhalb der 24 Stunden beantwortet dasselbe Objekt mit secret: null und secretAlreadyShown: true.

Ging die erste Antwort verloren, existiert das Objekt trotzdem (seine ID steht im Replay). Rotieren Sie das Secret des Endpunkts in der App (oder löschen Sie den Endpunkt und registrieren Sie ihn neu) oder rotieren Sie den API-Schlüssel, um ein neues Secret zu erhalten.

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

Sicher wiederholen

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

Routen, die einen Schlüssel nehmen

Jeder POST außer dem Multipart-Upload POST /v1/assets.

MethodePfadScopeZweck
POST/v1/codescodes:writeEinen Code erstellenidempotent
POST/v1/codes/movecodes:writeCodes in einen Ordner verschiebenidempotent
POST/v1/codes/move-domaincodes:writeCodes auf eine andere Domain verschiebenidempotent
POST/v1/codes/tagscodes:writeTags bei mehreren Codes hinzufügen oder entfernenidempotent
POST/v1/codes/{id}/duplicatecodes:writeEinen Code duplizierenidempotent
POST/v1/codes/bulkcodes:writeBis zu 500 Codes als einen Lauf erstellenidempotent
POST/v1/bulk-imports/{id}/cancelcodes:writeEinen wartenden oder laufenden Lauf abbrechenidempotent
POST/v1/bulk-imports/{id}/retrycodes:writeDie fehlgeschlagenen Zeilen eines Laufs wiederholenidempotent
POST/v1/analytics/exportsanalytics:readScans als CSV, im Hintergrund erstelltidempotent
POST/v1/reportsanalytics:readEinen Statistikbericht als PDF oder CSV erstellenidempotent
POST/v1/exportscodes:readViele Codes als ein ZIP exportierenidempotent
POST/v1/tagstags:writeEinen Tag erstellenidempotent
POST/v1/foldersfolders:writeEinen Ordner erstellenidempotent
POST/v1/templatestemplates:writeEine Vorlage erstellenidempotent
POST/v1/webhookswebhooks:writeEinen Endpunkt und Ereignistypen registrierenidempotent
POST/v1/webhooks/{id}/testwebhooks:writeEin webhook.test-Ereignis sendenidempotent
POST/v1/webhooks/{id}/deliveries/{deliveryId}/redeliverwebhooks:writeEine Zustellung erneut sendenidempotent
POST/v1/webhooks/{id}/recoverwebhooks:writeFehlgeschlagene Zustellungen erneut sendenidempotent
POST/v1/hookswebhooks:writeEinen Hook auf ein Ereignis abonnierenidempotent
POST/v1/cardscards:writeEine Visitenkarte erstellenidempotent
POST/v1/cards/{id}/deactivatecards:writeEine Visitenkarte deaktivierenidempotent
POST/v1/conversionsconversions:writeEine Conversion erfassenidempotent
POST/v1/nfc/tagscodes:writeEinen NFC-Tag für einen Code registrierenidempotent