Vai al contenuto

Idempotenza

Come funziona

Scegli una chiave univoca per operazione (un UUID, o un tuo ID come un numero d’ordine) e inviala come Idempotency-Key. Per 24 ore, la stessa chiave sulla stessa route restituisce di nuovo il primo risultato, con Idempotent-Replayed: true, invece di rifare il lavoro.

  • La stessa chiave con un corpo diverso risponde 422 idempotency_conflict.
  • La stessa chiave mentre la prima richiesta è ancora in corso risponde 409 conflict: attendi e riprova.
  • Una richiesta fallita non viene memorizzata, quindi ritentarla con la stessa chiave fa il lavoro.
  • Le chiavi sono limitate allo spazio di lavoro, al chiamante, alla route e al suo percorso. Il chiamante è la chiave API (o, nell’app, il membro connesso): due chiavi API che inviano lo stesso Idempotency-Key non si riproducono mai le risposte a vicenda, e una chiave API ruotata è un nuovo chiamante. Nemmeno la stessa chiave sulle route di due codici diversi entra in collisione.
  • PATCH e DELETE non accettano una chiave: ripeterli lascia lo stesso stato.

Le ripetizioni non portano mai un segreto

La risposta memorizzata non include mai un segreto. Un POST che ne restituisce uno (registrare un endpoint webhook, e creare o ruotare una chiave API nell’app) porta il segreto solo nella prima risposta, con secretAlreadyShown: false. Una ripetizione entro le 24 ore risponde con lo stesso oggetto con secret: null e secretAlreadyShown: true.

Se la prima risposta è andata persa, l’oggetto esiste ancora (il suo ID è nella ripetizione). Ruota il segreto dell’endpoint nell’app (o elimina l’endpoint e registralo di nuovo), oppure ruota la chiave API, per ottenere un nuovo segreto.

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

Ritentare in sicurezza

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

Route che accettano una chiave

Ogni POST tranne il caricamento multipart POST /v1/assets.

MetodoPercorsoScopeScopo
POST/v1/codescodes:writeCrea un codiceidempotente
POST/v1/codes/movecodes:writeSposta i codici in una cartellaidempotente
POST/v1/codes/move-domaincodes:writeSposta i codici su un altro dominioidempotente
POST/v1/codes/tagscodes:writeAggiungi o rimuovi tag su molti codiciidempotente
POST/v1/codes/{id}/duplicatecodes:writeDuplica un codiceidempotente
POST/v1/codes/bulkcodes:writeCrea fino a 500 codici in un’unica esecuzioneidempotente
POST/v1/bulk-imports/{id}/cancelcodes:writeAnnulla un’esecuzione in coda o in corsoidempotente
POST/v1/bulk-imports/{id}/retrycodes:writeRiprova le righe non riuscite di un’esecuzioneidempotente
POST/v1/analytics/exportsanalytics:readScansioni in CSV, preparate in backgroundidempotente
POST/v1/reportsanalytics:readGenera un report di statistiche in PDF o CSVidempotente
POST/v1/exportscodes:readEsporta molti codici in un unico ZIPidempotente
POST/v1/tagstags:writeCrea un tagidempotente
POST/v1/foldersfolders:writeCrea una cartellaidempotente
POST/v1/templatestemplates:writeCrea un modelloidempotente
POST/v1/webhookswebhooks:writeRegistra un endpoint e i tipi di eventoidempotente
POST/v1/webhooks/{id}/testwebhooks:writeInvia un evento webhook.testidempotente
POST/v1/webhooks/{id}/deliveries/{deliveryId}/redeliverwebhooks:writeInvia di nuovo una consegnaidempotente
POST/v1/webhooks/{id}/recoverwebhooks:writeReinvia le consegne non riusciteidempotente
POST/v1/hookswebhooks:writeIscrivi un hook a un eventoidempotente
POST/v1/cardscards:writeCrea un biglietto da visitaidempotente
POST/v1/cards/{id}/deactivatecards:writeDisattiva un biglietto da visitaidempotente
POST/v1/conversionsconversions:writeRegistra una conversioneidempotente
POST/v1/nfc/tagscodes:writeRegistra un tag NFC su un codiceidempotente