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

Dieser Artikel liegt noch nicht auf Deutsch vor und wird unten auf Englisch angezeigt.

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.

Cookie-Einstellungen

Sie entscheiden, welche Cookies verwendet werden. Ihre Wahl können Sie jederzeit über „Cookie-Einstellungen“ im Footer ändern. Datenschutzerklärung

Notwendig

Immer aktiv

Für den Betrieb der Website erforderlich: Ihr Hell-/Dunkel-Design und diese Cookie-Auswahl werden in Ihrem Browser gespeichert. Sie dienen nicht dem Tracking.

Analyse

Google Analytics 4 misst über Statistiken, wie Seiten genutzt werden, und hilft uns, die Website zu verbessern. Wenn Sie zustimmen, setzt es Cookies und erfasst Ihren Besuch vollständig. Wenn Sie ablehnen, setzt es keine Cookies, doch Google kann weiterhin technische Daten erhalten, etwa die aufgerufene Seite, Ihren Browsertyp und Ihre IP-Adresse.

_ga, _ga_* · bis zu 2 Jahre · Google