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.
{ "id": "whe_3k8m2q9x7c4v1b6n", "url": "https://hooks.example.com/dotwick", "secret": null, "secretAlreadyShown": true}Réessayer sans risque
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éthode | Chemin | Scope | Objet |
|---|---|---|---|
| POST | /v1/codes | codes:write | Créer un codeidempotent |
| POST | /v1/codes/move | codes:write | Déplacer des codes vers un dossieridempotent |
| POST | /v1/codes/move-domain | codes:write | Déplacer des codes vers un autre domaineidempotent |
| POST | /v1/codes/tags | codes:write | Ajouter ou retirer des étiquettes sur plusieurs codesidempotent |
| POST | /v1/codes/{id}/duplicate | codes:write | Dupliquer un codeidempotent |
| POST | /v1/codes/bulk | codes:write | Créer jusqu’à 500 codes en une seule sérieidempotent |
| POST | /v1/bulk-imports/{id}/cancel | codes:write | Annuler une série en file d’attente ou en coursidempotent |
| POST | /v1/bulk-imports/{id}/retry | codes:write | Réessayer les lignes échouées d’une sérieidempotent |
| POST | /v1/analytics/exports | analytics:read | Scans en CSV, préparés en arrière-planidempotent |
| POST | /v1/reports | analytics:read | Générer un rapport statistique en PDF ou CSVidempotent |
| POST | /v1/exports | codes:read | Exporter plusieurs codes dans un seul ZIPidempotent |
| POST | /v1/tags | tags:write | Créer une étiquetteidempotent |
| POST | /v1/folders | folders:write | Créer un dossieridempotent |
| POST | /v1/templates | templates:write | Créer un modèleidempotent |
| POST | /v1/webhooks | webhooks:write | Enregistrer un endpoint et ses types d’événementsidempotent |
| POST | /v1/webhooks/{id}/test | webhooks:write | Envoyer un événement webhook.testidempotent |
| POST | /v1/webhooks/{id}/deliveries/{deliveryId}/redeliver | webhooks:write | Renvoyer un envoiidempotent |
| POST | /v1/webhooks/{id}/recover | webhooks:write | Renvoyer les envois échouésidempotent |
| POST | /v1/hooks | webhooks:write | Abonner un hook à un événementidempotent |
| POST | /v1/cards | cards:write | Créer une carte de visiteidempotent |
| POST | /v1/cards/{id}/deactivate | cards:write | Désactiver une carte de visiteidempotent |
| POST | /v1/conversions | conversions:write | Enregistrer une conversionidempotent |
| POST | /v1/nfc/tags | codes:write | Enregistrer un tag NFC sur un codeidempotent |