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.
{ "id": "whe_3k8m2q9x7c4v1b6n", "url": "https://hooks.example.com/dotwick", "secret": null, "secretAlreadyShown": true}Ritentare in sicurezza
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.
| Metodo | Percorso | Scope | Scopo |
|---|---|---|---|
| POST | /v1/codes | codes:write | Crea un codiceidempotente |
| POST | /v1/codes/move | codes:write | Sposta i codici in una cartellaidempotente |
| POST | /v1/codes/move-domain | codes:write | Sposta i codici su un altro dominioidempotente |
| POST | /v1/codes/tags | codes:write | Aggiungi o rimuovi tag su molti codiciidempotente |
| POST | /v1/codes/{id}/duplicate | codes:write | Duplica un codiceidempotente |
| POST | /v1/codes/bulk | codes:write | Crea fino a 500 codici in un’unica esecuzioneidempotente |
| POST | /v1/bulk-imports/{id}/cancel | codes:write | Annulla un’esecuzione in coda o in corsoidempotente |
| POST | /v1/bulk-imports/{id}/retry | codes:write | Riprova le righe non riuscite di un’esecuzioneidempotente |
| POST | /v1/analytics/exports | analytics:read | Scansioni in CSV, preparate in backgroundidempotente |
| POST | /v1/reports | analytics:read | Genera un report di statistiche in PDF o CSVidempotente |
| POST | /v1/exports | codes:read | Esporta molti codici in un unico ZIPidempotente |
| POST | /v1/tags | tags:write | Crea un tagidempotente |
| POST | /v1/folders | folders:write | Crea una cartellaidempotente |
| POST | /v1/templates | templates:write | Crea un modelloidempotente |
| POST | /v1/webhooks | webhooks:write | Registra un endpoint e i tipi di eventoidempotente |
| POST | /v1/webhooks/{id}/test | webhooks:write | Invia un evento webhook.testidempotente |
| POST | /v1/webhooks/{id}/deliveries/{deliveryId}/redeliver | webhooks:write | Invia di nuovo una consegnaidempotente |
| POST | /v1/webhooks/{id}/recover | webhooks:write | Reinvia le consegne non riusciteidempotente |
| POST | /v1/hooks | webhooks:write | Iscrivi un hook a un eventoidempotente |
| POST | /v1/cards | cards:write | Crea un biglietto da visitaidempotente |
| POST | /v1/cards/{id}/deactivate | cards:write | Disattiva un biglietto da visitaidempotente |
| POST | /v1/conversions | conversions:write | Registra una conversioneidempotente |
| POST | /v1/nfc/tags | codes:write | Registra un tag NFC su un codiceidempotente |