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

# Idempotency

> Safely retry voucher and customer mutations using the Idempotency-Key header.

The Developer API accepts an `Idempotency-Key` request header on every
mutating endpoint so you can safely retry after a network failure without
double-creating, double-deducting, or double-updating.

## Supported endpoints

| Method | Endpoint                             | Effect of replay    |
| ------ | ------------------------------------ | ------------------- |
| POST   | `/developer/vouchers`                | No second voucher   |
| POST   | `/developer/vouchers/{code}/redeem`  | No second deduction |
| POST   | `/developer/customers`               | No second customer  |
| PATCH  | `/developer/customers/{customer_id}` | No second update    |

GET endpoints are naturally idempotent and ignore the header.

## How it works

1. Generate a unique key per logical operation (a UUID is fine). The header
   value can be up to 256 characters.
2. Send the key in the `Idempotency-Key` header on the request.
3. The server claims the key in Redis along with a SHA-256 fingerprint of
   the request body. The first request runs the operation and caches the
   response.
4. Subsequent requests with the **same key and the same body** return the
   cached response verbatim – they do not re-execute the operation.
5. The cache entry expires **24 hours** after the first request.

## Scoping

Idempotency keys are scoped to the **API key** that supplied them. Two
different API keys can use the same idempotency key value without colliding,
even within the same tenant.

## Same key, different body → 409

If you reuse an `Idempotency-Key` with a different request body within the
24-hour window, the server returns 409 Conflict instead of silently
replaying the original (now-stale) response:

```http theme={null}
HTTP/1.1 409 Conflict
Content-Type: application/json

{
  "detail": "Idempotency-Key was reused with a different request body. Use a fresh Idempotency-Key for a different operation."
}
```

This protects against the subtle bug where a client retries with a payload
that has been mutated between attempts (a different `amount`, a different
`email`, a different `first_name`) and would otherwise receive the original
response with no indication that the new payload was discarded.

## Concurrent duplicates → 409

If two requests with the same idempotency key arrive concurrently and the
first has not yet finished, the second receives:

```http theme={null}
HTTP/1.1 409 Conflict
Content-Type: application/json

{
  "detail": "A duplicate request is already being processed. Please wait a moment and try again."
}
```

Wait briefly and retry – the cached response will then be available.

## Endpoint errors release the key

If the first request fails (validation error, business-rule rejection, etc.)
the pending claim is released so you can retry with the same
`Idempotency-Key` after fixing the body. A transient endpoint error does not
permanently burn an idempotency key for 24 hours.

## Redis unavailability

If Redis is unreachable, idempotency degrades to fail-open: the operation
runs, but the response is not cached. Retries in that window will execute
again. This is rare and bounded – but design your client so a duplicate
operation (a duplicate voucher, a duplicate redemption) produces a
recoverable error rather than a silent double-write.

## Example

```bash theme={null}
curl -X POST \
  -H "X-API-Key: vg_live_EXAMPLEdoNotUseThis123456" \
  -H "Idempotency-Key: redeem-2026-04-26-VG-EXAMPLE1-001" \
  -H "Content-Type: application/json" \
  -d '{"amount": "25.00"}' \
  https://api.vouchergrid.com/api/v1/developer/vouchers/VG-EXAMPLE1/redeem
```

```ts theme={null}
const idempotencyKey = crypto.randomUUID();

const res = await fetch(
  `https://api.vouchergrid.com/api/v1/developer/vouchers/VG-EXAMPLE1/redeem`,
  {
    method: "POST",
    headers: {
      "X-API-Key": process.env.VOUCHERGRID_API_KEY!,
      "Idempotency-Key": idempotencyKey,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ amount: "25.00" }),
  },
);
```
