Skip to main content
Every write endpoint (POST, PATCH, DELETE) requires the Idempotency-Key header. The header protects you from duplicate side effects on network retries; natural-key upsert on key resources protects you from duplicates across longer time horizons.

The header

Idempotency-Key: <arbitrary-string>
  • Client-generated. UUIDs are recommended; any unique string up to 255 characters works.
  • Scoped per (organizationUuid, key). Keys don’t collide across customers.
  • Stored with the response for 24 hours.

Behavior matrix

RequestServer behavior
First request with key K, body B1Executes normally. Stores key + hash of B1 + response. Returns response.
Retry with key K, body B1 (identical)Returns cached response without re-executing. Side effects are not re-applied.
Retry with key K, body B2 (different)Returns 422 with type: idempotency-key-reused. No side effects.
Request without header on a write endpoint400 Bad Request. Header is required.
Key K after 24h windowTreated as a new key — executes normally.

Example — safe create-then-retry

async function createBuyer() {
  const idempotencyKey = crypto.randomUUID();
  const body = {
    kycMode: 'MARKETPLACE_LED',
    vatNumber: 'DE123456789',
    companyName: 'AutoHandel Mustermann GmbH',
    address: { ... },
  };

  for (let attempt = 0; attempt < 3; attempt++) {
    const r = await fetch(url, {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${token}`,
        'Idempotency-Key': idempotencyKey,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(body),
    });

    if (r.ok) return r.json();
    if (r.status >= 500 || r.status === 408) {
      // Transient — retry with the SAME key and SAME body.
      await sleep(Math.pow(2, attempt) * 1000);
      continue;
    }
    throw new ApiError(r.status, await r.json());
  }
}
Pattern rules:
  • Generate one key per logical operation. Not per HTTP attempt.
  • Persist the key if your retry might span a process restart — reuse the same key after restart.
  • Never reuse a key for different data — even small changes trigger 422. Fresh data = fresh key.

Natural-key upsert (second layer)

Some resources are upserted on a business-level natural key independent of the Idempotency-Key header:
ResourceNatural key
Buyer(sellerId, vatNumber)
Order(sellerId, externalOrderId)
Invoice(orderUuid, type) — one preliminary + one final per order
PaymentorderUuid — one payment record per order
Behavior:
  • Same body200 OK with existing resource. Idempotent regardless of the Idempotency-Key.
  • Different body409 Conflict with current resource. Use PATCH for explicit updates.
This layer handles the case where the Idempotency-Key cache missed (e.g. 24h elapsed between retries, or a different key was generated).

Why two layers

  • Header handles short-window network-retry dedup with exact-response replay.
  • Natural key handles cross-channel dedup — the same buyer created via API and then “discovered” via web app still converges to one record.
Together they make duplicate-creation vanishingly unlikely.

What happens when I send Idempotency-Key with a GET request?

Nothing — the header is ignored. Reads are naturally idempotent; the header is parsed only on write methods.

Next

Optimistic concurrency

Preventing lost updates on concurrent edits.

Error reference

422 idempotency-key-reused and related problems.