> ## Documentation Index
> Fetch the complete documentation index at: https://developers.novatrade24.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency

> Safe retries via the Idempotency-Key header and natural-key upsert for Buyer, Order, Invoice, and Payment.

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

| Request                                    | Server behavior                                                                |
| ------------------------------------------ | ------------------------------------------------------------------------------ |
| First request with key `K`, body `B1`      | Executes 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 endpoint | `400 Bad Request`. Header is required.                                         |
| Key `K` after 24h window                   | Treated as a new key — executes normally.                                      |

## Example — safe create-then-retry

```typescript theme={null}
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:

| Resource | Natural key                                                 |
| -------- | ----------------------------------------------------------- |
| Buyer    | `(sellerId, vatNumber)`                                     |
| Order    | `(sellerId, externalOrderId)`                               |
| Invoice  | `(orderUuid, type)` — one preliminary + one final per order |
| Payment  | `orderUuid` — one payment record per order                  |

Behavior:

* **Same body** → `200 OK` with existing resource. Idempotent regardless of
  the `Idempotency-Key`.
* **Different body** → `409 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

<CardGroup cols={2}>
  <Card title="Optimistic concurrency" icon="code-merge" href="/concepts/optimistic-concurrency">
    Preventing lost updates on concurrent edits.
  </Card>

  <Card title="Error reference" icon="triangle-exclamation" href="/reference/errors">
    `422 idempotency-key-reused` and related problems.
  </Card>
</CardGroup>
