Developers · Core concepts

Idempotency and retries

Make every write safe to retry with the Idempotency-Key header, and retry the right failures with the right backoff.

Version
v1.0.0
Base URL
https://dppapi-production.up.railway.app/v1
Format
JSON · OpenAPI 3.1
FeedbackLast updated

Cet article n'est pas encore disponible en français ; il est affiché ci-dessous en anglais.

On this pageThe Idempotency-Key header

Networks fail in the worst place: after the server has done the work and before you hear about it. An idempotency key makes the retry return the original answer instead of doing the work twice.

The Idempotency-Key header

  • Send Idempotency-Key: <unique string> on any POST or PATCH. A UUID v4 is ideal.
  • The first response — success or error — is stored for 24 hours against your API key and that key.
  • A retry with the same key and the same request (method, path and body) gets the stored response back — same status, same body — with the header Idempotent-Replayed: true. Nothing is executed a second time.
  • The same key for a different request is refused with 422 and idempotency_key_reused.
  • While the first request is still running, a concurrent retry with its key gets 409 idempotency_in_progress: wait briefly and retry.
  • A 5xx is never stored, so retrying after a server error really runs the request again. A 4xx is stored like any other answer.
  • Keys are 1–255 characters (otherwise 400 invalid_idempotency_key) and scoped to the API key that sent them, so two integrations cannot collide.
  • Dry runs are answered before idempotency is consulted: they are never stored or replayed.
A retry-safe batch
curl -X POST "https://dppapi-production.up.railway.app/v1/items/batch" \-H "Authorization: Bearer $DPP_API_KEY" \-H "Content-Type: application/json" \-H "Idempotency-Key: 7b3e0c52-1f7d-4b8a-9a52-2f0e6d1c9a44" \-d '{"modelId": "cm1q8x2k40003l70f9a8s7d6e","count": 100,"serialPrefix": "C48-2027-","placeOfManufacture": "Stuttgart, Germany"}'
422 · same key, different body
{"error": "idempotency_key_reused","request_id": "3f1c2a9e-8d4b-4c6f-9e2a-7b5d1c0e8f34"}

Choosing keys

Derive the key from the operation, not the attempt. Generate it once when the work is decided and keep it with the job, so a retry after a crash reuses it. Good keys are a UUID stored with the job, or a natural id: batch-<ERP order>-<line>, publish-<lot>-2027-01-15.

What to retry

OutcomeRetry?How
Network error, timeoutYesSame request, same idempotency key.
429YesAfter retry-after seconds.
500, 502, 503, 504YesExponential backoff with jitter, same key.
409 idempotency_in_progressYesAfter a short wait, same key.
409 organisation_frozenNoContact us; the organisation is read-only.
400, 401, 403, 404, 422NoThe request itself must change.
207Only the failed rowsFix and resend just those, with a new key.

A retry helper

import { randomUUID } from 'node:crypto';const RETRYABLE = new Set([429, 500, 502, 503, 504]);/** POST with one idempotency key across every attempt, backing off between them. */async function postWithRetry(url, body, { attempts = 5 } = {}) {const key = randomUUID(); // one key for the logical operation, not per attemptfor (let attempt = 1; ; attempt++) {let res;try {res = await fetch(url, {method: 'POST',headers: {Authorization: `Bearer ${process.env.DPP_API_KEY}`,'Content-Type': 'application/json','Idempotency-Key': key,},body: JSON.stringify(body),});} catch (networkError) {if (attempt >= attempts) throw networkError;}if (res && !RETRYABLE.has(res.status)) return res;if (attempt >= attempts) return res;const retryAfter = Number(res?.headers.get('retry-after'));const backoff = retryAfter > 0 ? retryAfter * 1000 : Math.min(30_000, 2 ** attempt * 250) * (0.5 + Math.random());await new Promise((r) => setTimeout(r, backoff));}}

Naturally idempotent operations

  • Publishing an already-published passport returns it unchanged (bulk publish reports alreadyPublished).
  • PATCH /models/{id} with the same fields leaves the same values.
  • Recording an event is not naturally idempotent — the same reading sent twice is two events. Use a key.

Préférences en matière de cookies

Vous choisissez les cookies utilisés. Vous pouvez modifier votre choix à tout moment via « Paramètres des cookies » en bas de page. Politique de confidentialité

Nécessaires

Toujours actifs

Indispensables au fonctionnement du site : votre thème clair/sombre et ce choix de cookies sont enregistrés dans votre navigateur. Ils ne servent pas à vous suivre.

Analyse

Google Analytics 4 mesure l’utilisation des pages au moyen de statistiques, ce qui nous aide à améliorer le site. Si vous acceptez, il dépose des cookies et mesure votre visite en détail. Si vous refusez, il ne dépose aucun cookie, mais Google peut tout de même recevoir des données techniques, comme la page consultée, le type de navigateur et l’adresse IP.

_ga, _ga_* · jusqu’à 2 ans · Google